Compare commits

...

45 Commits

Author SHA1 Message Date
Allan Carr
fa24d87966 Merged in feature/IO-3027-Datapumps-Refactor (pull request #1945)
Feature/IO-3027 Datapumps Refactor
2024-11-22 04:40:01 +00:00
Allan Carr
1b6eab8488 Merged in feature/IO-2921-CARSTAR-Canada-Chatter-Integration (pull request #1946)
Feature/IO-2921 CARSTAR Canada Chatter Integration
2024-11-22 04:39:49 +00:00
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
1a6e8bc5ba Merge branch 'release/2024-11-22' into feature/IO-2921-CARSTAR-Canada-Chatter-Integration
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>

# Conflicts:
#	server/data/chatter.js
2024-11-21 15:09:46 -08:00
Allan Carr
dd4ba8a467 IO-2921 Chatter Datapump Adjustment
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-21 14:40:36 -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
80b66fd7e8 Merged in feature/IO-2921-CARSTAR-Canada-Chatter-Integration (pull request #1940)
IO-2921 Chatter modifications as per Dave Bourbeau @ Chatter

Approved-by: Dave Richer
2024-11-20 18:58:31 +00:00
Allan Carr
45ac56e0bc IO-2921 Chatter modifications as per Dave Bourbeau @ Chatter
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-20 10:57:59 -08:00
Allan Carr
1ff1de8739 Merge branch 'master-AIO' into feature/IO-2921-CARSTAR-Canada-Chatter-Integration
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-20 10:56:02 -08:00
Allan Carr
ea1cc23ee7 Merged in feature/IO-3027-Datapumps-Refactor (pull request #1937)
IO-3027 Turn Off SFTP Logging for Production ENV

Approved-by: Dave Richer
2024-11-18 21:37:17 +00: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
289a666b6d Merged in release/2024-11-15 (pull request #1935)
IO-3030 Null Check memo
2024-11-18 16:48:26 +00:00
Allan Carr
b8836c7ae1 Merged in feature/IO-3030-QBO-Payment-Private-Note (pull request #1934)
IO-3030 Null Check memo
2024-11-18 16:40:15 +00:00
Allan Carr
eca31c5618 IO-3030 Null Check memo
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-18 08:42:52 -08:00
Allan Carr
7fdbedefce Merged in release/2024-11-15 (pull request #1933)
IO-3031 Appointment Schedule View Day
2024-11-16 01:40:16 +00:00
Allan Carr
7140b8d585 Merged in feature/IO-3031-Appointment-Schedule-View-Day (pull request #1931)
IO-3031 Appointment Schedule View Day
2024-11-16 01:33:29 +00:00
Allan Carr
5eed8d9809 IO-3031 Appointment Schedule View Day
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-15 17:35:49 -08:00
Dave Richer
57fe5b4c46 Merged in release/2024-11-15 (pull request #1930)
Release/2024 11 15 into master-AIO IO-2920, IO-3027, IO-3028, IO-3029, IO-3030, IO-3031, IO-3033
2024-11-15 23:59:53 +00:00
Allan Carr
f266ee1cfe Merged in feature/IO-3028-Word-Wrap-Line-Description (pull request #1928)
IO-3028 Adjust to TextArea with autoSize
2024-11-15 20:54:44 +00:00
Allan Carr
9550de5131 IO-3028 Adjust to TextArea with autoSize
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-15 12:57:08 -08:00
Patrick Fic
1f76ff882c Remove IO Event Logging. 2024-11-15 11:03:10 -08:00
Patrick Fic
749f73a272 Merged in feature/IO-2920-cash-discounting (pull request #1927)
IO-2920 Update config & totals for discount.
2024-11-15 18:58:54 +00:00
Patrick Fic
9c1774c417 Merge branch 'release/2024-11-15' into feature/IO-2920-cash-discounting 2024-11-15 10:58:22 -08:00
Allan Carr
26b3a43ce5 Merge branch 'feature/IO-3027-Datapumps-Refactor' into release/2024-11-15
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>

# Conflicts:
#	.vscode/settings.json
2024-11-15 10:05: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
9dc4546b2e Merged in feature/IO-3033-Total-Loss-Indicator (pull request #1925)
IO-3033 Total Loss Indicator

Approved-by: Dave Richer
2024-11-15 17:47:56 +00:00
Allan Carr
95aa0e45a6 IO-3033 Total Loss Indicator
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-14 16:47:35 -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
e9e1e820a7 release/2024-11-15 - Expose S3 client through createS3Client
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-11-14 11:57:40 -08:00
Allan Carr
b027a4e618 IO-3031 Adjust prop
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-14 11:52:47 -08:00
Allan Carr
c7fc75aa5c Merged in feature/IO-3031-Appointment-Schedule-View-Day (pull request #1922)
IO-3031 View Day when Scheduling

Approved-by: Dave Richer
2024-11-14 19:50:34 +00:00
Allan Carr
98d2372daf Merged in feature/IO-3030-QBO-Payment-Private-Note (pull request #1920)
IO-3030 QBO Payment Private Note

Approved-by: Dave Richer
2024-11-14 19:40:20 +00:00
Allan Carr
bf51380167 IO-3031 View Day when Scheduling
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-14 11:19:09 -08:00
Dave Richer
1ec827097f Merged in feature/IO-3029-Enhanced-Logging-File-Based (pull request #1921)
feature/IO-3029-Enhanced-Logging-File-Based: Adjust XML and JSON log to always upload
2024-11-14 18:55:57 +00:00
Allan Carr
ff7dd7d3ea IO-3030 QBO Payment Private Note
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-14 10:37:08 -08:00
Dave Richer
8cc4f88fa7 Merged in feature/IO-3029-Enhanced-Logging-File-Based (pull request #1918)
feature/IO-3029-Enhanced-Logging-File-Based: Final Enhancements
2024-11-14 16:36:13 +00:00
Dave Richer
7e6ab3a5ff Merged in feature/IO-3029-Enhanced-Logging-File-Based (pull request #1916)
feature/IO-3029-Enhanced-Logging-File-Based: Update Stream Key name
2024-11-14 04:14:54 +00:00
Dave Richer
34f876f838 Merged in feature/IO-3029-Enhanced-Logging-File-Based (pull request #1914)
feature/IO-3029-Enhanced-Logging-File-Based: Add File based S3 Logging.
2024-11-14 03:57:24 +00:00
Allan Carr
2f3eccf3d8 Merged in feature/IO-3028-Word-Wrap-Line-Description (pull request #1912)
IO-3028 Extend to Notes
2024-11-13 18:50:32 +00:00
Allan Carr
2b3e64d607 IO-3028 Extend to Notes
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-13 10:49:47 -08:00
Allan Carr
05b20505bb Merged in feature/IO-3028-Word-Wrap-Line-Description (pull request #1910)
IO-3028 Word Wrap Line Description

Approved-by: Dave Richer
2024-11-13 18:17:34 +00:00
Allan Carr
bddeae945c IO-3028 Word Wrap Line Description
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-13 10:06:01 -08:00
Patrick Fic
5b267f03b9 Add additional GIN indexes for db. 2024-11-12 20:21:43 -08:00
Patrick Fic
9aab47d8f8 IO-2920 Update config & totals for discount. 2024-11-05 16:37:21 -08:00
Patrick Fic
f2f84e2da8 Merge branch 'master-AIO' into feature/IO-2920-cash-discounting 2024-11-05 16:00:47 -08:00
34 changed files with 11896 additions and 11281 deletions

30
.vscode/settings.json vendored
View File

@@ -8,5 +8,35 @@
"pattern": "**/IMEX.xml", "pattern": "**/IMEX.xml",
"systemId": "logs/IMEX.xsd" "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

@@ -11156,6 +11156,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>imexpay</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>insurancecos</name> <name>insurancecos</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -11198,27 +11219,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>intellipay</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>intellipay_cash_discount</name> <name>intellipay_cash_discount</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -11747,6 +11747,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>ttl_adjustment</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>ttl_tax_adjustment</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -11775,6 +11817,27 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<concept_node>
<name>romepay</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>scheduling</name> <name>scheduling</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -36253,6 +36316,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>total_cust_payable_cash_discount</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>total_repairs</name> <name>total_repairs</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48360,6 +48444,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>tasks_in_view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>tasks_on_board</name> <name>tasks_on_board</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48402,6 +48507,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>total_amount_in_view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>total_amount_on_board</name> <name>total_amount_on_board</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48444,6 +48570,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>total_hours_in_view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>total_hours_on_board</name> <name>total_hours_on_board</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48465,6 +48612,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>total_jobs_in_view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>total_jobs_on_board</name> <name>total_jobs_on_board</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48507,6 +48675,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>total_lab_in_view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>total_lab_on_board</name> <name>total_lab_on_board</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48549,6 +48738,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>total_lar_in_view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>total_lar_on_board</name> <name>total_lar_on_board</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48724,6 +48934,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>tasks_in_view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>tasks_on_board</name> <name>tasks_on_board</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48766,6 +48997,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>total_amount_in_view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>total_amount_on_board</name> <name>total_amount_on_board</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48808,6 +49060,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>total_hours_in_view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>total_hours_on_board</name> <name>total_hours_on_board</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48829,6 +49102,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>total_jobs_in_view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>total_jobs_on_board</name> <name>total_jobs_on_board</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48871,6 +49165,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>total_lab_in_view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>total_lab_on_board</name> <name>total_lab_on_board</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48913,6 +49228,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>total_lar_in_view</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>total_lar_on_board</name> <name>total_lar_on_board</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -51761,6 +52097,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>production_not_production_status</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>production_over_time</name> <name>production_over_time</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -54225,6 +54582,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>created_by</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>description</name> <name>description</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -54487,6 +54865,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>related_items</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>remind_at</name> <name>remind_at</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

12
certs/io-ftp-test.key Normal file
View File

@@ -0,0 +1,12 @@
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNlY2RzYS
1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBYJnAujo17diR0fM2Ze1d1Ft6XHm5
U31pXdFEN+rGC4SoYTdZE8q3relxMS5GwwBOvgvVUuayfid2XS8ls/CMDiMBJAYqEK4CRY
PbbPB7lLnMWsF7muFhvs+SIpPQC+vtDwM2TKlxF0Y8p+iVRpvCADoggsSze7skmJWKmMTt
8jEdEOcAAAEQIyXsOSMl7DkAAAATZWNkc2Etc2hhMi1uaXN0cDUyMQAAAAhuaXN0cDUyMQ
AAAIUEAWCZwLo6Ne3YkdHzNmXtXdRbelx5uVN9aV3RRDfqxguEqGE3WRPKt63pcTEuRsMA
Tr4L1VLmsn4ndl0vJbPwjA4jASQGKhCuAkWD22zwe5S5zFrBe5rhYb7PkiKT0Avr7Q8DNk
ypcRdGPKfolUabwgA6IILEs3u7JJiVipjE7fIxHRDnAAAAQUO5dO9G7i0bxGTP0zV3eIwv
5g0NhrQJfW/bMHS6XWwaxdpr+QZ+DbBJVzZPwYC0wLMW4bJAf+kjqUnj4wGocoTeAAAAD2
lvLWZ0cC10ZXN0LWtleQECAwQ=
-----END OPENSSH PRIVATE KEY-----

View File

@@ -0,0 +1 @@
ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFgmcC6OjXt2JHR8zZl7V3UW3pceblTfWld0UQ36sYLhKhhN1kTyret6XExLkbDAE6+C9VS5rJ+J3ZdLyWz8IwOIwEkBioQrgJFg9ts8HuUucxawXua4WG+z5Iik9AL6+0PAzZMqXEXRjyn6JVGm8IAOiCCxLN7uySYlYqYxO3yMR0Q5w== io-ftp-test-key

12
certs/io-ftp-test.ppk Normal file
View File

@@ -0,0 +1,12 @@
PuTTY-User-Key-File-3: ecdsa-sha2-nistp521
Encryption: none
Comment: io-ftp-test-key
Public-Lines: 4
AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAFgmcC6OjXt
2JHR8zZl7V3UW3pceblTfWld0UQ36sYLhKhhN1kTyret6XExLkbDAE6+C9VS5rJ+
J3ZdLyWz8IwOIwEkBioQrgJFg9ts8HuUucxawXua4WG+z5Iik9AL6+0PAzZMqXEX
Rjyn6JVGm8IAOiCCxLN7uySYlYqYxO3yMR0Q5w==
Private-Lines: 2
AAAAQUO5dO9G7i0bxGTP0zV3eIwv5g0NhrQJfW/bMHS6XWwaxdpr+QZ+DbBJVzZP
wYC0wLMW4bJAf+kjqUnj4wGocoTe
Private-MAC: d67001d47e13c43dc8bdb9c68a25356a96c1c4a6714f3c5a1836fca646b78b54

View File

@@ -1,4 +1,4 @@
import { DatePicker } from "antd"; import { DatePicker, Space, TimePicker } from "antd";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import React, { useCallback, useState } from "react"; import React, { useCallback, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -20,6 +20,7 @@ const DateTimePicker = ({
onlyFuture, onlyFuture,
onlyToday, onlyToday,
isDateOnly = false, isDateOnly = false,
isSeparatedTime = false,
bodyshop, bodyshop,
...restProps ...restProps
}) => { }) => {
@@ -87,24 +88,57 @@ const DateTimePicker = ({
return ( return (
<div onKeyDown={handleKeyDown} id={id} style={{ width: "100%" }}> <div onKeyDown={handleKeyDown} id={id} style={{ width: "100%" }}>
<DatePicker {isSeparatedTime && (
showTime={ <Space direction="vertical" style={{ width: "100%" }}>
isDateOnly <DatePicker
? false showTime={false}
: { format="MM/DD/YYYY"
format: "hh:mm a", value={value ? dayjs(value) : null}
minuteStep: 15, onChange={handleChange}
defaultValue: dayjs(dayjs(), "HH:mm:ss") placeholder={t("general.labels.date")}
} onBlur={handleBlur}
} disabledDate={handleDisabledDate}
format={isDateOnly ? "MM/DD/YYYY" : "MM/DD/YYYY hh:mm a"} isDateOnly={true}
value={value ? dayjs(value) : null} {...restProps}
onChange={handleChange} />
placeholder={isDateOnly ? t("general.labels.date") : t("general.labels.datetime")} {value && (
onBlur={onBlur || handleBlur} <TimePicker
disabledDate={handleDisabledDate} format="hh:mm a"
{...restProps} minuteStep={15}
/> defaultOpenValue={dayjs(value)
.hour(dayjs().hour())
.minute(Math.floor(dayjs().minute() / 15) * 15)
.second(0)}
onChange={(value) => {
handleChange(value);
onBlur();
}}
placeholder={t("general.labels.time")}
{...restProps}
/>
)}
</Space>
)}
{!isSeparatedTime && (
<DatePicker
showTime={
isDateOnly
? false
: {
format: "hh:mm a",
minuteStep: 15,
defaultValue: dayjs(dayjs(), "HH:mm:ss")
}
}
format={isDateOnly ? "MM/DD/YYYY" : "MM/DD/YYYY hh:mm a"}
value={value ? dayjs(value) : null}
onChange={handleChange}
placeholder={isDateOnly ? t("general.labels.date") : t("general.labels.datetime")}
onBlur={onBlur || handleBlur}
disabledDate={handleDisabledDate}
{...restProps}
/>
)}
</div> </div>
); );
}; };
@@ -116,7 +150,8 @@ DateTimePicker.propTypes = {
id: PropTypes.string, id: PropTypes.string,
onlyFuture: PropTypes.bool, onlyFuture: PropTypes.bool,
onlyToday: PropTypes.bool, onlyToday: PropTypes.bool,
isDateOnly: PropTypes.bool isDateOnly: PropTypes.bool,
isSeparatedTime: PropTypes.bool
}; };
export default connect(mapStateToProps, null)(DateTimePicker); export default connect(mapStateToProps, null)(DateTimePicker);

View File

@@ -118,8 +118,7 @@ export function JobLinesComponent({
...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {}) ...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {})
} }
}), }),
sortOrder: state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order
ellipsis: true
}, },
{ {
title: t("joblines.fields.oem_partno"), title: t("joblines.fields.oem_partno"),

View File

@@ -45,7 +45,8 @@ export default function JobLineNotePopup({ jobline, disabled }) {
if (editing) if (editing)
return ( return (
<div> <div>
<Input <Input.TextArea
autoSize
autoFocus autoFocus
suffix={loading ? <LoadingSpinner /> : null} suffix={loading ? <LoadingSpinner /> : null}
value={note} value={note}

View File

@@ -1,10 +1,10 @@
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Form, Input, InputNumber, Modal, Select, Switch } from "antd"; import { Form, Input, InputNumber, Modal, Select, Switch } from "antd";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import InputCurrency from "../form-items-formatted/currency-form-item.component"; import InputCurrency from "../form-items-formatted/currency-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import JoblinesPreset from "../job-lines-preset-button/job-lines-preset-button.component"; import JoblinesPreset from "../job-lines-preset-button/job-lines-preset-button.component";
import { useSplitTreatments } from "@splitsoftware/splitio-react"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -61,7 +61,7 @@ export function JobLinesUpsertModalComponent({ bodyshop, open, jobLine, handleCa
]} ]}
name="line_desc" name="line_desc"
> >
<Input /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
<JoblinesPreset form={form} /> <JoblinesPreset form={form} />
</LayoutFormRow> </LayoutFormRow>

View File

@@ -141,14 +141,16 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
key: t("jobs.fields.ded_amt"), key: t("jobs.fields.ded_amt"),
total: job.job_totals.totals.custPayable.deductible total: job.job_totals.totals.custPayable.deductible
}, },
...(InstanceRenderManager({ ...InstanceRenderManager({
imex: [{ imex: [
key: t("jobs.fields.federal_tax_payable"), {
total: job.job_totals.totals.custPayable.federal_tax key: t("jobs.fields.federal_tax_payable"),
}], total: job.job_totals.totals.custPayable.federal_tax
}
],
rome: [], rome: [],
promanager: "USE_ROME" promanager: "USE_ROME"
})), }),
{ {
key: t("jobs.fields.other_amount_payable"), key: t("jobs.fields.other_amount_payable"),
total: job.job_totals.totals.custPayable.other_customer_amount total: job.job_totals.totals.custPayable.other_customer_amount
@@ -158,11 +160,32 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
total: job.job_totals.totals.custPayable.dep_taxes total: job.job_totals.totals.custPayable.dep_taxes
}, },
{ ...(bodyshop.intellipay_config?.enable_cash_discount
key: t("jobs.labels.total_cust_payable"), ? [
total: job.job_totals.totals.custPayable.total, {
bold: true key: t("jobs.labels.total_cust_payable_cash_discount"),
}, total: job.job_totals.totals.custPayable.total,
bold: true
},
{
key: t("jobs.labels.total_cust_payable"),
total: Dinero(job.job_totals.totals.custPayable.total)
.add(
Dinero(job.job_totals.totals.custPayable.total).percentage(
bodyshop.intellipay_config?.cash_discount_percentage || 0
)
)
.toJSON(),
bold: true
}
]
: [
{
key: t("jobs.labels.total_cust_payable"),
total: job.job_totals.totals.custPayable.total,
bold: true
}
]),
{ {
key: t("jobs.labels.net_repairs"), key: t("jobs.labels.net_repairs"),
total: job.job_totals.totals.net_repairs, total: job.job_totals.totals.net_repairs,

View File

@@ -5,6 +5,7 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component"; import FormItemPhone, { PhoneItemFormatterValidation } from "../form-items-formatted/phone-form-item.component";
@@ -12,7 +13,6 @@ import Car from "../job-damage-visual/job-damage-visual.component";
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component"; import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component"; import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
import FormRow from "../layout-form-row/layout-form-row.component"; import FormRow from "../layout-form-row/layout-form-row.component";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
@@ -185,6 +185,9 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
<Form.Item label={t("jobs.fields.towin")} name="towin" valuePropName="checked"> <Form.Item label={t("jobs.fields.towin")} name="towin" valuePropName="checked">
<Switch disabled={jobRO} /> <Switch disabled={jobRO} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.tlos_ind")} name="tlos_ind" valuePropName="checked">
<Switch disabled={jobRO} />
</Form.Item>
</FormRow> </FormRow>
</Col> </Col>
<Col {...lossColDamage}> <Col {...lossColDamage}>

View File

@@ -1,6 +1,5 @@
import { Button, Col, Form, Input, Row, Select, Space, Switch, Typography } from "antd"; import { Button, Col, Form, Input, Row, Select, Space, Switch, Typography } from "antd";
import axios from "axios"; import axios from "axios";
import dayjs from "../../utils/day";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -8,13 +7,14 @@ import { createStructuredSelector } from "reselect";
import { calculateScheduleLoad } from "../../redux/application/application.actions"; import { calculateScheduleLoad } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import dayjs from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component"; import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import EmailInput from "../form-items-formatted/email-form-item.component"; import EmailInput from "../form-items-formatted/email-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import ScheduleDayViewContainer from "../schedule-day-view/schedule-day-view.container"; import ScheduleDayViewContainer from "../schedule-day-view/schedule-day-view.container";
import ScheduleExistingAppointmentsList from "../schedule-existing-appointments-list/schedule-existing-appointments-list.component"; import ScheduleExistingAppointmentsList from "../schedule-existing-appointments-list/schedule-existing-appointments-list.component";
import "./schedule-job-modal.scss"; import "./schedule-job-modal.scss";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -84,7 +84,7 @@ export function ScheduleJobModalComponent({
} }
]} ]}
> >
<DateTimePicker onBlur={handleDateBlur} onlyFuture /> <DateTimePicker onBlur={handleDateBlur} onlyFuture isSeparatedTime />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name="scheduled_completion" name="scheduled_completion"

View File

@@ -142,7 +142,7 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
rome: [ rome: [
{ {
key: "intellipay", key: "intellipay",
label: t("bodyshop.labels.intellipay"), label: InstanceRenderManager({ rome: t("bodyshop.labels.romepay"), imex: t("bodyshop.labels.imexpay") }),
children: <ShopInfoIntellipay form={form} /> children: <ShopInfoIntellipay form={form} />
} }
], ],

View File

@@ -676,7 +676,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
} }
]} ]}
> >
<Input.TextArea rows={3} /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
<Space wrap> <Space wrap>
<DeleteFilled <DeleteFilled
@@ -737,7 +737,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
} }
]} ]}
> >
<Input.TextArea rows={3} /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
<Space wrap> <Space wrap>
<DeleteFilled <DeleteFilled
@@ -1187,7 +1187,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
key={`${index}line_desc`} key={`${index}line_desc`}
name={[field.name, "line_desc"]} name={[field.name, "line_desc"]}
> >
<Input /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("joblines.fields.mod_lbr_ty")} label={t("joblines.fields.mod_lbr_ty")}
@@ -1330,7 +1330,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
} }
]} ]}
> >
<Input /> <Input.TextArea autoSize />
</Form.Item> </Form.Item>
<Space wrap> <Space wrap>

