Compare commits

...

48 Commits

Author SHA1 Message Date
Patrick Fic
6f2b5e4c55 IO-3001 Update job costing label for ttl_adjustment 2024-11-29 11:56:18 -08:00
Patrick Fic
9ac27b6090 IO-3001 Add in adjustments to subtotal scrubbing. 2024-11-29 11:33:19 -08:00
Allan Carr
3d79be06de IO-3001 Correct Commenting of Button
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-11-27 18:02:28 -08:00
Patrick Fic
11ab7cd67e IO-3001 Null Coalesce for some items for better handling. 2024-11-22 09:18:09 -08:00
Patrick Fic
ec5258a431 Merge branch 'master-AIO' into feature/IO-3001-us-est-scrubbing 2024-11-20 14:11:14 -08:00
Patrick Fic
fbc7168bde IO-3001 branch cleanup 2024-11-20 14:03:34 -08:00
Patrick Fic
f2d9626888 IO-3001 Reverse n_ttl and g_ttl logic for audatex estimates. 2024-11-20 12:43:03 -08:00
Patrick Fic
2304e0bf02 IO-3001 Resolve towing totals issue. 2024-11-19 10:59:59 -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
Patrick Fic
e363dca3f0 IO-3001 Additional tax changes. 2024-11-15 10:56:07 -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
cb80b79e1d IO-3001 WIP CDK Adjustments. 2024-11-12 11:58:51 -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
Patrick Fic
94641ae01d IO-3001 Add resp. centers and begin QB testing. 2024-11-05 15:31:35 -08:00
Patrick Fic
9bb36d2223 Merge branch 'master-AIO' into feature/IO-3001-us-est-scrubbing 2024-11-05 08:52:49 -08:00
Patrick Fic
1205e71ea6 IO-3001 Add UI adjustments. 2024-11-01 19:54:49 -07:00
Patrick Fic
f8e65ada76 IO-3001 Initial adjustments to totals. 2024-10-30 14:06:00 -07:00
36 changed files with 12284 additions and 11319 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

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1"> <babeledit_project be_version="2.7.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project 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>
@@ -20639,6 +20702,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>time</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</name> <name>total</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -21811,6 +21895,48 @@
<folder_node> <folder_node>
<name>columns</name> <name>columns</name>
<children> <children>
<concept_node>
<name>average_human_readable</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>average_value</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>duration</name> <name>duration</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -31746,6 +31872,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>tlos_ind</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>towin</name> <name>towin</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -36253,6 +36400,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 +48528,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 +48591,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 +48654,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 +48696,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 +48759,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 +48822,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 +49018,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 +49081,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 +49144,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 +49186,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 +49249,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 +49312,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 +52181,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 +54666,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 +54949,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>

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

@@ -1,5 +1,5 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Form, notification, Popover, Tooltip } from "antd"; import { Button, Checkbox, Form, notification, Popover, Tooltip } from "antd";
import axios from "axios"; import axios from "axios";
import { t } from "i18next"; import { t } from "i18next";
import React, { useState } from "react"; import React, { useState } from "react";
@@ -60,24 +60,26 @@ export function JobLinesPartPriceChange({ job, line, refetch, technician }) {
} }
}; };
const popcontent = !technician && InstanceRenderManager({ const popcontent =
imex: null, !technician &&
rome: ( InstanceRenderManager({
<Form layout="vertical" onFinish={handleFinish} initialValues={{ act_price: line.act_price }}> imex: null,
<Form.Item name="act_price" label={t("jobs.labels.act_price_ppc")} rules={[{ required: true }]}> rome: (
<CurrencyFormItemComponent /> <Form layout="vertical" onFinish={handleFinish} initialValues={{ act_price: line.act_price }}>
</Form.Item> <Form.Item name="act_price" label={t("jobs.labels.act_price_ppc")} rules={[{ required: true }]}>
<Button <CurrencyFormItemComponent />
disabled={InstanceRenderManager({ imex: true, rome: false, promanager: true })} </Form.Item>
loading={loading} <Button
htmlType="primary" disabled={InstanceRenderManager({ imex: true, rome: false, promanager: true })}
> loading={loading}
{t("general.actions.save")} htmlType="primary"
</Button> >
</Form> {t("general.actions.save")}
), </Button>
promanager: null </Form>
}); ),
promanager: null
});
return ( return (
<JobLineConvertToLabor jobline={line} job={job}> <JobLineConvertToLabor jobline={line} job={job}>

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

@@ -22,6 +22,14 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
const data = useMemo(() => { const data = useMemo(() => {
return [ return [
...(job.job_totals?.totals?.ttl_adjustment
? [
{
key: `Subtotal Adj.`,
total: job.job_totals?.totals?.ttl_adjustment
}
]
: []),
{ {
key: t("jobs.labels.subtotal"), key: t("jobs.labels.subtotal"),
total: job.job_totals.totals.subtotal, total: job.job_totals.totals.subtotal,
@@ -102,7 +110,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax total: job.job_totals.totals.us_sales_tax_breakdown.ty4Tax
}, },
{ {
key: `${bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 || "TT"} - ${[ key: `${bodyshop.md_responsibility_centers.taxes.tax_ty5?.tax_type5 || "Adj."} - ${[
job.cieca_pft.ty5_rate1, job.cieca_pft.ty5_rate1,
job.cieca_pft.ty5_rate2, job.cieca_pft.ty5_rate2,
job.cieca_pft.ty5_rate3, job.cieca_pft.ty5_rate3,
@@ -113,6 +121,14 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
.join(", ")}%`, .join(", ")}%`,
total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax total: job.job_totals.totals.us_sales_tax_breakdown.ty5Tax
}, },
...(job.job_totals?.totals?.ttl_tax_adjustment
? [
{
key: `Tax Adj.`,
total: job.job_totals?.totals?.ttl_tax_adjustment
}
]
: []),
{ {
key: t("jobs.labels.total_sales_tax"), key: t("jobs.labels.total_sales_tax"),
bold: true, bold: true,
@@ -121,6 +137,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
.add(Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty3Tax)) .add(Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty3Tax))
.add(Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty4Tax)) .add(Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty4Tax))
.add(Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty5Tax)) .add(Dinero(job.job_totals.totals.us_sales_tax_breakdown.ty5Tax))
.add(Dinero(job.job_totals.totals.ttl_tax_adjustment))
.toJSON() .toJSON()
} }
].filter((item) => item.total.amount !== 0) ].filter((item) => item.total.amount !== 0)
@@ -141,14 +158,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 +177,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

@@ -1,6 +1,6 @@
import { gql, useApolloClient, useLazyQuery, useMutation, useQuery } from "@apollo/client"; import { gql, useApolloClient, useLazyQuery, useMutation, useQuery } from "@apollo/client";
import { useSplitTreatments } from "@splitsoftware/splitio-react"; import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Col, Row, notification } from "antd"; import { Col, Row, notification } from "antd"; //import { Button, Col, Row, notification } from "antd";
import Axios from "axios"; import Axios from "axios";
import _ from "lodash"; import _ from "lodash";
import queryString from "query-string"; import queryString from "query-string";
@@ -408,26 +408,25 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
updateSchComp={updateSchComp} updateSchComp={updateSchComp}
setSchComp={setSchComp} setSchComp={setSchComp}
/> />
{ {/* {
// currentUser.email.includes("@rome.") || currentUser.email.includes("@rome.") || currentUser.email.includes("@imex.") ? (
// currentUser.email.includes("@imex.") ? ( <Button
// <Button onClick={async () => {
// onClick={async () => { for (const record of data.available_jobs) {
// for (const record of data.available_jobs) { //Query the data
// //Query the data console.log("Start Job", record.id);
// console.log("Start Job", record.id); const { data } = await loadEstData({
// const {data} = await loadEstData({ variables: { id: record.id }
// variables: {id: record.id}, });
// }); console.log("Query has been awaited and is complete");
// console.log("Query has been awaited and is complete"); await onOwnerFindModalOk(data);
// await onOwnerFindModalOk(data); }
// } }}
// }} >
// > Add all jobs as new.
// Add all jobs as new. </Button>
// </Button> ) : null
// ) : null } */}
}
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
<JobsAvailableTableComponent <JobsAvailableTableComponent
@@ -617,6 +616,7 @@ function ResolveCCCLineIssues(estData, bodyshop) {
// ` | Act Price delete. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`; // ` | Act Price delete. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`;
estData.joblines.data[indexInEstData].act_price = 0; estData.joblines.data[indexInEstData].act_price = 0;
estData.joblines.data[indexInEstData].db_price = 0; estData.joblines.data[indexInEstData].db_price = 0;
estData.joblines.data[indexInEstData].part_type = null;
}); });
}); });
} }

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