View File

@@ -39,14 +39,13 @@ export function ShopInfoIntellipay({ bodyshop, form }) {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.intellipay_config.cash_discount_percentage")} label={t("bodyshop.fields.intellipay_config.cash_discount_percentage")}
valuePropName="checked"
dependencies={[["intellipay_config", "enable_cash_discount"]]} dependencies={[["intellipay_config", "enable_cash_discount"]]}
name={["intellipay_config", "cash_discount_percentage"]} name={["intellipay_config", "cash_discount_percentage"]}
rules={[ rules={[
({ getFieldsValue }) => ({ required: form.getFieldValue(["intellipay_config", "enable_cash_discount"]) }) ({ getFieldsValue }) => ({ required: form.getFieldValue(["intellipay_config", "enable_cash_discount"]) })
]} ]}
> >
<InputNumber min={0} max={100} precision={1} suffix='%'/> <InputNumber min={0} max={100} precision={1} suffix="%" />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
</> </>

View File

@@ -81,14 +81,14 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
user: (state.user && state.user.currentUser && state.user.currentUser.email) || null, user: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
...additionalParams ...additionalParams
}; };
axios.post("/ioevent", { // axios.post("/ioevent", {
useremail: (state.user && state.user.currentUser && state.user.currentUser.email) || null, // useremail: (state.user && state.user.currentUser && state.user.currentUser.email) || null,
bodyshopid: (state.user && state.user.bodyshop && state.user.bodyshop.id) || null, // bodyshopid: (state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
operationName: eventName, // operationName: eventName,
variables: additionalParams, // variables: additionalParams,
dbevent: false, // dbevent: false,
env: `master-AIO|${import.meta.env.VITE_APP_GIT_SHA_DATE}` // env: `master-AIO|${import.meta.env.VITE_APP_GIT_SHA_DATE}`
}); // });
// console.log( // console.log(
// "%c[Analytics]", // "%c[Analytics]",
// "background-color: green ;font-weight:bold;", // "background-color: green ;font-weight:bold;",