@@ -4334,6 +4334,70 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Input /> <Input />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
{InstanceRenderManager({
promanager: "USE_ROME",
rome: (
<LayoutFormRow header={<div>Adjustments</div>} id="refund">
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? (
<>
<Form.Item
label={t("bodyshop.labels.responsibilitycenters.ttl_adjustment")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_responsibility_centers", "ttl_adjustment", "dms_acctnumber"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.responsibilitycenters.ttl_tax_adjustment")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_responsibility_centers", "ttl_tax_adjustment", "dms_acctnumber"]}
>
<Input />
</Form.Item>
</>
) : (
<>
<Form.Item
label={t("bodyshop.labels.responsibilitycenters.ttl_adjustment")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_responsibility_centers", "ttl_adjustment", "accountitem"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.responsibilitycenters.ttl_tax_adjustment")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_responsibility_centers", "ttl_tax_adjustment", "accountitem"]}
>
<Input />
</Form.Item>
</>
)}
</LayoutFormRow>
)
})}
{Qb_Multi_Ar.treatment === "on" && ( {Qb_Multi_Ar.treatment === "on" && (
<LayoutFormRow header={<div>Multiple Payers Item</div>} id="accountitem"> <LayoutFormRow header={<div>Multiple Payers Item</div>} id="accountitem">
<Form.Item <Form.Item

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

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

View File

@@ -1,14 +1,12 @@
const path = require("path"); const path = require("path");
const fs = require("fs");
const Dinero = require("dinero.js"); const Dinero = require("dinero.js");
const { gql } = require("graphql-request"); const { gql } = require("graphql-request");
const queries = require("./server/graphql-client/queries");
const GraphQLClient = require("graphql-request").GraphQLClient;
const logger = require("./server/utils/logger");
const AxiosLib = require("axios").default; const AxiosLib = require("axios").default;
const axios = AxiosLib.create(); const axios = AxiosLib.create();
const pLimit = require("p-limit");
const converter = require("json-2-csv");
// Dinero.defaultCurrency = "USD";
// Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_EVEN"; Dinero.globalRoundingMode = "HALF_EVEN";
const client = require("./server/graphql-client/graphql-client").client; const client = require("./server/graphql-client/graphql-client").client;
require("dotenv").config({ require("dotenv").config({
@@ -16,8 +14,9 @@ require("dotenv").config({
}); });
async function RunTheTest() { async function RunTheTest() {
const bodyshopids = ["b501bb82-22b2-493a-8a0f-152938194869"]; const bodyshopids = ["71f8494c-89f0-43e0-8eb2-820b52d723bc"];
const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImJhNjI1OTZmNTJmNTJlZDQ0MDQ5Mzk2YmU3ZGYzNGQyYzY0ZjQ1M2UiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUm9tZSBEZXZlbG9wbWVudCIsImh0dHBzOi8vaGFzdXJhLmlvL2p3dC9jbGFpbXMiOnsieC1oYXN1cmEtZGVmYXVsdC1yb2xlIjoidXNlciIsIngtaGFzdXJhLWFsbG93ZWQtcm9sZXMiOlsidXNlciJdLCJ4LWhhc3VyYS11c2VyLWlkIjoidDZZbTFORGxDRE9QWnIzRjliZ3VXSDRMaFNYMiJ9LCJpb2FkbWluIjp0cnVlLCJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vcm9tZS1wcm9kLTEiLCJhdWQiOiJyb21lLXByb2QtMSIsImF1dGhfdGltZSI6MTcxMDk1MTg1MCwidXNlcl9pZCI6InQ2WW0xTkRsQ0RPUFpyM0Y5Ymd1V0g0TGhTWDIiLCJzdWIiOiJ0NlltMU5EbENET1BacjNGOWJndVdINExoU1gyIiwiaWF0IjoxNzExNTczODI1LCJleHAiOjE3MTE1Nzc0MjUsImVtYWlsIjoicGF0cmlja0Byb21lLmRldiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJwYXRyaWNrQHJvbWUuZGV2Il19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.0kBySA9tJznLYj8TtncHGVWJO0IcmLKP2G1UyyXwaj45kTa25bjT9RWjM-NslX_zjOvrvmQZzisFAb6M1Jf6geNjOMLIqb8bhihhzEZK4CcRfvjT6cpZxnOO2Dp_1Y5OePbvOBS_GlfdsovVWa84OLuhYC5G_3QwHT8_2Cttz4CbrC6M_vd7QsGODJYBbVKMhOdZhzpNq7AbOUh3749WRjLMMobpnZDrmQlsyg3PAqtX1FHO25WQS2rma9QahGDSY736JfbkuZJ2XbNn0axEGpK7RQLUcuRkFUlfKqYplNbR_e1Q3kEfRAZpxBPXZysrDcbDNhbkWCoTmJ3fle55OA`; const bearerToken = `Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6ImI4Y2FjOTViNGE1YWNkZTBiOTY1NzJkZWU4YzhjOTVlZWU0OGNjY2QiLCJ0eXAiOiJKV1QifQ.eyJuYW1lIjoiUGF0cmljayBGaWMgKERFVikiLCJodHRwczovL2hhc3VyYS5pby9qd3QvY2xhaW1zIjp7IngtaGFzdXJhLWRlZmF1bHQtcm9sZSI6InVzZXIiLCJ4LWhhc3VyYS1hbGxvd2VkLXJvbGVzIjpbInVzZXIiXSwieC1oYXN1cmEtdXNlci1pZCI6ImhOSjhBRHB0REhRQkRFcXNCOFFNWVRqaURuZjEifSwiaXNzIjoiaHR0cHM6Ly9zZWN1cmV0b2tlbi5nb29nbGUuY29tL2ltZXgtZGV2IiwiYXVkIjoiaW1leC1kZXYiLCJhdXRoX3RpbWUiOjE3MzAxMzIwMjksInVzZXJfaWQiOiJoTko4QURwdERIUUJERXFzQjhRTVlUamlEbmYxIiwic3ViIjoiaE5KOEFEcHRESFFCREVxc0I4UU1ZVGppRG5mMSIsImlhdCI6MTczMDg0MTc2NSwiZXhwIjoxNzMwODQ1MzY1LCJlbWFpbCI6InBhdHJpY2tAaW1leC5kZXYiLCJlbWFpbF92ZXJpZmllZCI6ZmFsc2UsImZpcmViYXNlIjp7ImlkZW50aXRpZXMiOnsiZW1haWwiOlsicGF0cmlja0BpbWV4LmRldiJdfSwic2lnbl9pbl9wcm92aWRlciI6InBhc3N3b3JkIn19.npQWkyB5cB4wmkaBsQiY3JbvBM9vKPqf3e22nVHnSydGcQi0p9M2mca9FcDtdcWvQlShUM63FF-6KkzpovC92sHauNmzCSXRInaaCPEussUUNSJEe2gEV03tYX447LkkSmFQbJ5V6qLTIDelm25fF0MoEDVnLTgythK_9927f8cxKZH1kEow0ymDeMaWey1sRyu7n15OJMcu692mfuQnBAArGTHGJ4YmReI7tMmdrV438MLxuVpH5CLb6uzlUdZoJ__7yh0kz0lkZEeHQAL8yq-0fISbPeZ5uXuMzYGrHuuKsIPRoeShVSVnF7ov8yTT3_YrCkhYbxl0eSTfBB5OdQ`;
const { jobs } = await client.request( const { jobs } = await client.request(
gql` gql`
query GET_JOBS($bodyshopids: [uuid!]!) { query GET_JOBS($bodyshopids: [uuid!]!) {
@@ -36,69 +35,98 @@ async function RunTheTest() {
const results = []; const results = [];
for (const [index, job] of jobs.entries()) { const limit = pLimit(5); // Set concurrency limit to 3
process.stdout.cursorTo(0);
process.stdout.write(
`Processing job ${index + 1} of ${jobs.length}. Failed jobs: ${results.filter((r) => r.result !== "PASS").length}`
);
try { const tasks = jobs.map((job, index) => {
await axios.post( return limit(async () => {
`http://localhost:4000/job/totalsssu`, process.stdout.cursorTo(0);
{ id: job.id }, process.stdout.write(
{ headers: { Authorization: bearerToken } } `Processing job ${index + 1} of ${jobs.length}. Failed jobs: ${results.filter((r) => r.overallTotalCorrect !== "PASS").length}. Correct jobs because of adjustment: ${results.filter((r) => r.correctJobsBecauseOfAdjustment).length}`
); );
const { jobs_by_pk: newjob } = await client.request(
gql` try {
query GET_JOBS($id: uuid!) { await axios.post(
jobs_by_pk(id: $id) { `http://localhost:4000/job/totalsssu`,
id { id: job.id },
ro_number { headers: { Authorization: bearerToken } }
cieca_ttl );
job_totals const { jobs_by_pk: newjob } = await client.request(
ownr_fn gql`
ownr_ln query GET_JOBS($id: uuid!) {
ownr_co_nm jobs_by_pk(id: $id) {
ins_co_nm id
comment ro_number
cieca_ttl
job_totals
ownr_fn
ownr_ln
ownr_co_nm
ins_co_nm
comment
}
} }
`,
{
id: job.id
} }
`, );
{
id: job.id const result = {
id: newjob.id,
owner: `${newjob.ownr_fn} ${newjob.ownr_ln} ${job.ownr_co_nm || ""}`,
ins_co: newjob.ins_co_nm,
comment: newjob.comment,
imexsubtotal: Dinero(newjob.job_totals.totals.subtotal).toFormat("0.00"),
imextotalrepair: Dinero(newjob.job_totals.totals.total_repairs).toFormat("0.00"),
g_tax: newjob.cieca_ttl.data.g_tax,
n_ttl_amt: newjob.cieca_ttl.data.n_ttl_amt,
g_ttl_amt: newjob.cieca_ttl.data.g_ttl_amt
};
const calcTotal = newjob.job_totals.totals.total_repairs.amount;
const ttlTotal = newjob.cieca_ttl.data.g_ttl_amt * 100;
result.difference = (calcTotal - ttlTotal) / 100;
if (Math.abs(calcTotal - ttlTotal) > 3) {
result.overallTotalCorrect = "***FAIL***";
} else {
result.overallTotalCorrect = "PASS";
} }
); result.ttl_adjustment = Dinero(newjob.job_totals.totals.ttl_adjustment).toFormat();
result.ttl_tax_adjustment = Dinero(newjob.job_totals.totals.ttl_tax_adjustment).toFormat();
const result = { const calcTaxDinero = Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty1Tax)
id: newjob.id, .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty2Tax))
owner: `${newjob.ownr_fn} ${newjob.ownr_ln} ${job.ownr_co_nm || ""}`, .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty3Tax))
ins_co: newjob.ins_co_nm, .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty4Tax))
comment: newjob.comment .add(Dinero(newjob.job_totals.totals.us_sales_tax_breakdown.ty5Tax))
}; .add(Dinero(newjob.job_totals.totals.ttl_tax_adjustment));
result.calcTax = calcTaxDinero.toFormat("0.00");
const calcTax = calcTaxDinero.getAmount() / 100;
const emsTax = newjob.cieca_ttl.data.g_tax;
result.taxDifference = calcTax - emsTax;
const calcTotal = newjob.job_totals.totals.total_repairs.amount; if (Math.abs(calcTax - emsTax) > 3) {
const ttlTotal = newjob.cieca_ttl.data.g_ttl_amt * 100; result.taxCorrect = "***FAIL***";
result.difference = (calcTotal - ttlTotal) / 100; } else {
result.taxCorrect = "PASS";
}
if (Math.abs(calcTotal - ttlTotal) > 3) { results.push(result);
//Diff is greater than 5 cents. Fail it. } catch (error) {
result.result = "***FAIL***"; results.push({
} else { ro_number: job.ro_number,
result.result = "PASS"; id: job.id,
result: error.message
});
} }
// console.log(`${result.result} => RO ${job.ro_number} - ${job.id} `); });
});
results.push(result); await Promise.all(tasks);
} catch (error) {
results.push({
ro_number: job.ro_number,
id: job.id,
result: "**503 FAILURE**"
});
}
}
console.table(results.filter((r) => r.result !== "PASS")); console.table(results.filter((r) => r.overallTotalCorrect !== "PASS"));
console.log("=======================================");
const summary = results.reduce( const summary = results.reduce(
(acc, val) => { (acc, val) => {
if (val.result === "PASS") { if (val.result === "PASS") {
@@ -110,18 +138,12 @@ async function RunTheTest() {
{ pass: 0, fail: 0 } { pass: 0, fail: 0 }
); );
console.log("Pass Rate: ", ((summary.pass / (summary.fail + summary.pass)) * 100).toFixed(1)); console.log("Pass Rate: ", ((summary.pass / (summary.fail + summary.pass)) * 100).toFixed(1));
const ret = converter.json2csv(results, { emptyFieldValue: "" });
fs.writeFile(`./logs/totalstest-${Date.now()}.csv`, ret, (error) => console.log(error));
} }
RunTheTest(); RunTheTest().catch((error) => {
console.log("Error in RunTheTest: ", error);
// mutation { });
// delete_jobs(where: {shopid: {_eq: "a7ee1503-ee05-4a02-b80e-bdb11d1cc8ac"}}) {
// affected_rows
// }
// delete_owners(where: {shopid: {_eq: "a7ee1503-ee05-4a02-b80e-bdb11d1cc8ac"}}) {
// affected_rows
// }
// delete_vehicles(where: {shopid: {_eq: "a7ee1503-ee05-4a02-b80e-bdb11d1cc8ac"}}) {
// affected_rows
// }
// }

7
package-lock.json generated
View File

@@ -65,6 +65,7 @@
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"p-limit": "^3.1.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"source-map-explorer": "^2.5.2" "source-map-explorer": "^2.5.2"
}, },
@@ -7706,7 +7707,8 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
"optional": true, "devOptional": true,
"license": "MIT",
"dependencies": { "dependencies": {
"yocto-queue": "^0.1.0" "yocto-queue": "^0.1.0"
}, },
@@ -9700,7 +9702,8 @@
"version": "0.1.0", "version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
"optional": true, "devOptional": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=10" "node": ">=10"
}, },

View File

@@ -75,6 +75,7 @@
"devDependencies": { "devDependencies": {
"@trivago/prettier-plugin-sort-imports": "^4.3.0", "@trivago/prettier-plugin-sort-imports": "^4.3.0",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"p-limit": "^3.1.0",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"source-map-explorer": "^2.5.2" "source-map-explorer": "^2.5.2"
} }

View File

@@ -548,6 +548,61 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
} }
} }
if (jobs_by_pk.job_totals.totals?.ttl_adjustment) {
// Do not need to check for ImEX or Rome because ImEX uses a different totals calculation that will never set this field.
if (qbo) {
const taxAccountCode = findTaxCode(
{
local: false,
federal: InstanceManager({ imex: true, rome: false }),
state: jobs_by_pk.tax_lbr_rt === 0 ? false : true
},
bodyshop.md_responsibility_centers.sales_tax_codes
);
const QboTaxId = InstanceManager({
imex: taxCodes[taxAccountCode],
rome: CheckQBOUSATaxID({
// jobline: jobline,
type: "adjustment",
job: jobs_by_pk
})
});
InvoiceLineAdd.push({
DetailType: "SalesItemLineDetail",
Amount: Dinero(jobs_by_pk.job_totals.totals?.ttl_adjustment).toFormat(DineroQbFormat),
SalesItemLineDetail: {
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
ItemRef: {
value: items[responsibilityCenters.ttl_adjustment?.accountitem]
},
TaxCodeRef: {
value: QboTaxId
},
Qty: 1
}
});
} else {
InvoiceLineAdd.push({
ItemRef: {
FullName: responsibilityCenters.ttl_adjustment?.accountitem
},
Desc: "Adjustment",
Quantity: 1,
Amount: Dinero(jobs_by_pk.job_totals.totals?.ttl_adjustment).toFormat(DineroQbFormat),
SalesTaxCodeRef: InstanceManager({
imex: {
FullName: "E"
},
rome: {
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
}
})
});
}
}
//Add tax lines //Add tax lines
const job_totals = jobs_by_pk.job_totals; const job_totals = jobs_by_pk.job_totals;
const federal_tax = Dinero(job_totals.totals.federal_tax); const federal_tax = Dinero(job_totals.totals.federal_tax);
@@ -824,7 +879,60 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
} }
} }
} }
if (jobs_by_pk.job_totals.totals.ttl_tax_adjustment) {
// Do not need to check for ImEX or Rome because ImEX uses a different totals calculation that will never set this field.
if (qbo) {
const taxAccountCode = findTaxCode(
{
local: false,
federal: InstanceManager({ imex: true, rome: false }),
state: jobs_by_pk.tax_lbr_rt === 0 ? false : true
},
bodyshop.md_responsibility_centers.sales_tax_codes
);
const QboTaxId = InstanceManager({
imex: taxCodes[taxAccountCode],
rome: CheckQBOUSATaxID({
// jobline: jobline,
type: "adjustment",
job: jobs_by_pk
})
});
InvoiceLineAdd.push({
DetailType: "SalesItemLineDetail",
Amount: Dinero(jobs_by_pk.job_totals.totals?.ttl_tax_adjustment).toFormat(DineroQbFormat),
SalesItemLineDetail: {
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
ItemRef: {
value: items[responsibilityCenters.ttl_tax_adjustment?.accountitem]
},
TaxCodeRef: {
value: QboTaxId
},
Qty: 1
}
});
} else {
InvoiceLineAdd.push({
ItemRef: {
FullName: responsibilityCenters.ttl_tax_adjustment?.accountitem
},
Desc: "Tax Adjustment",
Quantity: 1,
Amount: Dinero(jobs_by_pk.job_totals.totals?.ttl_tax_adjustment).toFormat(DineroQbFormat),
SalesTaxCodeRef: InstanceManager({
imex: {
FullName: "E"
},
rome: {
FullName: bodyshop.md_responsibility_centers.taxes.itemexemptcode || "NON"
}
})
});
}
}
if (!qbo && InvoiceLineAdd.length === 0) { if (!qbo && InvoiceLineAdd.length === 0) {
//Handle the scenario where there is a $0 sale invoice. //Handle the scenario where there is a $0 sale invoice.
InvoiceLineAdd.push({ InvoiceLineAdd.push({

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

@@ -352,6 +352,7 @@ function calculateAllocations(connectionData, job) {
// console.log("NO MASH ACCOUNT FOUND!!"); // console.log("NO MASH ACCOUNT FOUND!!");
} }
} }
if (InstanceManager({ rome: true })) { if (InstanceManager({ rome: true })) {
//profile level adjustments for parts //profile level adjustments for parts
Object.keys(job.job_totals.parts.adjustments).forEach((key) => { Object.keys(job.job_totals.parts.adjustments).forEach((key) => {
@@ -427,6 +428,41 @@ function calculateAllocations(connectionData, job) {
} else { } else {
return { ...taxAllocations[key], tax: key }; return { ...taxAllocations[key], tax: key };
} }
}) }),
...(job.job_totals.totals.ttl_adjustment
? [
{
center: "SUB ADJ",
sale: Dinero(job.job_totals.totals.ttl_adjustment),
cost: Dinero(),
profitCenter: {
name: "SUB ADJ",
accountdesc: "SUB ADJ",
accountitem: "SUB ADJ",
accountname: "SUB ADJ",
dms_acctnumber: bodyshop.md_responsibility_centers.ttl_adjustment.dms_acctnumber
},
costCenter: {}
}
]
: []),
...(job.job_totals.totals.ttl_tax_adjustment
? [
{
center: "TAX ADJ",
sale: Dinero(job.job_totals.totals.ttl_tax_adjustment),
cost: Dinero(),
profitCenter: {
name: "TAX ADJ",
accountdesc: "TAX ADJ",
accountitem: "TAX ADJ",
accountname: "TAX ADJ",
dms_acctnumber: bodyshop.md_responsibility_centers.ttl_tax_adjustment.dms_acctnumber
},
costCenter: {}
}
]
: [])
]; ];
} }

View File

@@ -47,7 +47,11 @@ 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);
@@ -146,7 +150,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 +158,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,
@@ -176,9 +180,8 @@ async function uploadViaSFTP(allxmlsToUpload) {
for (const xmlObj of allxmlsToUpload) { for (const xmlObj of allxmlsToUpload) {
try { try {
logger.log("autohouse-sftp-upload", "DEBUG", "api", null, { filename: xmlObj.filename });
xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`); xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`);
logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, { logger.log("autohouse-sftp-upload", "DEBUG", "api", null, {
filename: xmlObj.filename, filename: xmlObj.filename,
result: xmlObj.result result: xmlObj.result
}); });
@@ -609,10 +612,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

@@ -39,7 +39,11 @@ 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);
@@ -176,9 +180,8 @@ async function uploadViaSFTP(allcsvsToUpload) {
for (const csvObj of allcsvsToUpload) { for (const csvObj of allcsvsToUpload) {
try { try {
logger.log("chatter-sftp-upload", "DEBUG", "api", null, { filename: csvObj.filename });
csvObj.result = await sftp.put(Buffer.from(csvObj.csv), `${csvObj.filename}`); csvObj.result = await sftp.put(Buffer.from(csvObj.csv), `${csvObj.filename}`);
logger.log("chatter-sftp-upload-result", "DEBUG", "api", null, { logger.log("chatter-sftp-upload", "DEBUG", "api", null, {
filename: csvObj.filename, filename: csvObj.filename,
result: csvObj.result result: csvObj.result
}); });

View File

@@ -26,174 +26,184 @@ const ftpSetup = {
password: process.env.CLAIMSCORP_PASSWORD, password: process.env.CLAIMSCORP_PASSWORD,
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data), debug: (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"]
} }
}; };
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") {
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);
const { bodyshops } = await client.request(queries.GET_CLAIMSCORP_SHOPS); //Query for the List of Bodyshop Clients.
const specificShopIds = req.body.bodyshopIds; // ['uuid];
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
const batchSize = 10;
const shopsToProcess =
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
logger.log("claimscorp-shopsToProcess-generated", "DEBUG", "api", null, null);
if (shopsToProcess.length === 0) {
logger.log("claimscorp-shopsToProcess-empty", "DEBUG", "api", null, null);
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) {
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({
subject: `ClaimsCorp Report ${moment().format("MM-DD-YY")}`,
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 })),
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 processBatch(batch, start, end) {
for (const bodyshop of batch) {
const erroredJobs = [];
try {
logger.log("claimscorp-start-shop-extract", "DEBUG", "api", bodyshop.id, { logger.log("claimscorp-start-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname shopname: bodyshop.shopname
}); });
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 { jobs, bodyshops_by_pk } = await client.request(queries.CLAIMSCORP_QUERY, {
DataFeed: { bodyshopid: bodyshop.id,
ShopInfo: { start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
ShopID: bodyshops_by_pk.claimscorpid, ...(end && { end: moment(end).endOf("day") })
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) {
logger.log("claimscorp-failed-jobs", "ERROR", "api", bodyshop.id, {
count: erroredJobs.length,
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
});
}
var ret = builder
.create(
{
// 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;
}
let sftp = new Client(); const claimsCorpObject = {
sftp.on("error", (errors) => DataFeed: {
logger.log("claimscorp-sftp-error", "ERROR", "api", null, { ShopInfo: {
...errors ShopID: bodyshops_by_pk.claimscorpid,
}) ShopName: bodyshops_by_pk.shopname,
); RO: jobs.map((j) =>
try { CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
//Connect to the FTP and upload all. erroredJobs.push({ job: job, error: error.toString() });
})
)
}
}
};
await sftp.connect(ftpSetup); if (erroredJobs.length > 0) {
logger.log("claimscorp-failed-jobs", "ERROR", "api", bodyshop.id, {
for (const xmlObj of allxmlsToUpload) { count: erroredJobs.length,
logger.log("claimscorp-sftp-upload", "DEBUG", "api", null, { jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
filename: xmlObj.filename
});
const uploadResult = await sftp.put(Buffer.from(xmlObj.xml), `/${xmlObj.filename}`);
logger.log("claimscorp-sftp-upload-result", "DEBUG", "api", null, {
uploadResult
}); });
} }
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml const ret = builder.create({}, 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) { } 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(allxmlsToUpload) {
const sftp = new Client();
sftp.on("error", (errors) =>
logger.log("claimscorp-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack })
);
try {
//Connect to the FTP and upload all.
await sftp.connect(ftpSetup);
for (const xmlObj of allxmlsToUpload) {
try {
xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`);
logger.log("claimscorp-sftp-upload", "DEBUG", "api", null, {
filename: xmlObj.filename,
result: xmlObj.result
});
} catch (error) {
logger.log("claimscorp-sftp-upload-error", "ERROR", "api", null, {
filename: xmlObj.filename,
error: error.message,
stack: error.stack
});
throw error;
}
}
} catch (error) {
logger.log("claimscorp-sftp-error", "ERROR", "api", null, { 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 +455,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,8 +16,7 @@ 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,
@@ -30,173 +29,179 @@ const ftpSetup = {
} }
}; };
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") {
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);
const { bodyshops } = await client.request(queries.GET_KAIZEN_SHOPS, { imexshopid: kaizenShopsIDs }); //Query for the List of Bodyshop Clients.
const specificShopIds = req.body.bodyshopIds; // ['uuid];
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
const batchSize = 10;
const shopsToProcess =
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
logger.log("kaizen-shopsToProcess-generated", "DEBUG", "api", null, null);
if (shopsToProcess.length === 0) {
logger.log("kaizen-shopsToProcess-empty", "DEBUG", "api", null, null);
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) {
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({
subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
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 })),
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 processBatch(batch, start, end) {
for (const bodyshop of batch) {
const erroredJobs = [];
try {
logger.log("kaizen-start-shop-extract", "DEBUG", "api", bodyshop.id, { logger.log("kaizen-start-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname shopname: bodyshop.shopname
}); });
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 { jobs, bodyshops_by_pk } = await client.request(queries.KAIZEN_QUERY, {
DataFeed: { bodyshopid: bodyshop.id,
ShopInfo: { start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
ShopName: bodyshops_by_pk.shopname, ...(end && { end: moment(end).endOf("day") })
Jobs: jobs.map((j) =>
CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
erroredJobs.push({ job: job, error: error.toString() });
})
)
}
}
};
if (erroredJobs.length > 0) {
logger.log("kaizen-failed-jobs", "ERROR", "api", bodyshop.id, {
count: erroredJobs.length,
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
});
}
var ret = builder
.create(
{
// 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;
}
let sftp = new Client(); const kaizenObject = {
sftp.on("error", (errors) => DataFeed: {
logger.log("kaizen-sftp-error", "ERROR", "api", null, { ShopInfo: {
...errors ShopName: bodyshops_by_pk.shopname,
}) Jobs: jobs.map((j) =>
); CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
try { erroredJobs.push({ job: job, error: error.toString() });
//Connect to the FTP and upload all. })
)
}
}
};
await sftp.connect(ftpSetup); if (erroredJobs.length > 0) {
logger.log("kaizen-failed-jobs", "ERROR", "api", bodyshop.id, {
for (const xmlObj of allxmlsToUpload) { count: erroredJobs.length,
logger.log("kaizen-sftp-upload", "DEBUG", "api", null, { jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
filename: xmlObj.filename
});
const uploadResult = await sftp.put(Buffer.from(xmlObj.xml), `/${xmlObj.filename}`);
logger.log("kaizen-sftp-upload-result", "DEBUG", "api", null, {
uploadResult
}); });
} }
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml const ret = builder.create({}, 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) { } 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(allxmlsToUpload) {
const sftp = new Client();
sftp.on("error", (errors) =>
logger.log("kaizen-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack })
);
try {
//Connect to the FTP and upload all.
await sftp.connect(ftpSetup);
for (const xmlObj of allxmlsToUpload) {
try {
xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`);
logger.log("kaizen-sftp-upload", "DEBUG", "api", null, {
filename: xmlObj.filename,
result: xmlObj.result
});
} catch (error) {
logger.log("kaizen-sftp-upload-error", "ERROR", "api", null, {
filename: xmlObj.filename,
error: error.message,
stack: error.stack
});
throw error;
}
}
} catch (error) {
logger.log("kaizen-sftp-error", "ERROR", "api", null, { 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 +425,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

@@ -1365,6 +1365,7 @@ exports.GET_JOB_BY_PK = `query GET_JOB_BY_PK($id: uuid!) {
cieca_pfl cieca_pfl
cieca_pft cieca_pft
cieca_pfo cieca_pfo
cieca_ttl
vehicle { vehicle {
id id
notes notes

View File

@@ -849,6 +849,41 @@ function GenerateCostingData(job) {
gppercent: formatGpPercent(0) gppercent: formatGpPercent(0)
}); });
} }
//Push adjustments to bottom line.
if (job.job_totals?.totals?.ttl_adjustment) {
//Add to totals.
const Adjustment = Dinero(job.job_totals.totals.ttl_adjustment); //Need to invert, since this is being assigned as a cost.
summaryData.totalAdditionalSales = summaryData.totalAdditionalSales.add(Adjustment);
summaryData.totalSales = summaryData.totalSales.add(Adjustment);
//Add to lines.
costCenterData.push({
id: "AdjEst",
cost_center: "Adjustment (Est. Match)",
sale_labor: Dinero().toFormat(),
sale_labor_dinero: Dinero(),
sale_parts: Dinero().toFormat(),
sale_parts_dinero: Dinero(),
sale_additional: Adjustment.toFormat(),
sale_additional_dinero: Adjustment,
sale_sublet: Dinero(),
sale_sublet_dinero: Dinero(),
sales: Adjustment.toFormat(),
sales_dinero: Adjustment,
cost_parts: Dinero().toFormat(),
cost_parts_dinero: Dinero(),
cost_labor: Dinero().toFormat(), //Adjustment.toFormat(),
cost_labor_dinero: Dinero(), // Adjustment,
cost_additional: Dinero(),
cost_additional_dinero: Dinero(),
cost_sublet: Dinero(),
cost_sublet_dinero: Dinero(),
costs: Dinero().toFormat(),
costs_dinero: Dinero(),
gpdollars_dinero: Dinero(),
gpdollars: Dinero().toFormat(),
gppercent: formatGpPercent(0)
});
}
//Final summary data massaging. //Final summary data massaging.

View File

@@ -49,7 +49,7 @@ exports.totalsSsu = async function (req, res) {
} catch (error) { } catch (error) {
logger.log("job-totals-ssu-USA-error", "ERROR", req?.user?.email, id, { logger.log("job-totals-ssu-USA-error", "ERROR", req?.user?.email, id, {
jobid: id, jobid: id,
error error: error.message
}); });
res.status(503).send(); res.status(503).send();
} }
@@ -68,6 +68,54 @@ async function TotalsServerSide(req, res) {
ret.additional = CalculateAdditional(job); ret.additional = CalculateAdditional(job);
ret.totals = CalculateTaxesTotals(job, ret); ret.totals = CalculateTaxesTotals(job, ret);
// Sub total scrubbbing.
const emsTotal =
job.cieca_ttl.data.n_ttl_amt === job.cieca_ttl.data.g_ttl_amt //It looks like sometimes, gross and net are the same, but they shouldn't be.
? job.cieca_ttl.data.n_ttl_amt - job.cieca_ttl.data.g_tax
: job.cieca_ttl.data.g_ttl_amt - job.cieca_ttl.data.g_tax; //If they are, adjust the gross total down by the tax amount.
const ttlDifference =
emsTotal -
ret.totals.subtotal
.add(
Dinero({
amount: Math.round((job.adjustment_bottom_line || 0) * 100)
}).multiply(-1) //Add back in the adjustment to the subtotal. We don't want to scrub it twice.
)
.getAmount() /
100;
if (Math.abs(ttlDifference) > 0.0) {
//If difference is greater than a pennny, we need to adjust it.
ret.totals.ttl_adjustment = Dinero({ amount: Math.round(ttlDifference * 100) });
ret.totals.subtotal = ret.totals.subtotal.add(ret.totals.ttl_adjustment);
ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_adjustment);
ret.totals.net_repairs = ret.totals.net_repairs.add(ret.totals.ttl_adjustment);
logger.log("job-totals-USA-ttl-adj", "DEBUG", null, job.id, {
adjAmount: ttlDifference
});
}
//Taxes Scrubbing
const emsTaxTotal = job.cieca_ttl.data.g_tax;
const totalUsTaxes =
(ret.totals.us_sales_tax_breakdown.ty1Tax.getAmount() +
ret.totals.us_sales_tax_breakdown.ty2Tax.getAmount() +
ret.totals.us_sales_tax_breakdown.ty3Tax.getAmount() +
ret.totals.us_sales_tax_breakdown.ty4Tax.getAmount() +
ret.totals.us_sales_tax_breakdown.ty5Tax.getAmount()) /
100;
const ttlTaxDifference = emsTaxTotal - totalUsTaxes;
if (Math.abs(ttlTaxDifference) > 0.0) {
//If difference is greater than a pennny, we need to adjust it.
ret.totals.ttl_tax_adjustment = Dinero({ amount: Math.round(ttlTaxDifference * 100) });
ret.totals.total_repairs = ret.totals.total_repairs.add(ret.totals.ttl_tax_adjustment);
ret.totals.net_repairs = ret.totals.net_repairs.add(ret.totals.ttl_tax_adjustment);
logger.log("job-totals-USA-ttl-tax-adj", "DEBUG", null, job.id, {
adjAmount: ttlTaxDifference
});
}
return ret; return ret;
} catch (error) { } catch (error) {
logger.log("job-totals-ssu-USA-error", "ERROR", req.user?.email, job.id, { logger.log("job-totals-ssu-USA-error", "ERROR", req.user?.email, job.id, {
@@ -842,17 +890,21 @@ function CalculateTaxesTotals(job, otherTotals) {
} }
}); });
//Add towing and storage taxable amounts //Add towing and storage taxable amounts
const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTTW"); const stlTowing = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTTW" || c.ttl_type === "OTTW");
const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTST"); const stlStorage = job.cieca_stl?.data.find((c) => c.ttl_typecd === "OTST" || c.ttl_type === "OTST");
if (stlTowing) if (stlTowing)
taxableAmounts.TOW = Dinero({ taxableAmounts.TOW = taxableAmounts.TOW.add(
amount: Math.round(stlTowing.t_amt * 100) Dinero({
}); amount: Math.round(stlTowing.t_amt * 100)
})
);
if (stlStorage) if (stlStorage)
taxableAmounts.TOW = Dinero({ taxableAmounts.TOW = taxableAmounts.TOW.add(
amount: Math.round(stlStorage.t_amt * 100) (taxableAmounts.TOW = Dinero({
}); amount: Math.round(stlStorage.t_amt * 100)
}))
);
const pfp = job.parts_tax_rates; const pfp = job.parts_tax_rates;
@@ -959,7 +1011,7 @@ function CalculateTaxesTotals(job, otherTotals) {
} }
} }
} catch (error) { } catch (error) {
logger.log("job-totals-USA Key with issue", "error", null, null, { logger.log("job-totals-USA Key with issue", "error", null, job.id, {
key key
}); });
} }
@@ -989,7 +1041,7 @@ function CalculateTaxesTotals(job, otherTotals) {
for (let threshCounter = 1; threshCounter <= 5; threshCounter++) { for (let threshCounter = 1; threshCounter <= 5; threshCounter++) {
const thresholdAmount = parseFloat(job.cieca_pft[`ty${tyCounter}_thres${threshCounter}`]) || 0; const thresholdAmount = parseFloat(job.cieca_pft[`ty${tyCounter}_thres${threshCounter}`]) || 0;
const thresholdTaxRate = parseFloat(job.cieca_pft[`ty${tyCounter}_rate${threshCounter}`]) || 0; const thresholdTaxRate = parseFloat(job.cieca_pft[`ty${tyCounter}_rate${threshCounter}`]) || 0;
// console.log(taxTierKey, tyCounter, threshCounter, thresholdAmount, thresholdTaxRate);
let taxableAmountInThisThreshold; let taxableAmountInThisThreshold;
if ( if (
thresholdAmount === 9999.99 || thresholdAmount === 9999.99 ||
@@ -1013,11 +1065,8 @@ function CalculateTaxesTotals(job, otherTotals) {
taxableAmountInThisThreshold = Dinero({ taxableAmountInThisThreshold = Dinero({
amount: Math.round(thresholdAmount * 100) amount: Math.round(thresholdAmount * 100)
}); });
remainingTaxableAmounts[taxTierKey] = remainingTaxableAmounts[taxTierKey].subtract( remainingTaxableAmounts[taxTierKey] =
Dinero({ remainingTaxableAmounts[taxTierKey].subtract(taxableAmountInThisThreshold);
amount: Math.round(taxableAmountInThisThreshold * 100)
})
);
} }
} }
@@ -1026,8 +1075,8 @@ function CalculateTaxesTotals(job, otherTotals) {
totalTaxByTier[taxTierKey] = totalTaxByTier[taxTierKey].add(taxAmountToAdd); totalTaxByTier[taxTierKey] = totalTaxByTier[taxTierKey].add(taxAmountToAdd);
} }
} catch (error) { } catch (error) {
logger.log("job-totals-USA - PFP Calculation Error", "error", null, null, { logger.log("job-totals-USA - PFP Calculation Error", "error", null, job.id, {
error error: error.message
}); });
} }
}); });

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