View File

@@ -692,6 +692,7 @@ export const GET_JOB_BY_PK = gql`
tax_str_rt tax_str_rt
tax_sub_rt tax_sub_rt
tax_tow_rt tax_tow_rt
tlos_ind
towin towin
towing_payable towing_payable
unit_number unit_number

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

14
docker-build.ps1 Normal file
View File

@@ -0,0 +1,14 @@
# Stop and remove all containers, images, and networks from the Compose file
docker compose down --rmi all
# Prune all unused Docker objects including volumes
docker system prune --all --volumes --force
# Prune unused build cache
docker builder prune --all --force
# Prune all unused volumes
docker volume prune --all --force
# Rebuild and start the containers
docker compose up --build

16
docker-build.sh Normal file
View File

@@ -0,0 +1,16 @@
#!/bin/bash
# Stop and remove all containers, images, and networks from the Compose file
docker compose down --rmi all
# Prune all unused Docker objects including volumes
docker system prune --all --volumes --force
# Prune unused build cache
docker builder prune --all --force
# Prune all unused volumes
docker volume prune --all --force
# Rebuild and start the containers
docker compose up --build

View File

@@ -114,8 +114,8 @@ 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-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 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 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/io-ftp-test.key
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1 aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1 aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1
" "
# Node App: The Main IMEX API # Node App: The Main IMEX API
@@ -169,7 +169,7 @@ services:
# - redis-insight-data:/db # - redis-insight-data:/db
# ##Optional Container for SFTP/SSH Server for testing # ##Optional Container for SFTP/SSH Server for testing
# ssh-sftp-server: # ssh-sftp-server:
# image: atmoz/sftp:alpine # Using an image with SFTP support # image: atmoz/sftp:alpine # Using an image with SFTP support
# container_name: ssh-sftp-server # container_name: ssh-sftp-server
# hostname: ssh-sftp-server # hostname: ssh-sftp-server
@@ -178,9 +178,10 @@ services:
# ports: # ports:
# - "2222:22" # Expose port 22 for SSH/SFTP (mapped to 2222 on the host) # - "2222:22" # Expose port 22 for SSH/SFTP (mapped to 2222 on the host)
# volumes: # volumes:
# - ./certs/id_rsa.pub:/home/user/.ssh/keys/id_rsa.pub:ro # Mount the SSH public key # - ./certs/io-ftp-test.key.pub:/home/user/.ssh/keys/io-ftp-test.key.pub:ro # Mount the SSH public key as authorized_keys
# - ./upload:/home/user/upload # Mount a local directory for SFTP uploads # - ./upload:/home/user/upload # Mount a local directory for SFTP uploads
# environment: # environment:
# # - SFTP_USERS=user::1000::upload
# - SFTP_USERS=user:password:1000::upload # - SFTP_USERS=user:password:1000::upload
# command: > # command: >
# /bin/sh -c " # /bin/sh -c "

View File

@@ -0,0 +1,10 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE INDEX jobs_search_gin_ro_number ON jobs USING GIN ((ro_number) gin_trgm_ops);
-- CREATE INDEX jobs_search_gin_ownrfn ON jobs USING GIN ((ownr_fn) gin_trgm_ops);
-- CREATE INDEX jobs_search_gin_clm_no ON jobs USING GIN ((clm_no) gin_trgm_ops);
-- CREATE INDEX jobs_search_gin_plate_no ON jobs USING GIN ((plate_no) gin_trgm_ops);
-- CREATE INDEX jobs_search_gin_v_make_desc ON jobs USING GIN (( v_make_desc) gin_trgm_ops);
-- CREATE INDEX jobs_search_gin_v_model_desc ON jobs USING GIN (( v_model_desc) gin_trgm_ops);
-- CREATE INDEX jobs_search_gin_ownr_ln ON jobs USING GIN (( ownr_ln) gin_trgm_ops);
-- CREATE INDEX jobs_search_gin_ownr_co_nm ON jobs USING GIN (( ownr_co_nm) gin_trgm_ops);

View File

@@ -0,0 +1,8 @@
CREATE INDEX jobs_search_gin_ro_number ON jobs USING GIN ((ro_number) gin_trgm_ops);
CREATE INDEX jobs_search_gin_ownrfn ON jobs USING GIN ((ownr_fn) gin_trgm_ops);
CREATE INDEX jobs_search_gin_clm_no ON jobs USING GIN ((clm_no) gin_trgm_ops);
CREATE INDEX jobs_search_gin_plate_no ON jobs USING GIN ((plate_no) gin_trgm_ops);
CREATE INDEX jobs_search_gin_v_make_desc ON jobs USING GIN (( v_make_desc) gin_trgm_ops);
CREATE INDEX jobs_search_gin_v_model_desc ON jobs USING GIN (( v_model_desc) gin_trgm_ops);
CREATE INDEX jobs_search_gin_ownr_ln ON jobs USING GIN (( ownr_ln) gin_trgm_ops);
CREATE INDEX jobs_search_gin_ownr_co_nm ON jobs USING GIN (( ownr_co_nm) gin_trgm_ops);

11
package-lock.json generated
View File

@@ -54,7 +54,7 @@
"soap": "^1.1.5", "soap": "^1.1.5",
"socket.io": "^4.8.0", "socket.io": "^4.8.0",
"socket.io-adapter": "^2.5.5", "socket.io-adapter": "^2.5.5",
"ssh2-sftp-client": "^10.0.3", "ssh2-sftp-client": "^11.0.0",
"twilio": "^4.23.0", "twilio": "^4.23.0",
"uuid": "^10.0.0", "uuid": "^10.0.0",
"winston": "^3.15.0", "winston": "^3.15.0",
@@ -8740,16 +8740,17 @@
} }
}, },
"node_modules/ssh2-sftp-client": { "node_modules/ssh2-sftp-client": {
"version": "10.0.3", "version": "11.0.0",
"resolved": "https://registry.npmjs.org/ssh2-sftp-client/-/ssh2-sftp-client-10.0.3.tgz", "resolved": "https://registry.npmjs.org/ssh2-sftp-client/-/ssh2-sftp-client-11.0.0.tgz",
"integrity": "sha512-Wlhasz/OCgrlqC8IlBZhF19Uw/X/dHI8ug4sFQybPE+0sDztvgvDf7Om6o7LbRLe68E7XkFZf3qMnqAvqn1vkQ==", "integrity": "sha512-lOjgNYtioYquhtgyHwPryFNhllkuENjvCKkUXo18w/Q4UpEffCnEUBfiOTlwFdKIhG1rhrOGnA6DeKPSF2CP6w==",
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"concat-stream": "^2.0.0", "concat-stream": "^2.0.0",
"promise-retry": "^2.0.1", "promise-retry": "^2.0.1",
"ssh2": "^1.15.0" "ssh2": "^1.15.0"
}, },
"engines": { "engines": {
"node": ">=16.20.2" "node": ">=18.20.4"
}, },
"funding": { "funding": {
"type": "individual", "type": "individual",

View File

@@ -64,7 +64,7 @@
"soap": "^1.1.5", "soap": "^1.1.5",
"socket.io": "^4.8.0", "socket.io": "^4.8.0",
"socket.io-adapter": "^2.5.5", "socket.io-adapter": "^2.5.5",
"ssh2-sftp-client": "^10.0.3", "ssh2-sftp-client": "^11.0.0",
"twilio": "^4.23.0", "twilio": "^4.23.0",
"uuid": "^10.0.0", "uuid": "^10.0.0",
"winston": "^3.15.0", "winston": "^3.15.0",

View File

@@ -219,6 +219,11 @@ async function InsertPayment(oauthClient, qbo_realmId, req, payment, parentRef,
PaymentMethodRef: { PaymentMethodRef: {
value: paymentMethods[payment.type] value: paymentMethods[payment.type]
}, },
PrivateNote: payment.memo
? payment.memo.length > 4000
? payment.memo.substring(0, 4000).trim()
: payment.memo.trim()
: "",
PaymentRefNum: payment.transactionid, PaymentRefNum: payment.transactionid,
...(invoices && invoices.length === 1 && invoices[0] ...(invoices && invoices.length === 1 && invoices[0]
? { ? {

View File

@@ -25,15 +25,15 @@ const ftpSetup = {
port: process.env.AUTOHOUSE_PORT, port: process.env.AUTOHOUSE_PORT,
username: process.env.AUTOHOUSE_USER, username: process.env.AUTOHOUSE_USER,
password: process.env.AUTOHOUSE_PASSWORD, 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: { algorithms: {
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
} }
}; };
const allxmlsToUpload = [];
const allErrors = [];
exports.default = async (req, res) => { exports.default = async (req, res) => {
// Only process if in production environment. // Only process if in production environment.
if (process.env.NODE_ENV !== "production") { if (process.env.NODE_ENV !== "production") {
@@ -47,16 +47,21 @@ exports.default = async (req, res) => {
} }
// Send immediate response and continue processing. // Send immediate response and continue processing.
res.status(200).send(); res.status(202).json({
success: true,
message: "Processing request ...",
timestamp: new Date().toISOString()
});
try { try {
logger.log("autohouse-start", "DEBUG", "api", null, null); logger.log("autohouse-start", "DEBUG", "api", null, null);
const allXMLResults = [];
const allErrors = [];
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); //Query for the List of Bodyshop Clients. const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); //Query for the List of Bodyshop Clients.
const specificShopIds = req.body.bodyshopIds; // ['uuid]; const specificShopIds = req.body.bodyshopIds; // ['uuid];
const { start, end, skipUpload } = req.body; //YYYY-MM-DD const { start, end, skipUpload } = req.body; //YYYY-MM-DD
const batchSize = 10;
const shopsToProcess = const shopsToProcess =
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops; specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
logger.log("autohouse-shopsToProcess-generated", "DEBUG", "api", null, null); logger.log("autohouse-shopsToProcess-generated", "DEBUG", "api", null, null);
@@ -65,27 +70,18 @@ exports.default = async (req, res) => {
logger.log("autohouse-shopsToProcess-empty", "DEBUG", "api", null, null); logger.log("autohouse-shopsToProcess-empty", "DEBUG", "api", null, null);
return; return;
} }
const batchPromises = [];
for (let i = 0; i < shopsToProcess.length; i += batchSize) {
const batch = shopsToProcess.slice(i, i + batchSize);
const batchPromise = (async () => {
await processBatch(batch, start, end);
if (skipUpload) { await processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors);
for (const xmlObj of allxmlsToUpload) {
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
}
} else {
await uploadViaSFTP(allxmlsToUpload);
}
})();
batchPromises.push(batchPromise);
}
await Promise.all(batchPromises);
await sendServerEmail({ await sendServerEmail({
subject: `Autohouse Report ${moment().format("MM-DD-YY")}`, subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify( text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify(
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count, result: x.result })), allXMLResults.map((x) => ({
imexshopid: x.imexshopid,
filename: x.filename,
count: x.count,
result: x.result
})),
null, null,
2 2
)}` )}`
@@ -97,8 +93,8 @@ exports.default = async (req, res) => {
} }
}; };
async function processBatch(batch, start, end) { async function processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors) {
for (const bodyshop of batch) { for (const bodyshop of shopsToProcess) {
const erroredJobs = []; const erroredJobs = [];
try { try {
logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, { logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, {
@@ -128,12 +124,27 @@ async function processBatch(batch, start, end) {
}); });
} }
const ret = builder.create({}, autoHouseObject).end({ allowEmptyTags: true }); 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
};
allxmlsToUpload.push({ if (skipUpload) {
count: autoHouseObject.AutoHouseExport.RepairOrder.length, fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
xml: ret, } else {
filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml` 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, { logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, {
@@ -146,7 +157,7 @@ async function processBatch(batch, start, end) {
allErrors.push({ allErrors.push({
bodyshopid: bodyshop.id, bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid, imexshopid: bodyshop.imexshopid,
autuhouseid: bodyshop.autuhouseid, autohouseid: bodyshop.autohouseid,
fatal: true, fatal: true,
errors: [error.toString()] errors: [error.toString()]
}); });
@@ -154,7 +165,7 @@ async function processBatch(batch, start, end) {
allErrors.push({ allErrors.push({
bodyshopid: bodyshop.id, bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid, imexshopid: bodyshop.imexshopid,
autuhouseid: bodyshop.autuhouseid, autohouseid: bodyshop.autohouseid,
errors: erroredJobs.map((ej) => ({ errors: erroredJobs.map((ej) => ({
ro_number: ej.job?.ro_number, ro_number: ej.job?.ro_number,
jobid: ej.job?.id, jobid: ej.job?.id,
@@ -165,34 +176,35 @@ async function processBatch(batch, start, end) {
} }
} }
async function uploadViaSFTP(allxmlsToUpload) { async function uploadViaSFTP(xmlObj) {
const sftp = new Client(); const sftp = new Client();
sftp.on("error", (errors) => sftp.on("error", (errors) =>
logger.log("autohouse-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack }) logger.log("autohouse-sftp-connection-error", "ERROR", "api", xmlObj.bodyshopid, {
error: errors.message,
stack: errors.stack
})
); );
try { try {
//Connect to the FTP and upload all. //Connect to the FTP and upload all.
await sftp.connect(ftpSetup); await sftp.connect(ftpSetup);
for (const xmlObj of allxmlsToUpload) { try {
try { xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`);
logger.log("autohouse-sftp-upload", "DEBUG", "api", null, { filename: xmlObj.filename }); logger.log("autohouse-sftp-upload", "DEBUG", "api", xmlObj.bodyshopid, {
xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`); imexshopid: xmlObj.imexshopid,
logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, { filename: xmlObj.filename,
filename: xmlObj.filename, result: xmlObj.result
result: xmlObj.result });
}); } catch (error) {
} catch (error) { logger.log("autohouse-sftp-upload-error", "ERROR", "api", xmlObj.bodyshopid, {
logger.log("autohouse-sftp-upload-error", "ERROR", "api", null, { filename: xmlObj.filename,
filename: xmlObj.filename, error: error.message,
error: error.message, stack: error.stack
stack: error.stack });
}); throw error;
throw error;
}
} }
} catch (error) { } catch (error) {
logger.log("autohouse-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack }); logger.log("autohouse-sftp-error", "ERROR", "api", xmlObj.bodyshopid, { error: error.message, stack: error.stack });
throw error; throw error;
} finally { } finally {
sftp.end(); sftp.end();
@@ -609,10 +621,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
}; };
return ret; return ret;
} catch (error) { } catch (error) {
logger.log("autohouse-job-calculate-error", "ERROR", "api", null, { logger.log("autohouse-job-calculate-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
error
});
errorCallback({ jobid: job.id, ro_number: job.ro_number, error }); errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
} }
}; };

View File

@@ -17,15 +17,15 @@ const ftpSetup = {
port: process.env.CHATTER_PORT, port: process.env.CHATTER_PORT,
username: process.env.CHATTER_USER, username: process.env.CHATTER_USER,
privateKey: null, privateKey: null,
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: { algorithms: {
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
} }
}; };
const allcsvsToUpload = [];
const allErrors = [];
exports.default = async (req, res) => { exports.default = async (req, res) => {
// Only process if in production environment. // Only process if in production environment.
if (process.env.NODE_ENV !== "production") { if (process.env.NODE_ENV !== "production") {
@@ -39,16 +39,21 @@ exports.default = async (req, res) => {
} }
// Send immediate response and continue processing. // Send immediate response and continue processing.
res.status(200).send(); res.status(202).json({
success: true,
message: "Processing request ...",
timestamp: new Date().toISOString()
});
try { try {
logger.log("chatter-start", "DEBUG", "api", null, null); logger.log("chatter-start", "DEBUG", "api", null, null);
const allChatterObjects = [];
const allErrors = [];
const { bodyshops } = await client.request(queries.GET_CHATTER_SHOPS); //Query for the List of Bodyshop Clients. const { bodyshops } = await client.request(queries.GET_CHATTER_SHOPS); //Query for the List of Bodyshop Clients.
const specificShopIds = req.body.bodyshopIds; // ['uuid]; const specificShopIds = req.body.bodyshopIds; // ['uuid];
const { start, end, skipUpload } = req.body; //YYYY-MM-DD const { start, end, skipUpload } = req.body; //YYYY-MM-DD
const batchSize = 10;
const shopsToProcess = const shopsToProcess =
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops; specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
logger.log("chatter-shopsToProcess-generated", "DEBUG", "api", null, null); logger.log("chatter-shopsToProcess-generated", "DEBUG", "api", null, null);
@@ -58,29 +63,24 @@ exports.default = async (req, res) => {
return; return;
} }
const batchPromises = []; await processBatch(shopsToProcess, start, end, allChatterObjects, allErrors);
for (let i = 0; i < shopsToProcess.length; i += batchSize) {
const batch = shopsToProcess.slice(i, i + batchSize); const csvToUpload = {
const batchPromise = (async () => { count: allChatterObjects.length,
await processBatch(batch, start, end); csv: converter.json2csv(allChatterObjects, { emptyFieldValue: "" }),
if (skipUpload) { filename: `IMEX_ONLINE_solicitation_${moment().format("YYYYMMDD")}.csv`
for (const csvObj of allcsvsToUpload) { };
await fs.promises.writeFile(`./logs/${csvObj.filename}`, csvObj.csv);
} if (skipUpload) {
} else { await fs.promises.writeFile(`./logs/${csvToUpload.filename}`, csvToUpload.csv);
await uploadViaSFTP(allcsvsToUpload); } else {
} await uploadViaSFTP(csvToUpload);
})();
batchPromises.push(batchPromise);
} }
await Promise.all(batchPromises);
await sendServerEmail({ await sendServerEmail({
subject: `Chatter Report ${moment().format("MM-DD-YY")}`, subject: `Chatter Report ${moment().format("MM-DD-YY")}`,
text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify( text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\n
allcsvsToUpload.map((x) => ({ filename: x.filename, count: x.count, result: x.result })), Uploaded:\n${JSON.stringify({ filename: csvToUpload.filename, count: csvToUpload.count, result: csvToUpload.result }, null, 2)}`
null,
2
)}`
}); });
logger.log("chatter-end", "DEBUG", "api", null, null); logger.log("chatter-end", "DEBUG", "api", null, null);
@@ -89,8 +89,8 @@ exports.default = async (req, res) => {
} }
}; };
async function processBatch(batch, start, end) { async function processBatch(shopsToProcess, start, end, allChatterObjects, allErrors) {
for (const bodyshop of batch) { for (const bodyshop of shopsToProcess) {
try { try {
logger.log("chatter-start-shop-extract", "DEBUG", "api", bodyshop.id, { logger.log("chatter-start-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname shopname: bodyshop.shopname
@@ -112,15 +112,7 @@ async function processBatch(batch, start, end) {
phone_number: j.ownr_ph1 phone_number: j.ownr_ph1
}; };
}); });
allChatterObjects.push(...chatterObject);
const ret = converter.json2csv(chatterObject, { emptyFieldValue: "" });
allcsvsToUpload.push({
count: chatterObject.length,
csv: ret,
filename: `${bodyshop.shopname}_solicitation_${moment().format("YYYYMMDD")}.csv`
});
logger.log("chatter-end-shop-extract", "DEBUG", "api", bodyshop.id, { logger.log("chatter-end-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname shopname: bodyshop.shopname
}); });
@@ -162,7 +154,7 @@ async function getPrivateKey() {
} }
} }
async function uploadViaSFTP(allcsvsToUpload) { async function uploadViaSFTP(csvToUpload) {
const sftp = new Client(); const sftp = new Client();
sftp.on("error", (errors) => sftp.on("error", (errors) =>
logger.log("chatter-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack }) logger.log("chatter-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack })
@@ -174,22 +166,19 @@ async function uploadViaSFTP(allcsvsToUpload) {
//Connect to the FTP and upload all. //Connect to the FTP and upload all.
await sftp.connect({ ...ftpSetup, privateKey }); await sftp.connect({ ...ftpSetup, privateKey });
for (const csvObj of allcsvsToUpload) { try {
try { csvToUpload.result = await sftp.put(Buffer.from(csvToUpload.csv), `${csvToUpload.filename}`);
logger.log("chatter-sftp-upload", "DEBUG", "api", null, { filename: csvObj.filename }); logger.log("chatter-sftp-upload", "DEBUG", "api", null, {
csvObj.result = await sftp.put(Buffer.from(csvObj.csv), `${csvObj.filename}`); filename: csvToUpload.filename,
logger.log("chatter-sftp-upload-result", "DEBUG", "api", null, { result: csvToUpload.result
filename: csvObj.filename, });
result: csvObj.result } catch (error) {
}); logger.log("chatter-sftp-upload-error", "ERROR", "api", null, {
} catch (error) { filename: csvToUpload.filename,
logger.log("chatter-sftp-upload-error", "ERROR", "api", null, { error: csvToUpload.message,
filename: csvObj.filename, stack: csvToUpload.stack
error: error.message, });
stack: error.stack throw error;
});
throw error;
}
} }
} catch (error) { } catch (error) {
logger.log("chatter-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack }); logger.log("chatter-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack });

View File

@@ -24,9 +24,12 @@ const ftpSetup = {
port: process.env.CLAIMSCORP_PORT, port: process.env.CLAIMSCORP_PORT,
username: process.env.CLAIMSCORP_USER, username: process.env.CLAIMSCORP_USER,
password: process.env.CLAIMSCORP_PASSWORD, password: process.env.CLAIMSCORP_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: { algorithms: {
serverHostKey: ["ssh-rsa", "ssh-dss"] serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
} }
}; };
@@ -36,164 +39,183 @@ exports.default = async (req, res) => {
res.sendStatus(403); res.sendStatus(403);
return; return;
} }
// Only process if the appropriate token is provided.
//Query for the List of Bodyshop Clients.
logger.log("claimscorp-start", "DEBUG", "api", null, null);
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS);
const specificShopIds = req.body.bodyshopIds; // ['uuid]
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) { if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
res.sendStatus(401); res.sendStatus(401);
return; 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 { try {
for (const bodyshop of specificShopIds ? bodyshops.filter((b) => specificShopIds.includes(b.id)) : bodyshops) { logger.log("claimscorp-start", "DEBUG", "api", null, null);
logger.log("claimscorp-start-shop-extract", "DEBUG", "api", bodyshop.id, { const allXMLResults = [];
shopname: bodyshop.shopname const allErrors = [];
});
const erroredJobs = [];
try {
const { jobs, bodyshops_by_pk } = await client.request(queries.CLAIMSCORP_QUERY, {
bodyshopid: bodyshop.id,
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
...(end && { end: moment(end).endOf("day") })
});
const claimsCorpObject = { const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS); //Query for the List of Bodyshop Clients.
DataFeed: { const specificShopIds = req.body.bodyshopIds; // ['uuid];
ShopInfo: { const { start, end, skipUpload } = req.body; //YYYY-MM-DD
ShopID: bodyshops_by_pk.claimscorpid,
ShopName: bodyshops_by_pk.shopname,
RO: jobs.map((j) =>
CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
erroredJobs.push({ job: job, error: error.toString() });
})
)
}
}
};
if (erroredJobs.length > 0) { const shopsToProcess =
logger.log("claimscorp-failed-jobs", "ERROR", "api", bodyshop.id, { specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
count: erroredJobs.length, logger.log("claimscorp-shopsToProcess-generated", "DEBUG", "api", null, null);
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
});
}
var ret = builder if (shopsToProcess.length === 0) {
.create( logger.log("claimscorp-shopsToProcess-empty", "DEBUG", "api", null, null);
{
// version: "1.0",
// encoding: "UTF-8",
//keepNullNodes: true,
},
claimsCorpObject
)
.end({ allowEmptyTags: true });
allxmlsToUpload.push({
count: claimsCorpObject.DataFeed.ShopInfo.RO.length,
xml: ret,
filename: `${bodyshop.claimscorpid}-${moment().format("YYYYMMDDTHHMMss")}.xml`
});
logger.log("claimscorp-end-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname
});
} catch (error) {
//Error at the shop level.
logger.log("claimscorp-error-shop", "ERROR", "api", bodyshop.id, {
...error
});
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
claimscorpid: bodyshop.claimscorpid,
fatal: true,
errors: [error.toString()]
});
} finally {
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
claimscorpid: bodyshop.claimscorpid,
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: `ClaimsCorp 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
)}
`
});
return; return;
} }
let sftp = new Client(); await processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors);
sftp.on("error", (errors) =>
logger.log("claimscorp-sftp-error", "ERROR", "api", null, { await sendServerEmail({
...errors subject: `ClaimsCorp 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("claimscorp-end", "DEBUG", "api", null, null);
} catch (error) {
logger.log("claimscorp-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 { try {
//Connect to the FTP and upload all. logger.log("claimscorp-start-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname
});
await sftp.connect(ftpSetup); const { jobs, bodyshops_by_pk } = await client.request(queries.CLAIMSCORP_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) { const claimsCorpObject = {
logger.log("claimscorp-sftp-upload", "DEBUG", "api", null, { DataFeed: {
filename: xmlObj.filename ShopInfo: {
}); ShopID: bodyshops_by_pk.claimscorpid,
ShopName: bodyshops_by_pk.shopname,
RO: 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}`); if (erroredJobs.length > 0) {
logger.log("claimscorp-sftp-upload-result", "DEBUG", "api", null, { logger.log("claimscorp-failed-jobs", "ERROR", "api", bodyshop.id, {
uploadResult 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({}, claimsCorpObject).end({ allowEmptyTags: true }),
filename: `${bodyshop.claimscorpid}-${moment().format("YYYYMMDDTHHMMss")}.xml`,
count: claimsCorpObject.DataFeed.ShopInfo.RO.length
};
if (skipUpload) {
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
} else {
await uploadViaSFTP(xmlObj);
}
allXMLResults.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
claimscorpid: bodyshop.claimscorpid,
count: xmlObj.count,
filename: xmlObj.filename,
result: xmlObj.result
});
logger.log("claimscorp-end-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname
});
} catch (error) { } catch (error) {
logger.log("claimscorp-sftp-error", "ERROR", "api", null, { //Error at the shop level.
...error logger.log("claimscorp-error-shop", "ERROR", "api", bodyshop.id, { error: error.message, stack: error.stack });
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
claimscorpid: bodyshop.claimscorpid,
fatal: true,
errors: [error.toString()]
}); });
} finally { } finally {
sftp.end(); allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
claimscorpid: bodyshop.claimscorpid,
errors: erroredJobs.map((ej) => ({
ro_number: ej.job?.ro_number,
jobid: ej.job?.id,
error: ej.error
}))
});
} }
sendServerEmail({
subject: `ClaimsCorp 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("claimscorp-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("claimscorp-sftp-upload", "DEBUG", "api", xmlObj.bodyshopid, {
imexshopid: xmlObj.imexshopid,
filename: xmlObj.filename,
result: xmlObj.result
});
} catch (error) {
logger.log("claimscorp-sftp-upload-error", "ERROR", "api", xmlObj.bodyshopid, {
filename: xmlObj.filename,
error: error.message,
stack: error.stack
});
throw error;
}
} catch (error) {
logger.log("claimscorp-sftp-error", "ERROR", "api", xmlObj.bodyshopid, {
error: error.message,
stack: error.stack
});
throw error;
} finally {
sftp.end();
}
}
const CreateRepairOrderTag = (job, errorCallback) => { const CreateRepairOrderTag = (job, errorCallback) => {
//Level 2 //Level 2
@@ -445,10 +467,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
}; };
return ret; return ret;
} catch (error) { } catch (error) {
logger.log("claimscorp-job-calculate-error", "ERROR", "api", null, { logger.log("claimscorp-job-calculate-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
error
});
errorCallback({ jobid: job.id, ro_number: job.ro_number, error }); errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
} }
}; };

View File

@@ -16,15 +16,17 @@ const { sendServerEmail } = require("../email/sendemail");
const DineroFormat = "0,0.00"; const DineroFormat = "0,0.00";
const DateFormat = "MM/DD/YYYY"; const DateFormat = "MM/DD/YYYY";
const repairOpCodes = ["OP4", "OP9", "OP10"]; const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"];
const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"];
const ftpSetup = { const ftpSetup = {
host: process.env.KAIZEN_HOST, host: process.env.KAIZEN_HOST,
port: process.env.KAIZEN_PORT, port: process.env.KAIZEN_PORT,
username: process.env.KAIZEN_USER, username: process.env.KAIZEN_USER,
password: process.env.KAIZEN_PASSWORD, password: process.env.KAIZEN_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: { algorithms: {
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"] serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
} }
@@ -36,167 +38,178 @@ exports.default = async (req, res) => {
res.sendStatus(403); res.sendStatus(403);
return; return;
} }
// Only process if the appropriate token is provided.
//Query for the List of Bodyshop Clients.
logger.log("kaizen-start", "DEBUG", "api", null, null);
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE", "SHAW"];
const { bodyshops } = await client.request(queries.GET_KAIZEN_SHOPS, {
imexshopid: kaizenShopsIDs
});
const specificShopIds = req.body.bodyshopIds; // ['uuid]
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) { if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
res.sendStatus(401); res.sendStatus(401);
return; 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 { try {
for (const bodyshop of specificShopIds ? bodyshops.filter((b) => specificShopIds.includes(b.id)) : bodyshops) { logger.log("kaizen-start", "DEBUG", "api", null, null);
logger.log("kaizen-start-shop-extract", "DEBUG", "api", bodyshop.id, { const allXMLResults = [];
shopname: bodyshop.shopname const allErrors = [];
});
const erroredJobs = [];
try {
const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, {
bodyshopid: bodyshop.id,
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
...(end && { end: moment(end).endOf("day") })
});
const kaizenObject = { const { bodyshops } = await client.request(queries.GET_KAIZEN_SHOPS, { imexshopid: kaizenShopsIDs }); //Query for the List of Bodyshop Clients.
DataFeed: { const specificShopIds = req.body.bodyshopIds; // ['uuid];
ShopInfo: { const { start, end, skipUpload } = req.body; //YYYY-MM-DD
ShopName: bodyshops_by_pk.shopname,
Jobs: jobs.map((j) =>
CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
erroredJobs.push({ job: job, error: error.toString() });
})
)
}
}
};
if (erroredJobs.length > 0) { const shopsToProcess =
logger.log("kaizen-failed-jobs", "ERROR", "api", bodyshop.id, { specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
count: erroredJobs.length, logger.log("kaizen-shopsToProcess-generated", "DEBUG", "api", null, null);
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
});
}
var ret = builder if (shopsToProcess.length === 0) {
.create( logger.log("kaizen-shopsToProcess-empty", "DEBUG", "api", null, null);
{
// version: "1.0",
// encoding: "UTF-8",
//keepNullNodes: true,
},
kaizenObject
)
.end({ allowEmptyTags: true });
allxmlsToUpload.push({
count: kaizenObject.DataFeed.ShopInfo.Jobs.length,
xml: ret,
filename: `${bodyshop.shopname}-${moment().format("YYYYMMDDTHHMMss")}.xml`
});
logger.log("kaizen-end-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname
});
} catch (error) {
//Error at the shop level.
logger.log("kaizen-error-shop", "ERROR", "api", bodyshop.id, {
...error
});
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
shopname: bodyshop.shopname,
fatal: true,
errors: [error.toString()]
});
} finally {
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
shopname: bodyshop.shopname,
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: `Kaizen 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
)}
`
});
return; return;
} }
let sftp = new Client(); await processShopData(shopsToProcess, start, end, skipUpload, allXMLResults, allErrors);
sftp.on("error", (errors) =>
logger.log("kaizen-sftp-error", "ERROR", "api", null, { await sendServerEmail({
...errors subject: `Kaizen 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("kaizen-end", "DEBUG", "api", null, null);
} catch (error) {
logger.log("kaizen-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 { try {
//Connect to the FTP and upload all. logger.log("kaizen-start-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname
});
await sftp.connect(ftpSetup); const { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_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) { const kaizenObject = {
logger.log("kaizen-sftp-upload", "DEBUG", "api", null, { DataFeed: {
filename: xmlObj.filename ShopInfo: {
}); ShopName: bodyshops_by_pk.shopname,
Jobs: 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}`); if (erroredJobs.length > 0) {
logger.log("kaizen-sftp-upload-result", "DEBUG", "api", null, { logger.log("kaizen-failed-jobs", "ERROR", "api", bodyshop.id, {
uploadResult 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({}, kaizenObject).end({ allowEmptyTags: true }),
filename: `${bodyshop.shopname}-${moment().format("YYYYMMDDTHHMMss")}.xml`,
count: kaizenObject.DataFeed.ShopInfo.Jobs.length
};
if (skipUpload) {
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
} else {
await uploadViaSFTP(xmlObj);
}
allXMLResults.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
count: xmlObj.count,
filename: xmlObj.filename,
result: xmlObj.result
});
logger.log("kaizen-end-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname
});
} catch (error) { } catch (error) {
logger.log("kaizen-sftp-error", "ERROR", "api", null, { //Error at the shop level.
...error logger.log("kaizen-error-shop", "ERROR", "api", bodyshop.id, { error: error.message, stack: error.stack });
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
shopname: bodyshop.shopname,
fatal: true,
errors: [error.toString()]
}); });
} finally { } finally {
sftp.end(); allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
shopname: bodyshop.shopname,
errors: erroredJobs.map((ej) => ({
ro_number: ej.job?.ro_number,
jobid: ej.job?.id,
error: ej.error
}))
});
} }
sendServerEmail({
subject: `Kaizen 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("kaizen-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("kaizen-sftp-upload", "DEBUG", "api", xmlObj.bodyshopid, {
imexshopid: xmlObj.imexshopid,
filename: xmlObj.filename,
result: xmlObj.result
});
} catch (error) {
logger.log("kaizen-sftp-upload-error", "ERROR", "api", xmlObj.bodyshopid, {
filename: xmlObj.filename,
error: error.message,
stack: error.stack
});
throw error;
}
} catch (error) {
logger.log("kaizen-sftp-error", "ERROR", "api", xmlObj.bodyshopid, { error: error.message, stack: error.stack });
throw error;
} finally {
sftp.end();
}
}
const CreateRepairOrderTag = (job, errorCallback) => { const CreateRepairOrderTag = (job, errorCallback) => {
//Level 2 //Level 2
@@ -420,10 +433,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
}; };
return ret; return ret;
} catch (error) { } catch (error) {
logger.log("kaizen-job-calculate-error", "ERROR", "api", null, { logger.log("kaizen-job-calculate-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
error
});
errorCallback({ jobid: job.id, ro_number: job.ro_number, error }); errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
} }
}; };

View File

@@ -95,14 +95,14 @@ const createS3Client = () => {
throw error; throw error;
} }
}; };
return { return {
uploadFileToS3, uploadFileToS3,
downloadFileFromS3, downloadFileFromS3,
listFilesInS3Bucket, listFilesInS3Bucket,
deleteFileFromS3, deleteFileFromS3,
copyFileInS3, copyFileInS3,
fileExistsInS3 fileExistsInS3,
...s3Client
}; };
}; };