Compare commits

...

111 Commits

Author SHA1 Message Date
Patrick Fic
12a5f17351 Add create user. 2022-03-04 10:11:18 -08:00
Patrick Fic
a2032553d9 IO-1746 Smart Scheduling Dates to 10. 2022-03-03 12:39:18 -08:00
Patrick Fic
d22979dadc IO-1748 Remove show phone on vendor search select in report cente.r 2022-03-03 08:52:25 -08:00
Patrick Fic
c1068ec92b IO-1728 Set cc create form to be vertical. 2022-03-03 08:37:21 -08:00
Patrick Fic
f8151e387e Resolve missing fields from time tickets query. 2022-03-02 16:04:49 -08:00
Patrick Fic
b98bfe566a IO-1760 2022-03-02 15:26:18 -08:00
Patrick Fic
c0220f0ca2 IO-1756 Include documents when sending job notes. 2022-03-02 14:36:19 -08:00
Patrick Fic
3e121a1a25 IO-1759 Replace offset with timezone. 2022-03-02 12:56:29 -08:00
Patrick Fic
a2a8868223 IO-1748 Add vendor # to parts order modal. 2022-03-02 11:18:10 -08:00
Patrick Fic
80d16b4651 IO-1747 Add CSR to Visual Board. 2022-03-02 10:54:10 -08:00
Patrick Fic
4c1a333514 Autohouse Extract Updates & Remove duedate on payables. 2022-03-01 18:23:52 -08:00
Patrick Fic
0660b79c01 Autohouse Extract Updates & Remove duedate on payables. 2022-03-01 17:27:04 -08:00
Patrick Fic
63ec578b6a IO-1749 Inactive employees removed from drop downs. 2022-03-01 15:17:33 -08:00
Patrick Fic
dcf388ff7c IO-1745 Resolve scheduling showing incorrect days. 2022-03-01 14:53:42 -08:00
Patrick Fic
0cd1b41ed9 IO-1728 Remove unsaved changes popup when creating CC 2022-03-01 13:24:07 -08:00
Patrick Fic
79124daa9a IO-1719 Add invoice number to job line 2022-03-01 13:14:03 -08:00
Patrick Fic
76ec55d709 Merged in release/2022-02-18 (pull request #405)
Updated task scheduler.

Approved-by: Patrick Fic
2022-02-18 20:59:57 +00:00
Patrick Fic
375b8ba050 Updated task scheduler. 2022-02-18 09:42:42 -08:00
Patrick Fic
2192cb1e7c IO-1744 Added missing totals to job costing modal. 2022-02-17 17:14:37 -08:00
Patrick Fic
65b505035a IO-1706 Resolve print center issue. 2022-02-17 17:08:25 -08:00
Patrick Fic
191f3f96a2 IO-1744 Resolve missing sublet calcuations. 2022-02-17 17:06:02 -08:00
Patrick Fic
14d873f795 IO-1742 Remove flat rate slider on time ticket entry. 2022-02-17 16:23:51 -08:00
Patrick Fic
55ad75df8a IO-1744 Break out sublet on job costing 2022-02-17 16:17:45 -08:00
Patrick Fic
5f112e797d CI Fix. 2022-02-16 17:11:55 -08:00
Patrick Fic
b842cee076 IO-1738 PVRT Update 2022-02-16 16:45:49 -08:00
Patrick Fic
2c1c11828d IO-1740 Scoreboard to 1 decimal. 2022-02-16 15:27:13 -08:00
Patrick Fic
e7c380d780 Standardize production minute picker. 2022-02-16 15:20:56 -08:00
Patrick Fic
0958ea5ba6 IO-1706 Payroll Table 2022-02-16 14:22:39 -08:00
Patrick Fic
8031f2b2ed Merge branch 'hotfix/2022-02-14' into release/2022-02-18 2022-02-15 16:03:39 -08:00
Patrick Fic
996863fcb7 Potential resolution to date time issues post 4pm. 2022-02-15 16:03:20 -08:00
Patrick Fic
066f395a40 IO-1738 Remove PVRT from subtotal. 2022-02-15 13:41:35 -08:00
Patrick Fic
51c5d163a5 Merged in hotfix/2022-02-14 (pull request #395)
Job costing additional fix.
2022-02-15 20:17:19 +00:00
Patrick Fic
d8ec6dd997 Merge branch 'hotfix/2022-02-14' into release/2022-02-18 2022-02-15 12:16:34 -08:00
Patrick Fic
cc636bdfe2 Job costing additional fix. 2022-02-15 12:16:08 -08:00
Patrick Fic
a3e8f56728 Resolve Date Picker Issues. 2022-02-15 09:30:17 -08:00
Patrick Fic
a6f2cfba0f Merged in hotfix/2022-02-14 (pull request #393)
Revert "Potential resolution to date time issues post 4pm."
2022-02-15 17:13:36 +00:00
Patrick Fic
2e7d8df781 Revert "Potential resolution to date time issues post 4pm."
This reverts commit e79b9f9084.
2022-02-15 09:13:10 -08:00
Patrick Fic
da51aeb135 Merged in hotfix/2022-02-14 (pull request #392)
Potential resolution to date time issues post 4pm.
2022-02-15 16:27:59 +00:00
Patrick Fic
680dd98f6d Merge branch 'hotfix/2022-02-14' into release/2022-02-18 2022-02-14 19:33:13 -08:00
Patrick Fic
e79b9f9084 Potential resolution to date time issues post 4pm. 2022-02-14 19:31:26 -08:00
Patrick Fic
22e6d596e6 Merge branch 'hotfix/2022-02-14' into release/2022-02-18 2022-02-14 13:14:12 -08:00
Patrick Fic
1ba904d082 Merged in hotfix/2022-02-14 (pull request #389)
Resolve bill update error.
2022-02-14 21:13:48 +00:00
Patrick Fic
62be2b0a0a Resolve bill update error. 2022-02-14 13:11:14 -08:00
Patrick Fic
dc77930950 Resolve bill update error. 2022-02-14 13:02:54 -08:00
Patrick Fic
520b61706f Merged in release/2022-02-18 (pull request #386)
Release/2022 02 18
2022-02-14 19:03:49 +00:00
Patrick Fic
09a87dd2a7 IO-1724 Update cancel appointment label. 2022-02-14 10:43:11 -08:00
Patrick Fic
f884d2e23f IO-1721 Update time ticket time fields to be in 5 minute increments. 2022-02-14 10:41:09 -08:00
Patrick Fic
b84935efdc IO-1707 Add employee filtering to time ticket list. 2022-02-14 10:38:58 -08:00
Patrick Fic
b3aeee4f45 IO-1667 Add print center to production detail drawer. 2022-02-14 10:33:21 -08:00
Patrick Fic
06f266a292 IO-1700 Add name to cc list. 2022-02-14 10:27:41 -08:00
Patrick Fic
a2d54d5dd5 Merged in release/2022-02-11 (pull request #385)
Release/2022 02 11
2022-02-11 23:06:48 +00:00
Patrick Fic
b34694f3c4 Merged in release/2022-02-11 (pull request #384)
Add PBS Error message.
2022-02-11 23:03:19 +00:00
Patrick Fic
43b8842027 Add PBS Error message. 2022-02-11 14:45:28 -08:00
Patrick Fic
0dbb3a446a Merged in release/2022-02-11 (pull request #383)
IO-1708 Revert timezone change in app
2022-02-11 16:28:12 +00:00
Patrick Fic
75b2398421 IO-1708 Revert timezone change in app 2022-02-11 08:24:34 -08:00
Patrick Fic
2409042450 Merged in release/2022-02-11 (pull request #382)
Release/2022 02 11
2022-02-11 01:52:23 +00:00
Patrick Fic
85ccb36b2e IO-1708 Revert time zone app side changes. 2022-02-10 17:51:55 -08:00
Patrick Fic
a616921e2b IO-1708 Remove setting application side time zone. 2022-02-10 17:50:46 -08:00
Patrick Fic
f509ea07c0 Merged in release/2022-02-11 (pull request #381)
IO-1723 Add Additional GP to summary for multi costing.
2022-02-11 01:01:59 +00:00
Patrick Fic
efb62b59f4 IO-1723 Add Additional GP to summary for multi costing. 2022-02-10 17:00:59 -08:00
Patrick Fic
e263c32d83 Merged in release/2022-02-11 (pull request #380)
Release/2022 02 11
2022-02-11 00:31:18 +00:00
Patrick Fic
30afe97fba IO-1685 Task Scheduler. 2022-02-10 16:30:49 -08:00
Patrick Fic
e383b0800c IO-1723 Add GPs for additional items on costing. 2022-02-10 16:30:04 -08:00
Patrick Fic
e0507f2d17 IO-1723 Add GPs for additional items on costing. 2022-02-10 16:29:45 -08:00
Patrick Fic
cdd3841d49 IO-1708 Updated timezone for date fields. 2022-02-10 16:21:10 -08:00
Patrick Fic
4a023faf67 Merged in release/2022-02-11 (pull request #379)
IO-1718 Only allow type change based on split for part order.
2022-02-10 23:15:13 +00:00
Patrick Fic
e9f4b48839 IO-1718 Only allow type change based on split for part order. 2022-02-10 15:14:25 -08:00
Patrick Fic
90f0232ff0 Merged in release/2022-02-11 (pull request #378)
Release/2022 02 11
2022-02-10 22:49:49 +00:00
Patrick Fic
b94ea099b9 Smart Scheduling Updates. 2022-02-10 14:49:19 -08:00
Patrick Fic
9f5b1c4ea5 Resolve in house bill posting. 2022-02-10 14:21:56 -08:00
Patrick Fic
4842605035 Autohouse year parsing upddate. 2022-02-10 11:01:23 -08:00
Patrick Fic
f9f14255a8 AH Updates. 2022-02-10 10:48:31 -08:00
Patrick Fic
139dedd3e7 Add upload logging. 2022-02-10 08:35:49 -08:00
Patrick Fic
bd9c2cd0af IO-1727 Skip posting contact and vehicle for PBS.. 2022-02-09 16:46:32 -08:00
Patrick Fic
e0e2183d86 Merged in release/2022-02-11 (pull request #377)
Release/2022 02 11
2022-02-10 00:30:28 +00:00
Patrick Fic
f8695972a3 IO-1708 Remove timezone from date fields on server calcuations. 2022-02-09 16:29:16 -08:00
Patrick Fic
4c70351429 Remove OEC from return modal. 2022-02-09 16:00:07 -08:00
Patrick Fic
bc504d2a78 Merged in release/2022-02-11 (pull request #376)
Release/2022 02 11
2022-02-09 23:52:27 +00:00
Patrick Fic
22dfcc215e IO-1718 Add part type to OEC. 2022-02-09 15:51:19 -08:00
Patrick Fic
415d6cca29 IO-1723 Job Costing Additional Items 2022-02-09 15:06:00 -08:00
Patrick Fic
6c0bf67f37 IO-1723 Updated job costing. 2022-02-09 14:55:16 -08:00
Patrick Fic
351459681c IO-1637 Tax rates randomly not pulling. 2022-02-09 10:55:12 -08:00
Patrick Fic
e7e9ca6dfc Schema Updates. 2022-02-09 10:35:15 -08:00
Patrick Fic
39998a279e Autohouse Fixes. 2022-02-09 10:34:52 -08:00
Patrick Fic
7c66e5cb90 Merged in release/2022-02-11 (pull request #375)
Release/2022 02 11
2022-02-09 17:46:15 +00:00
Patrick Fic
b4e0dcc395 IO-1699 Add additional scoreboard total details. 2022-02-08 13:08:50 -08:00
Patrick Fic
c16a2a83a5 IO-1679 Add returns to reconciliation calculation. 2022-02-08 13:08:31 -08:00
Patrick Fic
a2dca6c1a1 IO-1720 Add bill mark as exported. 2022-02-08 11:11:56 -08:00
Patrick Fic
ffb39bbee7 IO-1687 Add payable due date. 2022-02-08 11:07:31 -08:00
Patrick Fic
46731975e6 Merge branch 'hotfix/2022-02-08' into release/2022-02-11 2022-02-08 10:50:33 -08:00
Patrick Fic
06f725ebb1 Merged in hotfix/2022-02-08 (pull request #374)
Resolve monthly employee efficiency for infinity.
2022-02-08 18:05:04 +00:00
Patrick Fic
8745ffd08f Merged in hotfix/2022-02-08 (pull request #373)
Resolve monthly employee efficiency for infinity.
2022-02-08 18:04:24 +00:00
Patrick Fic
634adb6e9a Resolve monthly employee efficiency for infinity. 2022-02-08 09:47:58 -08:00
Patrick Fic
b5386be6af Merged in hotfix/2022-02-08 (pull request #372)
Hotfix/2022 02 08
2022-02-08 17:25:38 +00:00
Patrick Fic
14e9ac2cdb IO-1714 Resolve monthly efficiency component. 2022-02-08 09:08:16 -08:00
Patrick Fic
76fb8f453d Updated projected monthly sales. 2022-02-08 09:08:09 -08:00
Patrick Fic
afc674d74c IO-1676 Add RO Comment. 2022-02-07 19:31:08 -08:00
Patrick Fic
8c67a94387 IO-1714 Resolve monthly efficiency component. 2022-02-07 18:50:18 -08:00
Patrick Fic
a17b2b0923 Updated projected monthly sales. 2022-02-07 18:31:06 -08:00
Patrick Fic
91c5560fe8 IO-1708 Add shop timezone & update server side calculations. 2022-02-07 17:43:34 -08:00
Patrick Fic
356928ce77 Resolve double partner notification. 2022-02-07 15:04:07 -08:00
Patrick Fic
eb05a746c4 Resolve dashboard/reporting discrepancy. 2022-02-07 10:38:05 -08:00
Patrick Fic
07b5c5e93c Merged in release/2022-02-04 (pull request #368)
Update QBO Export.
2022-02-07 16:55:08 +00:00
Patrick Fic
dcb9c32336 Merged in release/2022-02-04 (pull request #366)
Release/2022 02 04
2022-02-05 01:35:28 +00:00
Patrick Fic
6a59092d6a Merged in release/2022-01-28 (pull request #362)
release/2022-01-28

Approved-by: Patrick Fic
2022-01-28 01:51:05 +00:00
Patrick Fic
9d770a4cd5 Merged in release/2022-01-28 (pull request #358)
release/2022-01-28

Approved-by: Patrick Fic
2022-01-24 23:57:37 +00:00
Patrick Fic
a20e005583 Merged in release/2022-01-21 (pull request #356)
PBS Improvements.
2022-01-21 22:55:52 +00:00
Patrick Fic
b81d3369af Merged in release/2022-01-21 (pull request #355)
Release/2022 01 21
2022-01-21 22:21:45 +00:00
Patrick Fic
9ab08fbdd0 Merged in release/2022-01-14 (pull request #349)
release/2022-01-14

Approved-by: Patrick Fic
2022-01-14 22:41:23 +00:00
Patrick Fic
1a53e7c2f7 Merged in release/2022-01-14 (pull request #346)
Release/2022 01 14
2022-01-12 16:53:58 +00:00
Patrick Fic
36a7b8346e Merged in release/2022-01-14 (pull request #344)
Release/2022 01 14
2022-01-11 20:44:01 +00:00
93 changed files with 7378 additions and 385 deletions

View File

@@ -1,4 +1,4 @@
<babeledit_project be_version="2.7.1" version="1.2">
<babeledit_project version="1.2" be_version="2.7.1">
<!--
BabelEdit project file
@@ -2863,6 +2863,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>markexported</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>markforreexport</name>
<definition_loaded>false</definition_loaded>
@@ -3120,6 +3141,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>markexported</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>reexport</name>
<definition_loaded>false</definition_loaded>
@@ -3830,6 +3872,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>disablecontactvehiclecreation</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>dms_acctnumber</name>
<definition_loaded>false</definition_loaded>
@@ -7794,6 +7857,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>timezone</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>tt_allow_post_to_invoiced</name>
<definition_loaded>false</definition_loaded>
@@ -20329,6 +20413,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>comment</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>customerowing</name>
<definition_loaded>false</definition_loaded>
@@ -25036,6 +25141,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>cost_Additional</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>cost_labor</name>
<definition_loaded>false</definition_loaded>
@@ -25078,6 +25204,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>cost_sublet</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>costs</name>
<definition_loaded>false</definition_loaded>
@@ -26930,6 +27077,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>sale_additional</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>sale_labor</name>
<definition_loaded>false</definition_loaded>
@@ -26972,6 +27140,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>sale_sublet</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>sales</name>
<definition_loaded>false</definition_loaded>
@@ -32001,6 +32190,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>part_type</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>quantity</name>
<definition_loaded>false</definition_loaded>
@@ -35523,6 +35733,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>exported_payroll</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>
</folder_node>
<folder_node>
@@ -36104,6 +36335,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>comment</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>compact</name>
<definition_loaded>false</definition_loaded>
@@ -38903,6 +39155,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>dailyactual</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>dailytarget</name>
<definition_loaded>false</definition_loaded>
@@ -38966,6 +39239,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>todateactual</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>weeklyactual</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>weeklytarget</name>
<definition_loaded>false</definition_loaded>

View File

@@ -33,6 +33,7 @@
"logrocket": "^2.1.2",
"markerjs2": "^2.17.2",
"moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.34",
"phone": "^3.1.10",
"preval.macro": "^5.0.0",
"prop-types": "^15.7.2",

View File

@@ -28,6 +28,7 @@ import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -107,7 +108,7 @@ export function BillDetailEditcontainer({
);
billlines.forEach((billline) => {
const { deductedfromlbr, ...il } = billline;
const { deductedfromlbr, jobline, ...il } = billline;
delete il.__typename;
if (il.id) {
@@ -206,6 +207,8 @@ export function BillDetailEditcontainer({
cost: i.actual_cost,
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type,
};
}),
isReturn: true,
@@ -234,6 +237,7 @@ export function BillDetailEditcontainer({
</Button>
</Popconfirm>
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
<BillMarkExportedButton bill={data && data.bills_by_pk} />
</Space>
}
/>

View File

@@ -93,6 +93,7 @@ function BillEnterModalContainer({
deductedfromlbr,
lbr_adjustment,
location: lineLocation,
part_type,
...restI
} = i;
@@ -216,7 +217,11 @@ function BillEnterModalContainer({
if (enterAgain) {
form.resetFields();
form.setFieldsValue({ ...form.getFieldsValue(), billlines: [] });
form.resetFields();
form.setFieldsValue({
...formValues,
billlines: [],
});
} else {
toggleModalVisible();
}

View File

@@ -0,0 +1,82 @@
import { useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import { gql } from "@apollo/client";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectAuthLevel,
selectBodyshop,
} from "../../redux/user/user.selectors";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillMarkExportedButton);
export function BillMarkExportedButton({ bodyshop, authLevel, bill }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [updateBill] = useMutation(gql`
mutation UPDATE_BILL($billId: uuid!) {
update_bills(where: { id: { _eq: $billId } }, _set: { exported: true }) {
returning {
id
exported
exported_at
}
}
}
`);
const handleUpdate = async () => {
setLoading(true);
const result = await updateBill({
variables: { billId: bill.id },
});
if (!result.errors) {
notification["success"]({
message: t("bills.successes.markexported"),
});
} else {
notification["error"]({
message: t("bills.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
setLoading(false);
//Get the owner details, populate it all back into the job.
};
const hasAccess = HasRbacAccess({
bodyshop,
authLevel,
action: "bills:reexport",
});
if (hasAccess)
return (
<Button
loading={loading}
disabled={bill.exported}
onClick={handleUpdate}
>
{t("bills.labels.markexported")}
</Button>
);
return <></>;
}

View File

@@ -76,6 +76,7 @@ export function BillsListTableComponent({
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type,
};
}),
isReturn: true,

View File

@@ -9,7 +9,7 @@ import { DateFormatter } from "../../utils/DateFormatter";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
//import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
@@ -32,7 +32,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
}
/>
<FormFieldsChanged form={form} />
{/* <FormFieldsChanged form={form} /> */}
<LayoutFormRow header={t("courtesycars.labels.vehicle")}>
<Form.Item
label={t("courtesycars.fields.make")}

View File

@@ -97,7 +97,9 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
render: (text, record) =>
record.cccontracts.length === 1 ? (
<Link to={`/manage/jobs/${record.cccontracts[0].job.id}`}>
{record.cccontracts[0].job.ro_number}
{`${record.cccontracts[0].job.ro_number} - ${
record.cccontracts[0].job.ownr_fn || ""
} ${record.cccontracts[0].job.ownr_ln || ""} ${record.cccontracts[0].job.ownr_co_nm || ""}`}
</Link>
) : null,
},

View File

@@ -40,7 +40,7 @@ export default function DashboardMonthlyEmployeeEfficiency({
(dayAcc, dayVal) => {
return {
actual: dayAcc.actual + dayVal.actualhrs,
productive: dayAcc.actual + dayVal.productivehrs,
productive: dayAcc.productive + dayVal.productivehrs,
};
},
{ actual: 0, productive: 0 }
@@ -50,11 +50,13 @@ export default function DashboardMonthlyEmployeeEfficiency({
}
const dailyEfficiency =
((dailyHrs.productive - dailyHrs.actual) / dailyHrs.productive + 1) * 100;
((dailyHrs.productive - dailyHrs.actual) / dailyHrs.actual + 1) * 100;
const theValue = {
date: moment(val).format("DD"),
...dailyHrs,
// ...dailyHrs,
actual: dailyHrs.actual.toFixed(1),
productive: dailyHrs.productive.toFixed(1),
dailyEfficiency: isNaN(dailyEfficiency) ? 0 : dailyEfficiency.toFixed(1),
accActual:
acc.length > 0
@@ -67,12 +69,18 @@ export default function DashboardMonthlyEmployeeEfficiency({
: dailyHrs.productive,
accEfficiency: 0,
};
theValue.accEfficiency = (
theValue.accEfficiency =
((theValue.accProductive - theValue.accActual) /
(theValue.accProductive || 1) +
(theValue.accActual || 0) +
1) *
100
).toFixed(1);
100;
if (isNaN(theValue.accEfficiency)) {
theValue.accEfficiency = 0;
} else {
theValue.accEfficiency = theValue.accEfficiency.toFixed(1);
}
return [...acc, theValue];
}, []);
@@ -131,6 +139,7 @@ export default function DashboardMonthlyEmployeeEfficiency({
//stackId="day"
barSize={20}
fill="#102568"
format={"0.0"}
/>
<Bar
name="Productive Hours"
@@ -140,6 +149,7 @@ export default function DashboardMonthlyEmployeeEfficiency({
//stackId="day"
barSize={20}
fill="#017664"
format={"0.0"}
/>
</ComposedChart>
</ResponsiveContainer>

View File

@@ -31,16 +31,51 @@ export default function DashboardProjectedMonthlySales({ data, ...cardProps }) {
}
export const DashboardProjectedMonthlySalesGql = `
projected_monthly_sales: jobs(where: {_or: [{_and: [{date_invoiced: {_gte: "${moment()
projected_monthly_sales: jobs(where: {
voided: {_eq: false},
_or: [
{_and: [
{date_invoiced:{_is_null: false }},
{date_invoiced: {_gte: "${moment()
.startOf("month")
.startOf("day")
.toISOString()}"}}, {date_invoiced: {_lte: "${moment()
.endOf("month")
.endOf("day")
.toISOString()}"}}]},
{
_and:[
{date_invoiced:{_is_null: true }},
{actual_completion: {_gte: "${moment()
.startOf("month")
.startOf("day")
.toISOString()}"}}, {actual_completion: {_lte: "${moment()
.endOf("month")
.endOf("day")
.toISOString()}"}}
]
},
{_and: [
{date_invoiced: {_is_null: true}},
{actual_completion: {_is_null: true}}
{scheduled_completion: {_gte: "${moment()
.startOf("month")
.format("YYYY-MM-DD")}"}}, {date_invoiced: {_lte: "${moment()
.startOf("day")
.toISOString()}"}}, {scheduled_completion: {_lte: "${moment()
.endOf("month")
.format("YYYY-MM-DD")}"}}]}, {_and: [{scheduled_completion: {_gte: "${moment()
.startOf("month")
.format("YYYY-MM-DD")}"}}, {scheduled_completion: {_lte: "${moment()
.endOf("month")
.format("YYYY-MM-DD")}"}}]}]}) {
.endOf("day")
.toISOString()}"}}
]}
]}) {
id
ro_number
voided
date_invoiced
job_totals
}

View File

@@ -280,12 +280,13 @@ const createDashboardQuery = (state) => {
return gql`
query QUERY_DASHBOARD_DETAILS {
${componentBasedAdditions || ""}
monthly_sales: jobs(where: {_and: [{date_invoiced: {_gte: "${moment()
.startOf("month")
.format("YYYY-MM-DD")}"}}, {date_invoiced: {_lte: "${moment()
.endOf("month")
.format("YYYY-MM-DD")}"}}]}) {
monthly_sales: jobs(where: {_and: [
{ voided: {_eq: false}},
{date_invoiced: {_gte: "${moment()
.startOf("month").startOf('day').toISOString()}"}}, {date_invoiced: {_lte: "${moment()
.endOf("month").endOf('day').toISOString()}"}}]}) {
id
ro_number
date_invoiced
job_totals
rate_la1
@@ -333,14 +334,14 @@ const createDashboardQuery = (state) => {
part_qty
part_type
}
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" } }) {
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
aggregate {
sum {
mod_lb_hrs
}
}
}
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" } }) {
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
aggregate {
sum {
mod_lb_hrs

View File

@@ -6,6 +6,7 @@ import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries";
import { axiosAuthInterceptorId } from "../../utils/CleanAxios";
import client from "../../utils/GraphQLClient";
import exifr from "exifr";
import { store } from "../../redux/store";
//Context: currentUserEmail, bodyshop, jobid, invoiceid
@@ -112,7 +113,19 @@ export const uploadToCloudinary = async (
);
if (cloudinaryUploadResponse.status !== 200) {
if (!!onError) onError(cloudinaryUploadResponse.statusText);
if (!!onError) {
onError(cloudinaryUploadResponse.statusText);
}
try {
axios.post("/newlog", {
message: "client-cloudinary-upload-error",
type: "error",
user: store.getState().user.email,
object: cloudinaryUploadResponse,
});
} catch (error) {}
notification["error"]({
message: i18n.t("documents.errors.insert", {
message: cloudinaryUploadResponse.statusText,

View File

@@ -3,27 +3,41 @@ import moment from "moment";
import React, { useRef } from "react";
//To be used as a form element only.
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(FormDatePicker);
const dateFormat = "MM/DD/YYYY";
export default function FormDatePicker({
export function FormDatePicker({
bodyshop,
value,
onChange,
onBlur,
onlyFuture,
isDateOnly = true,
...restProps
}) {
const ref = useRef();
const handleChange = (newDate) => {
if (value !== newDate && onChange) {
onChange(newDate);
onChange(isDateOnly ? newDate && newDate.format("YYYY-MM-DD") : newDate);
}
};
const handleKeyDown = (e) => {
if (e.key.toLowerCase() === "t") {
if (onChange) {
onChange(new moment());
onChange(isDateOnly ? moment().format("YYYY-MM-DD") : moment());
// if (ref.current && ref.current.blur) ref.current.blur();
}
} else if (e.key.toLowerCase() === "enter") {
@@ -51,7 +65,8 @@ export default function FormDatePicker({
});
}
if (_a.isValid() && onChange) onChange(_a);
if (_a.isValid() && onChange)
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
};
return (
@@ -62,6 +77,7 @@ export default function FormDatePicker({
onChange={handleChange}
format={dateFormat}
onBlur={onBlur || handleBlur}
showToday={false}
disabledTime
{...(onlyFuture && {
disabledDate: (d) => moment().subtract(1, "day").isAfter(d),

View File

@@ -26,19 +26,20 @@ const DateTimePicker = (
value={value}
onBlur={onBlur}
onChange={onChange}
isDateOnly={false}
/>
<TimePicker
{...restProps}
value={value ? moment(value) : null}
{...(onlyFuture && {
disabledDate: (d) => moment().isAfter(d),
})}
onChange={onChange}
showSecond={false}
minuteStep={15}
onBlur={onBlur}
format="hh:mm a"
value={value ? moment(value) : null}
{...(onlyFuture && {
disabledDate: (d) => moment().isAfter(d),
})}
onChange={onChange}
showSecond={false}
minuteStep={15}
onBlur={onBlur}
format="hh:mm a"
{...restProps}
/>
</div>
);

View File

@@ -72,7 +72,7 @@ export default function JobBillsTotalComponent({
const discrepWithLbrAdj = discrepancy.add(lbrAdjustments);
const discrepWithCms = discrepWithLbrAdj.add(billCms);
const discrepWithCms = discrepWithLbrAdj.add(totalReturns);
const creditsNotReceived = totalReturns.subtract(billCms); //billCms is tracked as a negative number.
return (
@@ -171,8 +171,8 @@ export default function JobBillsTotalComponent({
}
>
<Statistic
title={t("bills.labels.billcmtotal")}
value={billCms.toFormat()}
title={t("bills.labels.totalreturns")}
value={totalReturns.toFormat()}
/>
</Tooltip>
<Typography.Title>=</Typography.Title>

View File

@@ -26,11 +26,6 @@ export function JobCostingModalContainer({
const { visible, context } = jobCostingModal;
const { jobId } = context;
// const { loading, error, data } = useQuery(QUERY_JOB_COSTING_DETAILS, {
// variables: { id: jobId },
// skip: !jobId,
// });
useEffect(() => {
async function getData() {
if (jobId && visible) {
@@ -46,8 +41,14 @@ export function JobCostingModalContainer({
<Modal
visible={visible}
title={t("jobs.labels.jobcosting")}
onOk={() => toggleModalVisible()}
onCancel={() => toggleModalVisible()}
onOk={() => {
toggleModalVisible();
setCostingData(null);
}}
onCancel={() => {
toggleModalVisible();
setCostingData(null);
}}
cancelButtonProps={{ style: { display: "none" } }}
width="90%"
destroyOnClose

View File

@@ -16,6 +16,14 @@ export default function JobCostingStatistics({ summaryData }) {
value={Dinero(summaryData.totalPartsSales).toFormat()}
title={t("jobs.labels.sale_parts")}
/>
<Statistic
value={Dinero(summaryData.totalSubletSales).toFormat()}
title={t("jobs.labels.sale_sublet")}
/>
<Statistic
value={Dinero(summaryData.totalAdditionalSales).toFormat()}
title={t("jobs.labels.sale_additional")}
/>
<Statistic
value={Dinero(summaryData.totalSales).toFormat()}
title={t("jobs.labels.total_sales")}
@@ -28,6 +36,14 @@ export default function JobCostingStatistics({ summaryData }) {
value={Dinero(summaryData.totalPartsCost).toFormat()}
title={t("jobs.labels.cost_parts")}
/>
<Statistic
value={Dinero(summaryData.totalSubletCost).toFormat()}
title={t("jobs.labels.cost_sublet")}
/>
<Statistic
value={Dinero(summaryData.totalAdditionalCost).toFormat()}
title={t("jobs.labels.cost_Additional")}
/>
<Statistic
value={Dinero(summaryData.totalCost).toFormat()}
title={t("jobs.labels.total_cost")}

View File

@@ -55,15 +55,17 @@ export function JobEmployeeAssignments({
0
}
>
{bodyshop.employees.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
{bodyshop.employees
.filter((emp) => emp.active)
.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
</Select>
</Col>
<Col span={24}>

View File

@@ -10,7 +10,7 @@ export default function JobLinesBillRefernece({ jobline }) {
{subletRequired && <WarningFilled />}
{`${(billLine.actual_price * billLine.quantity).toFixed(2)} (${
billLine.bill.vendor.name
})`}
} #${billLine.bill.invoice_number})`}
</div>
);
}

View File

@@ -33,10 +33,10 @@ export default function JobTotalsTableOther({ job }) {
key: t("jobs.fields.storage_payable"),
total: job.job_totals.additional.storage,
},
{
key: t("jobs.fields.ca_bc_pvrt"),
total: job.job_totals.additional.pvrt,
},
// {
// key: t("jobs.fields.ca_bc_pvrt"),
// total: job.job_totals.additional.pvrt,
// },
];
}, [job.job_totals, t]);

View File

@@ -3,7 +3,22 @@ import Dinero from "dinero.js";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
export default function JobTotalsTableTotals({ job }) {
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobTotalsTableTotals);
export function JobTotalsTableTotals({ bodyshop, job }) {
const { t } = useTranslation();
const data = useMemo(() => {
@@ -21,6 +36,14 @@ export default function JobTotalsTableTotals({ job }) {
key: t("jobs.labels.state_tax_amt"),
total: job.job_totals.totals.state_tax,
},
...(bodyshop.region_config === "CA_BC"
? [
{
key: t("jobs.fields.ca_bc_pvrt"),
total: job.job_totals.additional.pvrt,
},
]
: []),
{
key: t("jobs.labels.federal_tax_amt"),
total: job.job_totals.totals.federal_tax,
@@ -58,7 +81,7 @@ export default function JobTotalsTableTotals({ job }) {
bold: true,
},
];
}, [job.job_totals, t]);
}, [job.job_totals, t, bodyshop.region_config]);
const columns = [
{

View File

@@ -21,6 +21,7 @@ import ProductionListColumnProductionNote from "../production-list-columns/produ
import "./jobs-detail-header.styles.scss";
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -86,6 +87,12 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
) : null}
</Space>
</DataLabel>
<DataLabel
label={t("jobs.fields.comment")}
valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}
>
<ProductionListColumnComment record={job} />
</DataLabel>
<DataLabel label={t("jobs.fields.ins_co_nm_short")}>
{job.ins_co_nm}
</DataLabel>

View File

@@ -30,7 +30,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
width: "8%",
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder,
@@ -47,7 +47,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
key: "ownr_ln",
ellipsis: true,
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
width: "25%",
//sortOrder: sortcolumn === "ownr_ln" && sortorder,
render: (text, record) => {
return record.ownerid ? (
@@ -67,7 +67,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
width: "12%",
ellipsis: true,
render: (text, record) => (
<StartChatButton phone={record.ownr_ph1} jobid={record.id} />
@@ -77,7 +77,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
width: "12%",
ellipsis: true,
render: (text, record) => (
<StartChatButton phone={record.ownr_ph2} jobid={record.id} />
@@ -87,7 +87,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
width: "10%",
ellipsis: true,
sorter: true, // (a, b) => alphaSort(a.status, b.status),
sortOrder: sortcolumn === "status" && sortorder,
@@ -104,7 +104,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
width: "15%",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
@@ -124,7 +124,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
width: "8%",
ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder: sortcolumn === "plate_no" && sortorder,
@@ -136,7 +136,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
width: "12%",
ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder: sortcolumn === "clm_no" && sortorder,
@@ -155,7 +155,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
width: "10%",
sorter: true, //(a, b) => a.clm_total - b.clm_total,
sortOrder: sortcolumn === "clm_total" && sortorder,
render: (text, record) => {
@@ -170,11 +170,17 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.owner_owing"),
dataIndex: "owner_owing",
key: "owner_owing",
width: "8%",
render: (text, record) => (
<CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
},
];
const handleTableChange = (pagination, filters, sorter) => {
@@ -224,7 +230,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
>
<Table
loading={loading}
scroll={{ x: true }}
pagination={{
position: "top",
pageSize: 25,

View File

@@ -60,6 +60,9 @@ export function JobsList({ bodyshop }) {
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.comments || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
@@ -262,6 +265,13 @@ export function JobsList({ bodyshop }) {
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
responsive: ["md"],
},
// {
// title: t("jobs.fields.owner_owing"),
// dataIndex: "owner_owing",

View File

@@ -124,7 +124,7 @@ export function JobNotesComponent({
messageObject={{
subject: Templates.individual_job_note.subject,
}}
id={record.id}
id={jobId}
/>
</Space>
),

View File

@@ -27,11 +27,11 @@ export function PartnerPingComponent({ bodyshop, setPartnerVersion }) {
useEffect(() => {
// Create an scoped async function in the hook
async function checkPartnerStatus() {
if (!bodyshop) return;
try {
//if (process.env.NODE_ENV === "development") return;
const PartnerResponse = await axios.post("http://localhost:1337/ping/");
const { appver, qbpath } = PartnerResponse.data;
if (!bodyshop) return;
setPartnerVersion(appver);
console.log({ appver, qbpath });
if (

View File

@@ -319,6 +319,15 @@ export function PartsOrderListTableComponent({
},
]
: []),
{
title: t("parts_orders.fields.part_type"),
dataIndex: "part_type",
key: "part_type",
render: (text, record) =>
record.part_type
? t(`joblines.fields.part_types.${record.part_type}`)
: null,
},
{
title: t("parts_orders.fields.oem_partno"),
dataIndex: "oem_partno",

View File

@@ -1,6 +1,15 @@
import { DeleteFilled, WarningFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Divider, Form, Input, InputNumber, Radio, Space, Tag } from "antd";
import {
Divider,
Form,
Input,
InputNumber,
Radio,
Space,
Tag,
Select,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -37,6 +46,11 @@ export function PartsOrderModalComponent({
{},
bodyshop.imexshopid
);
const { OEConnection_PriceChange } = useTreatments(
["OEConnection_PriceChange"],
{},
bodyshop.imexshopid
);
const { t } = useTranslation();
return (
@@ -59,6 +73,7 @@ export function PartsOrderModalComponent({
options={vendorList}
disabled={isReturn}
preferredMake={preferredMake}
showPhone
/>
</Form.Item>
<Form.Item
@@ -114,6 +129,52 @@ export function PartsOrderModalComponent({
>
<Input />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.part_type")}
key={`${index}part_type`}
name={[field.name, "part_type"]}
>
<Select
disabled={
!(
sendType === "oec" &&
OEConnection_PriceChange.treatment === "on"
)
}
>
<Select.Option value="PAA">
{t("joblines.fields.part_types.PAA")}
</Select.Option>
<Select.Option value="PAC">
{t("joblines.fields.part_types.PAC")}
</Select.Option>
<Select.Option value="PAL">
{t("joblines.fields.part_types.PAL")}
</Select.Option>
<Select.Option value="PAG">
{t("joblines.fields.part_types.PAG")}
</Select.Option>
<Select.Option value="PAM">
{t("joblines.fields.part_types.PAM")}
</Select.Option>
<Select.Option value="PAP">
{t("joblines.fields.part_types.PAP")}
</Select.Option>
<Select.Option value="PAN">
{t("joblines.fields.part_types.PAN")}
</Select.Option>
<Select.Option value="PAO">
{t("joblines.fields.part_types.PAO")}
</Select.Option>
<Select.Option value="PAR">
{t("joblines.fields.part_types.PAR")}
</Select.Option>
<Select.Option value="PAS">
{t("joblines.fields.part_types.PAS")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
label={t("parts_orders.fields.oem_partno")}
key={`${index}oem_partno`}
@@ -192,7 +253,7 @@ export function PartsOrderModalComponent({
<Radio value={"none"}>{t("general.labels.none")}</Radio>
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
<Radio value={"p"}>{t("parts_orders.labels.print")}</Radio>
{OEConnection.treatment === "on" && (
{OEConnection.treatment === "on" && !isReturn && (
<Radio value={"oec"}>{t("parts_orders.labels.oec")}</Radio>
)}
</Radio.Group>

View File

@@ -77,7 +77,7 @@ export function PartsOrderModalContainer({
const { refetch } = actions;
const [form] = Form.useForm();
const [saving, setSaving] = useState(false);
const sendTypeState = useState("e");
const sendType = sendTypeState[0];
@@ -93,7 +93,7 @@ export function PartsOrderModalContainer({
const handleFinish = async (values) => {
logImEXEvent("parts_order_insert");
setSaving(true);
const insertResult = await insertPartOrder({
variables: {
po: [
@@ -196,6 +196,11 @@ export function PartsOrderModalContainer({
(item) => item.id === values.vendorid
)[0];
let vendorEmails =
matchingVendor &&
matchingVendor.email &&
matchingVendor.email.split(RegExp("[;,]"));
GenerateDocument(
{
name: isReturn
@@ -206,7 +211,7 @@ export function PartsOrderModalContainer({
},
},
{
to: matchingVendor ? [matchingVendor.email] : null,
to: matchingVendor ? vendorEmails : null,
replyTo: bodyshop.email,
subject: isReturn
? Templates.parts_return_slip.subject
@@ -247,8 +252,6 @@ export function PartsOrderModalContainer({
});
}
console.log(partsOrder.data.parts_orders_by_pk);
const oecResponse = await axios.post(
"http://localhost:1337/oec/",
@@ -275,11 +278,11 @@ export function PartsOrderModalContainer({
error: JSON.stringify(error.message),
}),
});
setSaving(false);
return;
}
}
setSaving(false);
toggleModalVisible();
};
@@ -301,6 +304,7 @@ export function PartsOrderModalContainer({
cost: value.cost,
quantity: value.part_qty,
job_line_id: isReturn ? value.joblineid : value.id,
part_type: value.part_type,
});
return acc;
}, [])
@@ -324,6 +328,8 @@ export function PartsOrderModalContainer({
}
onCancel={() => toggleModalVisible()}
onOk={() => form.submit()}
okButtonProps={{ loading: saving }}
cancelButtonProps={{ loading: saving }}
destroyOnClose
width="75%"
forceRender

View File

@@ -22,7 +22,7 @@ export default function ProductionBoardCard(
) {
const { t } = useTranslation();
let employee_body, employee_prep, employee_refinish; //employee_csr;
let employee_body, employee_prep, employee_refinish, employee_csr;
if (card.employee_body) {
employee_body = bodyshop.employees.find((e) => e.id === card.employee_body);
}
@@ -34,6 +34,9 @@ export default function ProductionBoardCard(
(e) => e.id === card.employee_refinish
);
}
if (card.employee_csr) {
employee_csr = bodyshop.employees.find((e) => e.id === card.employee_csr);
}
// if (card.employee_csr) {
// employee_csr = bodyshop.employees.find((e) => e.id === card.employee_csr);
// }
@@ -131,11 +134,11 @@ export default function ProductionBoardCard(
)} ${employee_refinish.last_name.charAt(0)}`
: ""
} ${card.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</Col>
{/* <Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`C: ${
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`C: ${
employee_csr
? `${employee_csr.first_name} ${employee_csr.last_name}`
: ""
}`}</Col> */}
}`}</Col>
</Row>
</Col>
)}

View File

@@ -66,6 +66,7 @@ export const createBoardData = (AllStatuses, Jobs, filter) => {
include ||
j.employee_body === employeeId ||
j.employee_prep === employeeId ||
j.employee_csr === employeeId ||
j.employee_refinish === employeeId;
}
@@ -76,9 +77,8 @@ export const createBoardData = (AllStatuses, Jobs, filter) => {
Object.keys(DataGroupedByStatus).map((statusGroupKey) => {
try {
boardLanes.columns.find(
(l) => l.id === statusGroupKey
).cards = sortByParentId(DataGroupedByStatus[statusGroupKey]);
boardLanes.columns.find((l) => l.id === statusGroupKey).cards =
sortByParentId(DataGroupedByStatus[statusGroupKey]);
} catch (error) {
console.log("Error while creating board card", error);
}

View File

@@ -0,0 +1,79 @@
import Icon from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, Input, Popover } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { FaRegStickyNote } from "react-icons/fa";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
export default function ProductionListColumnComment({ record }) {
const { t } = useTranslation();
const [note, setNote] = useState(record.comment || "");
const [visible, setVisible] = useState(false);
const [updateAlert] = useMutation(UPDATE_JOB);
const handleSaveNote = (e) => {
e.stopPropagation();
setVisible(false);
updateAlert({
variables: {
jobId: record.id,
job: {
comment: note,
},
},
}).then(() => {
if (record.refetch) record.refetch();
});
};
const handleChange = (e) => {
e.stopPropagation();
setNote(e.target.value);
};
const handleVisibleChange = (flag) => {
setVisible(flag);
if (flag) setNote(record.comment || "");
};
return (
<Popover
onVisibleChange={handleVisibleChange}
visible={visible}
content={
<div style={{ width: "30em" }}>
<Input.TextArea
rows={5}
value={note}
onChange={handleChange}
// onPressEnter={handleSaveNote}
autoFocus
allowClear
/>
<div>
<Button onClick={handleSaveNote}>
{t("general.actions.save")}
</Button>
</div>
</div>
}
trigger={["click"]}
>
<div
style={{
width: "100%",
height: "19px",
cursor: "pointer",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
<Icon component={FaRegStickyNote} style={{ marginRight: ".2rem" }} />
{record.comment || " "}
</div>
</Popover>
);
}

View File

@@ -20,6 +20,7 @@ import ProductionListColumnNote from "./production-list-columns.productionnote.c
import ProductionListColumnStatus from "./production-list-columns.status.component";
import ProductionListColumnCategory from "./production-list-columns.status.category";
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
import ProductionListColumnComment from "./production-list-columns.comment.component";
const r = ({ technician, state, activeStatuses, bodyshop }) => {
return [
@@ -95,7 +96,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
sortOrder:
state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="actual_in" />
<ProductionListDate record={record} field="actual_in" time/>
),
},
{
@@ -109,7 +110,12 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.columnKey === "scheduled_completion" &&
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="scheduled_completion" pastIndicator />
<ProductionListDate
record={record}
field="scheduled_completion"
pastIndicator
time
/>
),
},
{
@@ -156,7 +162,12 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.columnKey === "scheduled_delivery" &&
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="scheduled_delivery" pastIndicator/>
<ProductionListDate
record={record}
field="scheduled_delivery"
pastIndicator
time
/>
),
},
{
@@ -325,6 +336,13 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
ellipsis: true,
render: (text, record) => <ProductionListColumnNote record={record} />,
},
{
title: i18n.t("production.labels.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
render: (text, record) => <ProductionListColumnComment record={record} />,
},
{
title: i18n.t("production.labels.touchtime"),
dataIndex: "tt",

View File

@@ -20,9 +20,9 @@ export default function ProductionListDate({
const handleChange = (date) => {
logImEXEvent("product_toggle_date", { field });
if (date.isSame(record[field] && moment(record[field]))) {
return;
}
// if (date.isSame(record[field] && moment(record[field]))) {
// return;
// }
//e.stopPropagation();
updateAlert({
@@ -67,12 +67,14 @@ export default function ProductionListDate({
value={(record[field] && moment(record[field])) || null}
onChange={handleChange}
format="MM/DD/YYYY"
isDateOnly={!time}
/>
{time && (
<TimePicker
onClick={(e) => e.stopPropagation()}
value={(record[field] && moment(record[field])) || null}
onChange={handleChange}
minuteStep={15}
format="hh:mm a"
/>
)}

View File

@@ -116,15 +116,17 @@ export function ProductionListEmpAssignment({
0
}
>
{bodyshop.employees.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
{bodyshop.employees
.filter((emp) => emp.active)
.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
</Select>
</Col>
<Col span={24}>

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import { Descriptions, Drawer, Space } from "antd";
import { Descriptions, Drawer, Space, PageHeader, Button } from "antd";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -16,8 +16,25 @@ import JobEmployeeAssignments from "../job-employee-assignments/job-employee-ass
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
import JobAtChange from "../job-at-change/job-at-change.component";
import { PrinterFilled } from "@ant-design/icons";
export default function ProductionListDetail({ jobs }) {
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "printCenter" })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListDetail);
export function ProductionListDetail({ jobs, setPrintCenterContext }) {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const { selected } = search;
@@ -39,11 +56,29 @@ export default function ProductionListDetail({ jobs }) {
return (
<Drawer
title={
<Space>
<span>{t("production.labels.jobdetail")}</span>
<span>{theJob.ro_number}</span>
<ProductionRemoveButton jobId={theJob.id} />
</Space>
<PageHeader
title={theJob.ro_number}
extra={
<Space>
<ProductionRemoveButton jobId={theJob.id} />{" "}
<Button
onClick={() => {
setPrintCenterContext({
actions: { refetch: refetch },
context: {
id: theJob.id,
job: theJob,
type: "job",
},
});
}}
>
<PrinterFilled />
{t("jobs.actions.printCenter")}
</Button>
</Space>
}
/>
}
placement="right"
width={"33%"}

View File

@@ -65,6 +65,7 @@ export function ScheduleCalendarContainer({ calculateScheduleLoad }) {
color: "red",
start: moment(e.start).startOf("day").toDate(),
end: moment(e.end).startOf("day").toDate(),
allDay: true,
vacation: true,
};
}),

View File

@@ -1,6 +1,8 @@
import { CalendarOutlined } from "@ant-design/icons";
import { Card, Col, Row, Statistic } from "antd";
import React from "react";
import _ from "lodash";
import moment from "moment";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -16,25 +18,78 @@ const mapDispatchToProps = (dispatch) => ({
});
const rowGutter = [16, 16];
const statSpans = { xs: 24, sm: 6 };
const statSpans = { xs: 24, sm: 3 };
export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
const { t } = useTranslation();
const values = useMemo(() => {
const dateHash = _.groupBy(scoreBoardlist, "date");
console.log(
"🚀 ~ file: scoreboard-targets-table.component.jsx ~ line 31 ~ values ~ dateHash",
dateHash
);
let ret = {
todayBody: 0,
todayPaint: 0,
weeklyPaint: 0,
weeklyBody: 0,
toDateBody: 0,
toDatePaint: 0,
};
const today = moment();
if (dateHash[today.format("YYYY-MM-DD")]) {
dateHash[today.format("YYYY-MM-DD")].forEach((d) => {
ret.todayBody = ret.todayBody + d.bodyhrs;
ret.todayPaint = ret.todayPaint + d.painthrs;
});
}
let StartOfWeek = moment().startOf("week");
while (StartOfWeek.isSameOrBefore(today)) {
if (dateHash[StartOfWeek.format("YYYY-MM-DD")]) {
dateHash[StartOfWeek.format("YYYY-MM-DD")].forEach((d) => {
ret.weeklyBody = ret.weeklyBody + d.bodyhrs;
ret.weeklyPaint = ret.weeklyPaint + d.painthrs;
});
}
StartOfWeek = StartOfWeek.add(1, "day");
}
let startOfMonth = moment().startOf("month");
while (startOfMonth.isSameOrBefore(today)) {
if (dateHash[startOfMonth.format("YYYY-MM-DD")]) {
dateHash[startOfMonth.format("YYYY-MM-DD")].forEach((d) => {
ret.toDateBody = ret.toDateBody + d.bodyhrs;
ret.toDatePaint = ret.toDatePaint + d.painthrs;
});
}
startOfMonth = startOfMonth.add(1, "day");
}
return ret;
}, [scoreBoardlist]);
console.log(
"🚀 ~ file: scoreboard-targets-table.component.jsx ~ line 51 ~ values ~ values",
values
);
return (
<Card
title={t("scoreboard.labels.targets")}
extra={<ScoreboardJobsList scoreBoardlist={scoreBoardlist} />}
>
<Row gutter={rowGutter}>
<Col xs={24} sm={{ offset: 0, span: 4 }} lg={{ offset: 5, span: 4 }}>
<Col xs={24} sm={{ offset: 0, span: 4 }} lg={{ span: 4 }}>
<Statistic
title={t("scoreboard.labels.workingdays")}
value={Util.CalculateWorkingDaysThisMonth()}
prefix={<CalendarOutlined />}
/>
</Col>
<Col xs={24} sm={{ offset: 0, span: 20 }} lg={{ offset: 0, span: 13 }}>
<Col xs={24} sm={{ offset: 0, span: 20 }} lg={{ offset: 0, span: 20 }}>
<Row>
<Col {...statSpans}>
<Statistic
@@ -43,6 +98,12 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
prefix="B"
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.dailyactual")}
value={values.todayBody.toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.weeklytarget")}
@@ -52,6 +113,12 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.weeklyactual")}
value={values.weeklyBody.toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.monthlytarget")}
@@ -70,6 +137,12 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.todateactual")}
value={values.toDateBody.toFixed(1)}
/>
</Col>
</Row>
<Row>
<Col {...statSpans}>
@@ -78,6 +151,9 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
prefix="P"
/>
</Col>
<Col {...statSpans}>
<Statistic value={values.todayPaint.toFixed(1)} />
</Col>
<Col {...statSpans}>
<Statistic
value={Util.WeeklyTargetHrs(
@@ -86,6 +162,9 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
)}
/>
</Col>
<Col {...statSpans}>
<Statistic value={values.weeklyPaint.toFixed(1)} />
</Col>
<Col {...statSpans}>
<Statistic
value={Util.MonthlyTargetHrs(
@@ -102,6 +181,9 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
)}
/>
</Col>
<Col {...statSpans}>
<Statistic value={values.toDatePaint.toFixed(1)} />
</Col>
</Row>
</Col>
</Row>

View File

@@ -19,6 +19,9 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import momentTZ from "moment-timezone";
const timeZonesList = momentTZ.tz.names();
export default function ShopInfoGeneral({ form }) {
const { t } = useTranslation();
return (
@@ -84,6 +87,7 @@ export default function ShopInfoGeneral({ form }) {
<Form.Item label={t("bodyshop.fields.email")} name="email">
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.phone")}
name="phone"
@@ -97,6 +101,23 @@ export default function ShopInfoGeneral({ form }) {
<Form.Item label={t("bodyshop.fields.website")} name="website">
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.timezone")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name="timezone"
>
<Select
showSearch
options={timeZonesList.map((z) => {
return { label: z, value: z };
})}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.insurance_vendor_id")}
name="insurance_vendor_id"

View File

@@ -129,6 +129,15 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
>
<Input />
</Form.Item>
{bodyshop.pbs_serialnumber && (
<Form.Item
label={t("bodyshop.fields.dms.disablecontactvehiclecreation")}
valuePropName="checked"
name={["pbs_configuration", "disablecontactvehicle"]}
>
<Switch />
</Form.Item>
)}
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.dms.cdk.payers")}>
<Form.List name={["cdk_configuration", "payers"]}>

View File

@@ -76,6 +76,21 @@ export function TimeTicketList({
state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
render: (text, record) =>
`${record.employee.first_name} ${record.employee.last_name}`,
filters:
timetickets
.map((l) => l.employeeid)
.filter(onlyUnique)
.map((s) => {
return {
text: (() => {
const emp = bodyshop.employees.find((e) => e.id === s);
return `${emp.first_name} ${emp.last_name}`;
})(), //
value: [s],
};
}) || [],
onFilter: (value, record) => value.includes(record.employeeid),
},
{
title: t("timetickets.fields.cost_center"),

View File

@@ -159,8 +159,10 @@ export function TimeTicketModalComponent({
name="flat_rate"
label={t("timetickets.fields.flat_rate")}
valuePropName="checked"
noStyle
style={{ display: "none" }}
>
<Switch />
<Switch style={{ display: "none" }} />
</Form.Item>
</LayoutFormRow>
@@ -212,6 +214,7 @@ export function TimeTicketModalComponent({
<>
<Form.Item label={t("timetickets.fields.clockon")} name="clockon">
<FormDateTimePicker
minuteStep={5}
disabled={
!HasRbacAccess({
bodyshop,
@@ -245,6 +248,7 @@ export function TimeTicketModalComponent({
]}
>
<FormDateTimePicker
minuteStep={5}
disabled={
!HasRbacAccess({
bodyshop,

View File

@@ -0,0 +1,41 @@
import { Button } from "antd";
import queryString from "query-string";
import React, { useState } from "react";
import { useLocation } from "react-router-dom";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import { useTranslation } from "react-i18next";
import moment from "moment";
const PayrollTemplate = TemplateList("special").exported_payroll;
export default function TimeTicketsPayrollTable() {
const searchParams = queryString.parse(useLocation().search);
const { start, end } = searchParams;
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
await GenerateDocument(
{
name: PayrollTemplate.key,
variables: {
start: start
? start
: moment().startOf("week").subtract(7, "days").format("YYYY-MM-DD"),
end: end ? end : moment().endOf("week").format("YYYY-MM-DD"),
},
},
{},
"x"
);
setLoading(false);
};
return (
<Button loading={loading} onClick={handleClick}>
{t("printcenter.payments.exported_payroll")}
</Button>
);
}

View File

@@ -1,12 +1,13 @@
import { HeartOutlined } from "@ant-design/icons";
import { Select, Tag } from "antd";
import { Select, Space, Tag } from "antd";
import React, { forwardRef, useEffect, useState } from "react";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
const { Option } = Select;
//To be used as a form element only.
const VendorSearchSelect = (
{ value, onChange, options, onSelect, disabled, preferredMake },
{ value, onChange, options, onSelect, disabled, preferredMake, showPhone },
ref
) => {
const [option, setOption] = useState(value);
@@ -35,6 +36,7 @@ const VendorSearchSelect = (
style={{
width: "100%",
}}
dropdownMatchSelectWidth={false}
onChange={setOption}
optionFilterProp="name"
onSelect={onSelect}
@@ -50,10 +52,15 @@ const VendorSearchSelect = (
>
<div className="imex-flex-row">
<div style={{ flex: 1 }}>{o.name}</div>
<HeartOutlined />
{o.discount && o.discount !== 0 ? (
<Tag color="green">{`${o.discount * 100}%`}</Tag>
) : null}
<Space style={{ marginLeft: "1rem" }}>
<HeartOutlined style={{ color: "red" }} />
{o.phone && showPhone && (
<PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>
)}
{o.discount && o.discount !== 0 ? (
<Tag color="green">{`${o.discount * 100}%`}</Tag>
) : null}
</Space>
</div>
</Option>
))
@@ -64,9 +71,14 @@ const VendorSearchSelect = (
<div className="imex-flex-row" style={{ width: "100%" }}>
<div style={{ flex: 1 }}>{o.name}</div>
{o.discount && o.discount !== 0 ? (
<Tag color="green">{`${o.discount * 100}%`}</Tag>
) : null}
<Space style={{ marginLeft: "1rem" }}>
{o.phone && showPhone && (
<PhoneNumberFormatter>{o.phone}</PhoneNumberFormatter>
)}
{o.discount && o.discount !== 0 ? (
<Tag color="green">{`${o.discount * 100}%`}</Tag>
) : null}
</Space>
</div>
</Option>
))

View File

@@ -109,12 +109,14 @@ export default function VendorsFormComponent({
<Form.Item
label={t("vendors.fields.email")}
rules={[
{
type: "email",
message: t("general.validation.invalidemail"),
},
]}
rules={
[
// {
// type: "email",
// message: t("general.validation.invalidemail"),
// },
]
}
name="email"
>
<FormItemEmail email={getFieldValue("email")} />
@@ -158,7 +160,7 @@ export default function VendorsFormComponent({
<InputNumber min={0} max={1} precision={2} step={0.01} />
</Form.Item>
<Form.Item label={t("vendors.fields.due_date")} name="due_date">
<InputNumber />
<InputNumber min={0} />
</Form.Item>
{
// <Form.Item

View File

@@ -84,6 +84,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
line_remarks
quantity
job_line_id
part_type
cost
jobline {
id
@@ -123,6 +124,10 @@ export const QUERY_BILLS_BY_JOBID = gql`
applicable_taxes
deductedfromlbr
lbr_adjustment
jobline{
oem_partno
part_type
}
}
}
}
@@ -159,6 +164,10 @@ export const QUERY_BILL_BY_PK = gql`
cost_center
quantity
joblineid
jobline{
oem_partno
part_type
}
applicable_taxes
deductedfromlbr
lbr_adjustment

View File

@@ -100,6 +100,7 @@ export const QUERY_BODYSHOP = gql`
pbs_serialnumber
md_filehandlers
md_email_cc
timezone
employees {
user_email
id
@@ -197,6 +198,7 @@ export const UPDATE_SHOP = gql`
pbs_serialnumber
md_filehandlers
md_email_cc
timezone
employees {
id
first_name

View File

@@ -89,6 +89,9 @@ export const QUERY_ALL_CC = gql`
job {
id
ro_number
ownr_fn
ownr_ln
ownr_co_nm
}
}
}

View File

@@ -59,6 +59,8 @@ export const GET_LINE_TICKET_BY_PK = gql`
employeeid
memo
flat_rate
clockon
clockoff
employee {
id
first_name

View File

@@ -12,11 +12,7 @@ export const QUERY_ALL_ACTIVE_JOBS = gql`
ownr_ph1
ownr_ph2
ownr_ea
owner {
id
allow_text_message
preferred_contact
}
comment
plate_no
plate_st
v_vin
@@ -121,6 +117,7 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
id
status
ro_number
comment
ownr_fn
ownr_ln
category
@@ -193,6 +190,7 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
id
status
ro_number
comment
ownr_fn
category
ownr_ln
@@ -264,6 +262,7 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
jobs(where: { inproduction: { _eq: true } }) {
id
updated_at
comment
status
category
ro_number
@@ -489,6 +488,7 @@ export const GET_JOB_BY_PK = gql`
alt_transport
intakechecklist
invoice_final_note
comment
loss_desc
kmin
kmout
@@ -696,6 +696,7 @@ export const GET_JOB_BY_PK = gql`
joblineid
bill {
id
invoice_number
vendor {
id
name
@@ -830,6 +831,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
ownr_co_nm
ownr_ph1
ownr_ph2
comment
ownr_ea
ca_gst_registrant
owner_owing
@@ -1034,7 +1036,7 @@ export const UPDATE_JOB = gql`
update_jobs(where: { id: { _eq: $jobId } }, _set: $job) {
returning {
id
comment
date_exported
status
alt_transport
@@ -1766,13 +1768,12 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
order_by: $order
where: { status: { _in: $statusList } }
) {
comment
ownr_fn
ownr_ln
ownr_co_nm
ownerid
ownr_ph1
ownr_ph2
ownr_ea
plate_no
plate_st
v_vin
@@ -1781,32 +1782,16 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
v_make_desc
v_color
vehicleid
actual_completion
actual_delivery
actual_in
id
ins_co_nm
ins_ct_fn
ins_ct_ln
ins_ph1
ins_ea
est_co_nm
est_ph1
est_ea
est_ct_fn
est_ct_ln
clm_no
clm_total
owner_owing
ro_number
po_number
scheduled_completion
scheduled_in
scheduled_delivery
status
updated_at
ded_amt
vehicleid
}
search_jobs_aggregate(
args: { search: $search }

View File

@@ -69,6 +69,7 @@ export const QUERY_PARTS_ORDER_OEC = gql`
db_price
line_desc
quantity
part_type
}
job {
bodyshop{

View File

@@ -87,6 +87,7 @@ export const QUERY_ALL_VENDORS_FOR_ORDER = gql`
discount
email
active
phone
}
jobs(where: { id: { _eq: $jobId } }) {
v_make_desc

View File

@@ -70,7 +70,12 @@ export function CourtesyCarCreateContainer({
return (
<RbacWrapper action="courtesycar:create">
<Form form={form} autoComplete="new-password" onFinish={handleFinish}>
<Form
form={form}
autoComplete="new-password"
onFinish={handleFinish}
layout="vertical"
>
<CourtesyCarFormComponent form={form} saveLoading={loading} />
</Form>
</RbacWrapper>

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import { Col, Row } from "antd";
import { Col, Row, Space } from "antd";
import moment from "moment";
import queryString from "query-string";
import React, { useEffect } from "react";
@@ -11,6 +11,7 @@ import AlertComponent from "../../components/alert/alert.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component";
import TimeTicketList from "../../components/time-ticket-list/time-ticket-list.component";
import TimeTicketsPayrollTable from "../../components/time-tickets-payroll-table/time-tickets-payroll-table.component";
import TimeTicketsSummaryEmployees from "../../components/time-tickets-summary-employees/time-tickets-summary-employees.component";
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
import {
@@ -68,7 +69,12 @@ export function TimeTicketsContainer({
<TimeTicketList
loading={loading}
timetickets={data ? data.timetickets : []}
extra={<TimeTicketsDatesSelector />}
extra={
<Space wrap>
<TimeTicketsPayrollTable />
<TimeTicketsDatesSelector />
</Space>
}
/>
</Col>
<Col span={24}>

View File

@@ -41,6 +41,7 @@ import UserActionTypes from "./user.types";
import axios from "axios";
import { messaging } from "../../firebase/firebase.utils";
import { getToken } from "firebase/messaging";
export function* onEmailSignInStart() {
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
}
@@ -266,6 +267,12 @@ export function* onSetShopDetails() {
export function* SetAuthLevelFromShopDetails({ payload }) {
try {
const userEmail = yield select((state) => state.user.currentUser.email);
try {
console.log("Setting shop timezone.");
// moment.tz.setDefault(payload.timezone);
} catch (error) {
console.log(error);
}
factory.client(payload.imexshopid);

View File

@@ -21,7 +21,7 @@
"actions": {
"block": "Block Day",
"calculate": "Calculate SMART Dates",
"cancel": "Cancel",
"cancel": "Cancel Appointment",
"intake": "Intake",
"new": "New Appointment",
"preview": "Preview",
@@ -183,6 +183,7 @@
"federal_tax": "Federal Tax",
"iouexists": "An IOU exists that is associated to this RO.",
"local_tax": "Local Tax",
"markexported": "Mark Exported",
"markforreexport": "Mark for Re-export",
"new": "New Bill",
"noneselected": "No bill selected.",
@@ -197,6 +198,7 @@
"created": "Invoice added successfully.",
"deleted": "Bill deleted successfully.",
"exported": "Bill(s) exported successfully.",
"markexported": "Bill marked as exported.",
"reexport": "Bill marked for re-export."
},
"validation": {
@@ -244,6 +246,7 @@
"dms": {
"cashierid": "Cashier ID",
"default_journal": "Default Journal",
"disablecontactvehiclecreation": "Disable Contact & Vehicle Updates/Creation",
"dms_acctnumber": "DMS Account #",
"dms_wip_acctnumber": "DMS W.I.P. Account #",
"generic_customer_number": "Generic Customer Number",
@@ -483,6 +486,7 @@
"production_statuses": "Production Statuses"
},
"target_touchtime": "Target Touch Time",
"timezone": "Timezone",
"tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs",
"use_fippa": "Use FIPPA for Names on Generated Documents?",
"website": "Website",
@@ -1242,6 +1246,7 @@
"class": "Class",
"clm_no": "Claim #",
"clm_total": "Claim Total",
"comment": "Comment",
"customerowing": "Customer Owing",
"date_estimated": "Date Estimated",
"date_exported": "Exported",
@@ -1482,8 +1487,10 @@
"closejob": "Close Job {{ro_number}}",
"contracts": "CC Contracts",
"cost": "Cost",
"cost_Additional": "Cost - Additional",
"cost_labor": "Cost - Labor",
"cost_parts": "Cost - Parts",
"cost_sublet": "Cost - Sublet",
"costs": "Costs",
"create": {
"jobinfo": "Job Info",
@@ -1553,7 +1560,7 @@
"partstotal": "Parts Total (ex. Taxes)",
"plitooltips": {
"billtotal": "The total amount of all bill lines that have been posted against this RO (not including credits, taxes, or labor adjustments).",
"creditmemos": "The total amount of all credit memos entered. This amount does not reflect any parts returns created.",
"creditmemos": "The total amount of all returns created. This amount does not reflect credit memos that have been posted.",
"creditsnotreceived": "The total amount of returns created for this job that do not have a corresponding credit memo posted. An amount greater than $0 indicates that vendors have not provided requested credit memos.",
"discrep1": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Too many bills/bill lines that have been posted against this RO. Check to make sure every bill posted on this RO is correctly posted and assigned.</li>\n<li>You do not have the latest supplement imported, or, a supplement must be submitted and then imported.</li>\n<li>You have posted a bill line to labor.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this job from reconciling.</i>",
"discrep2": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Used an incorrect rate when deducting from labor.</li>\n<li>An outstanding imbalance higher in the reconciliation process.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this job from reconciling.</i>",
@@ -1581,8 +1588,10 @@
"relatedros": "Related ROs",
"returntotals": "Return Totals",
"rosaletotal": "RO Parts Total",
"sale_additional": "Sales - Additional",
"sale_labor": "Sales - Labor",
"sale_parts": "Sales - Parts & Sublet",
"sale_parts": "Sales - Parts",
"sale_sublet": "Sales - Sublet",
"sales": "Sales",
"savebeforeconversion": "You have unsaved changes on the job. Please save them before converting it. ",
"scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the job. ",
@@ -1900,6 +1909,7 @@
"order_date": "Order Date",
"order_number": "Order Number",
"orderedby": "Ordered By",
"part_type": "Type",
"quantity": "Qty.",
"return": "Return",
"status": "Status"
@@ -2109,7 +2119,8 @@
"title": "Print Center"
},
"payments": {
"ca_bc_etf_table": "ICBC ETF Table"
"ca_bc_etf_table": "ICBC ETF Table",
"exported_payroll": "Payroll Table"
},
"subjects": {
"jobs": {
@@ -2150,6 +2161,7 @@
"bodypriority": "B/P",
"cardsettings": "Card Settings",
"clm_no": "Claim Number",
"comment": "Comment",
"compact": "Compact Cards",
"detailpriority": "D/P",
"employeeassignments": "Employee Assignments",
@@ -2230,7 +2242,7 @@
"gsr_by_csr": "Gross Sales by CSR",
"gsr_by_delivery_date": "Gross Sales by Delivery Date",
"gsr_by_estimator": "Gross Sales by Estimator",
"gsr_by_exported_date": "Gross Sales by Export Date",
"gsr_by_exported_date": "Exported Gross Sales",
"gsr_by_ins_co": "Gross Sales by Insurance Company",
"gsr_by_make": "Gross Sales by Vehicle Make",
"gsr_by_referral": "Gross Sales by Referral Source",
@@ -2315,9 +2327,12 @@
},
"labels": {
"asoftodaytarget": "As of Today",
"dailyactual": "Actual (D)",
"dailytarget": "Daily",
"monthlytarget": "Monthly",
"targets": "Targets",
"todateactual": "Actual (MTD)",
"weeklyactual": "Actual (W)",
"weeklytarget": "Weekly",
"workingdays": "Working Days / Month"
},
@@ -2588,7 +2603,7 @@
"country": "Country",
"discount": "Discount % (as decimal)",
"display_name": "Display Name",
"due_date": "Payment Due Date",
"due_date": "Payment Due Date (# of days)",
"email": "Contact Email",
"favorite": "Favorite?",
"lkq": "LKQ",

View File

@@ -183,6 +183,7 @@
"federal_tax": "",
"iouexists": "",
"local_tax": "",
"markexported": "",
"markforreexport": "",
"new": "",
"noneselected": "",
@@ -197,6 +198,7 @@
"created": "",
"deleted": "",
"exported": "",
"markexported": "",
"reexport": ""
},
"validation": {
@@ -244,6 +246,7 @@
"dms": {
"cashierid": "",
"default_journal": "",
"disablecontactvehiclecreation": "",
"dms_acctnumber": "",
"dms_wip_acctnumber": "",
"generic_customer_number": "",
@@ -483,6 +486,7 @@
"production_statuses": ""
},
"target_touchtime": "",
"timezone": "",
"tt_allow_post_to_invoiced": "",
"use_fippa": "",
"website": "",
@@ -1242,6 +1246,7 @@
"class": "",
"clm_no": "Reclamación #",
"clm_total": "Reclamar total",
"comment": "",
"customerowing": "Cliente debido",
"date_estimated": "Fecha estimada",
"date_exported": "Exportado",
@@ -1482,8 +1487,10 @@
"closejob": "",
"contracts": "",
"cost": "",
"cost_Additional": "",
"cost_labor": "",
"cost_parts": "",
"cost_sublet": "",
"costs": "",
"create": {
"jobinfo": "",
@@ -1581,8 +1588,10 @@
"relatedros": "",
"returntotals": "",
"rosaletotal": "",
"sale_additional": "",
"sale_labor": "",
"sale_parts": "",
"sale_sublet": "",
"sales": "",
"savebeforeconversion": "",
"scheduledinchange": "",
@@ -1900,6 +1909,7 @@
"order_date": "",
"order_number": "",
"orderedby": "",
"part_type": "",
"quantity": "",
"return": "",
"status": ""
@@ -2109,7 +2119,8 @@
"title": ""
},
"payments": {
"ca_bc_etf_table": ""
"ca_bc_etf_table": "",
"exported_payroll": ""
},
"subjects": {
"jobs": {
@@ -2150,6 +2161,7 @@
"bodypriority": "",
"cardsettings": "",
"clm_no": "",
"comment": "",
"compact": "",
"detailpriority": "",
"employeeassignments": "",
@@ -2315,9 +2327,12 @@
},
"labels": {
"asoftodaytarget": "",
"dailyactual": "",
"dailytarget": "",
"monthlytarget": "",
"targets": "",
"todateactual": "",
"weeklyactual": "",
"weeklytarget": "",
"workingdays": ""
},

View File

@@ -183,6 +183,7 @@
"federal_tax": "",
"iouexists": "",
"local_tax": "",
"markexported": "",
"markforreexport": "",
"new": "",
"noneselected": "",
@@ -197,6 +198,7 @@
"created": "",
"deleted": "",
"exported": "",
"markexported": "",
"reexport": ""
},
"validation": {
@@ -244,6 +246,7 @@
"dms": {
"cashierid": "",
"default_journal": "",
"disablecontactvehiclecreation": "",
"dms_acctnumber": "",
"dms_wip_acctnumber": "",
"generic_customer_number": "",
@@ -483,6 +486,7 @@
"production_statuses": ""
},
"target_touchtime": "",
"timezone": "",
"tt_allow_post_to_invoiced": "",
"use_fippa": "",
"website": "",
@@ -1242,6 +1246,7 @@
"class": "",
"clm_no": "Prétendre #",
"clm_total": "Total réclamation",
"comment": "",
"customerowing": "Client propriétaire",
"date_estimated": "Date estimée",
"date_exported": "Exportés",
@@ -1482,8 +1487,10 @@
"closejob": "",
"contracts": "",
"cost": "",
"cost_Additional": "",
"cost_labor": "",
"cost_parts": "",
"cost_sublet": "",
"costs": "",
"create": {
"jobinfo": "",
@@ -1581,8 +1588,10 @@
"relatedros": "",
"returntotals": "",
"rosaletotal": "",
"sale_additional": "",
"sale_labor": "",
"sale_parts": "",
"sale_sublet": "",
"sales": "",
"savebeforeconversion": "",
"scheduledinchange": "",
@@ -1900,6 +1909,7 @@
"order_date": "",
"order_number": "",
"orderedby": "",
"part_type": "",
"quantity": "",
"return": "",
"status": ""
@@ -2109,7 +2119,8 @@
"title": ""
},
"payments": {
"ca_bc_etf_table": ""
"ca_bc_etf_table": "",
"exported_payroll": ""
},
"subjects": {
"jobs": {
@@ -2150,6 +2161,7 @@
"bodypriority": "",
"cardsettings": "",
"clm_no": "",
"comment": "",
"compact": "",
"detailpriority": "",
"employeeassignments": "",
@@ -2315,9 +2327,12 @@
},
"labels": {
"asoftodaytarget": "",
"dailyactual": "",
"dailytarget": "",
"monthlytarget": "",
"targets": "",
"todateactual": "",
"weeklyactual": "",
"weeklytarget": "",
"workingdays": ""
},

View File

@@ -17,7 +17,8 @@ const Templates = TemplateList();
export default async function RenderTemplate(
templateObject,
bodyshop,
renderAsHtml = false
renderAsHtml = false,
renderAsExcel = false
) {
//Query assets that match the template name. Must be in format <<templateName>>.query
let { contextData, useShopSpecificTemplate } = await fetchContextData(
@@ -30,6 +31,7 @@ export default async function RenderTemplate(
? `/${bodyshop.imexshopid}/${templateObject.name}`
: `/${templateObject.name}`,
...(renderAsHtml ? {} : { recipe: "chrome-pdf" }),
...(renderAsExcel ? { recipe: "html-to-xlsx" } : {}),
},
data: {
...contextData,
@@ -37,7 +39,7 @@ export default async function RenderTemplate(
...templateObject.context,
headerpath: `/${bodyshop.imexshopid}/header.html`,
bodyshop: bodyshop,
offset: moment().utcOffset(),
offset: bodyshop.timezone, //moment().utcOffset(),
},
};
@@ -182,6 +184,9 @@ export const GenerateDocument = async (
template,
})
);
} else if (sendType === "x") {
console.log("excel");
await RenderTemplate(template, bodyshop, false, true);
} else {
await RenderTemplate(template, bodyshop);
}

View File

@@ -1635,6 +1635,13 @@ export const TemplateList = (type, context) => {
key: "ca_bc_etf_table",
disabled: false,
},
exported_payroll: {
title: i18n.t("printcenter.payments.exported_payroll"),
description: "Est Detail",
subject: i18n.t("printcenter.payments.exported_payroll"),
key: "exported_payroll",
disabled: false,
},
production_by_technician_one: {
title: i18n.t(
"reportcenter.templates.production_by_technician_one"

View File

@@ -9460,7 +9460,14 @@ moment-business-days@^1.2.0:
resolved "https://registry.yarnpkg.com/moment-business-days/-/moment-business-days-1.2.0.tgz#6172f9f38dbf443c2f859baabeabbd2935f63d65"
integrity sha512-QJlceLfMSxy/jZSOgJYCKeKw+qGYHj8W0jMa/fYruyoJ85+bJuLRiYv5DIaflyuRipmYRfD4kDlSwVYteLN+Jw==
moment@^2.24.0, moment@^2.25.3:
moment-timezone@^0.5.34:
version "0.5.34"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c"
integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==
dependencies:
moment ">= 2.9.0"
"moment@>= 2.9.0", moment@^2.24.0, moment@^2.25.3:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==

View File

@@ -815,6 +815,7 @@
- email
- enforce_class
- enforce_referral
- entegral_configuration
- entegral_id
- features
- federal_tax_id
@@ -867,6 +868,7 @@
- target_touchtime
- template_header
- textid
- timezone
- tt_allow_post_to_invoiced
- updated_at
- use_fippa
@@ -926,6 +928,7 @@
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- pbs_configuration
- phone
- prodtargethrs
- production_config
@@ -939,6 +942,7 @@
- state
- state_tax_id
- target_touchtime
- timezone
- tt_allow_post_to_invoiced
- updated_at
- use_fippa
@@ -2707,6 +2711,7 @@
- clm_title
- clm_total
- clm_zip
- comment
- converted
- created_at
- cust_pr
@@ -2963,6 +2968,7 @@
- clm_title
- clm_total
- clm_zip
- comment
- converted
- created_at
- cust_pr
@@ -3229,6 +3235,7 @@
- clm_title
- clm_total
- clm_zip
- comment
- converted
- created_at
- cust_pr

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
alter table "public"."bodyshops" alter column "entegral_configuration" set default jsonb_build_array();

View File

@@ -0,0 +1 @@
alter table "public"."bodyshops" alter column "entegral_configuration" set default jsonb_build_object();

View File

@@ -0,0 +1 @@
ALTER TABLE "public"."bodyshops" ALTER COLUMN "timezone" drop default;

View File

@@ -0,0 +1 @@
alter table "public"."bodyshops" alter column "timezone" set default 'America/Vancouver';

5325
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -35,8 +35,10 @@
"graylog2": "^0.2.1",
"inline-css": "^3.0.0",
"intuit-oauth": "^4.0.0",
"json-2-csv": "^3.17.0",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"moment-timezone": "^0.5.34",
"node-mailjet": "^3.3.4",
"node-quickbooks": "^2.0.39",
"nodemailer": "^6.7.1",

View File

@@ -149,6 +149,7 @@ app.post(
fb.unsubscribe
);
app.post("/adm/updateuser", fb.validateFirebaseIdToken, fb.updateUser);
app.post("/adm/createuser", fb.validateFirebaseIdToken, fb.createUser);
//Stripe Processing
var stripe = require("./server/stripe/payment");
@@ -177,8 +178,15 @@ var data = require("./server/data/data");
app.post("/data/ah", data.autohouse);
app.post("/data/arms", data.arms);
var taskHandler = require("./server/tasks/tasks");
app.post("/taskHandler", taskHandler.taskHandler);
var ioevent = require("./server/ioevent/ioevent");
app.post("/ioevent", ioevent.default);
app.post("/newlog", (req, res) => {
const { message, type, user, record, object } = req.body;
logger.log(message, type, user, record, object);
});
var cdkGetMake = require("./server/cdk/cdk-get-makes");
app.post("/cdk/getvehicles", fb.validateFirebaseIdToken, cdkGetMake.default);

View File

@@ -14,9 +14,10 @@ const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants");
const CalculateAllocations =
require("../../cdk/cdk-calculate-allocations").default;
const CdkBase = require("../../web-sockets/web-socket");
const moment = require("moment");
const moment = require("moment-timezone");
const Dinero = require("dinero.js");
const axios = AxiosLib.create();
axios.interceptors.request.use((x) => {
const socket = x.socket;
@@ -102,40 +103,54 @@ exports.PbsSelectedCustomer = async function PbsSelectedCustomer(
selectedCustomerId
) {
try {
CdkBase.createLogEvent(
socket,
"DEBUG",
`User selected customer ${selectedCustomerId || "NEW"}`
);
if (
socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle === false
) {
CdkBase.createLogEvent(
socket,
"DEBUG",
`User selected customer ${selectedCustomerId || "NEW"}`
);
//Upsert the contact information as per Wafaa's Email.
CdkBase.createLogEvent(
socket,
"DEBUG",
`Upserting contact information to DMS for ${
socket.JobData.ownr_fn || ""
} ${socket.JobData.ownr_ln || ""} ${socket.JobData.ownr_co_nm || ""}`
);
const ownerRef = await UpsertContactData(socket, selectedCustomerId);
//Upsert the contact information as per Wafaa's Email.
CdkBase.createLogEvent(
socket,
"DEBUG",
`Upserting contact information to DMS for ${
socket.JobData.ownr_fn || ""
} ${socket.JobData.ownr_ln || ""} ${socket.JobData.ownr_co_nm || ""}`
);
const ownerRef = await UpsertContactData(socket, selectedCustomerId);
CdkBase.createLogEvent(
socket,
"DEBUG",
`Upserting vehicle information to DMS for ${socket.JobData.v_vin}`
);
await UpsertVehicleData(socket, ownerRef.ReferenceId);
CdkBase.createLogEvent(
socket,
"DEBUG",
`Upserting vehicle information to DMS for ${socket.JobData.v_vin}`
);
await UpsertVehicleData(socket, ownerRef.ReferenceId);
} else {
CdkBase.createLogEvent(
socket,
"DEBUG",
`Contact and Vehicle updates disabled. Skipping to accounting data insert.`
);
}
CdkBase.createLogEvent(socket, "DEBUG", `Inserting account data.`);
CdkBase.createLogEvent(
socket,
"DEBUG",
`Inserting accounting posting data..`
);
await InsertAccountPostingData(socket);
const insertResponse = await InsertAccountPostingData(socket);
CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`);
await MarkJobExported(socket, socket.JobData.id);
if (insertResponse.WasSuccessful) {
CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`);
await MarkJobExported(socket, socket.JobData.id);
socket.emit("export-success", socket.JobData.id);
socket.emit("export-success", socket.JobData.id);
} else {
CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`);
}
} catch (error) {
CdkBase.createLogEvent(
socket,
@@ -553,7 +568,9 @@ async function InsertAccountPostingData(socket) {
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).toISOString(),
InvoiceDate: moment(socket.JobData.date_invoiced)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
};
wips.push(item);
}
@@ -567,7 +584,9 @@ async function InsertAccountPostingData(socket) {
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).toISOString(),
InvoiceDate: moment(socket.JobData.date_invoiced)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
};
wips.push(item);
@@ -578,7 +597,9 @@ async function InsertAccountPostingData(socket) {
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).toISOString(),
InvoiceDate: moment(socket.JobData.date_invoiced)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
};
wips.push(itemWip);
//Add to the WIP account.
@@ -593,7 +614,9 @@ async function InsertAccountPostingData(socket) {
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).toISOString(),
InvoiceDate: moment(socket.JobData.date_invoiced)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
};
wips.push(item2);
}
@@ -609,7 +632,9 @@ async function InsertAccountPostingData(socket) {
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).toISOString(),
InvoiceDate: moment(socket.JobData.date_invoiced)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
};
wips.push(item);
@@ -622,7 +647,9 @@ async function InsertAccountPostingData(socket) {
Posting: {
Reference: socket.JobData.ro_number,
JournalCode: socket.txEnvelope.journal,
TransactionDate: moment(socket.JobData.date_invoiced).toISOString(), //"0001-01-01T00:00:00.0000000Z",
TransactionDate: moment(socket.JobData.date_invoiced)
.tz(socket.JobData.bodyshop.timezone)
.toISOString(), //"0001-01-01T00:00:00.0000000Z",
Description: socket.txEnvelope.story,
//AdditionalInfo: "String",
Source: "ImEX Online",

View File

@@ -17,7 +17,7 @@ const {
setNewRefreshToken,
} = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const moment = require("moment");
const moment = require("moment-timezone");
const GraphQLClient = require("graphql-request").GraphQLClient;
const findTaxCode = require("../qb-receivables-lines").findTaxCode;
@@ -178,8 +178,16 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
VendorRef: {
value: vendor.Id,
},
TxnDate: moment(bill.date).format("YYYY-MM-DD"),
//DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
TxnDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.format("YYYY-MM-DD"),
...(!bill.is_credit_memo &&
bill.vendor.due_date && {
DueDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.add(bill.vendor.due_date, "days")
.format("YYYY-MM-DD"),
}),
DocNumber: bill.invoice_number,
//...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),

View File

@@ -15,7 +15,7 @@ const {
setNewRefreshToken,
} = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const moment = require("moment");
const moment = require("moment-timezone");
const GraphQLClient = require("graphql-request").GraphQLClient;
const {
QueryInsuranceCo,
@@ -137,14 +137,22 @@ exports.default = async (req, res) => {
}
if (payment.amount > 0) {
await InsertPayment(oauthClient, qbo_realmId, req, payment, jobTier);
await InsertPayment(
oauthClient,
qbo_realmId,
req,
payment,
jobTier,
bodyshop
);
} else {
await InsertCreditMemo(
oauthClient,
qbo_realmId,
req,
payment,
jobTier
jobTier,
bodyshop
);
}
ret.push({ paymentid: payment.id, success: true });
@@ -178,7 +186,8 @@ async function InsertPayment(
qbo_realmId,
req,
payment,
parentRef
parentRef,
bodyshop
) {
const { paymentMethods, invoices } = await QueryMetaData(
oauthClient,
@@ -198,7 +207,8 @@ async function InsertPayment(
CustomerRef: {
value: parentRef.Id,
},
TxnDate: moment(payment.date).format("YYYY-MM-DD"),
TxnDate: moment(payment.date) //.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
//DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
DocNumber: payment.paymentnum,
TotalAmt: Dinero({
@@ -362,7 +372,8 @@ async function InsertCreditMemo(
qbo_realmId,
req,
payment,
parentRef
parentRef,
bodyshop
) {
const { paymentMethods, invoices, items, taxCodes } = await QueryMetaData(
oauthClient,
@@ -382,7 +393,9 @@ async function InsertCreditMemo(
CustomerRef: {
value: parentRef.Id,
},
TxnDate: moment(payment.date).format("YYYY-MM-DD"),
TxnDate: moment(payment.date)
//.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
DocNumber: payment.paymentnum,
...(invoices && invoices[0]
? { InvoiceRef: { value: invoices[0].Id } }

View File

@@ -17,7 +17,7 @@ const {
} = require("./qbo-callback");
const OAuthClient = require("intuit-oauth");
const CreateInvoiceLines = require("../qb-receivables-lines").default;
const moment = require("moment");
const moment = require("moment-timezone");
const GraphQLClient = require("graphql-request").GraphQLClient;
const { generateOwnerTier } = require("../qbxml/qbxml-utils");
@@ -440,7 +440,9 @@ async function InsertInvoice(
const invoiceObj = {
Line: InvoiceLineAdd,
TxnDate: moment(job.date_invoiced).format("YYYY-MM-DD"),
TxnDate: moment(job.date_invoiced)
.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
DocNumber: job.ro_number,
...(job.class ? { ClassRef: { value: classes[job.class] } } : {}),
CustomerMemo: {

View File

@@ -5,7 +5,7 @@ const queries = require("../../graphql-client/queries");
const Dinero = require("dinero.js");
var builder = require("xmlbuilder2");
const QbXmlUtils = require("./qbxml-utils");
const moment = require("moment");
const moment = require("moment-timezone");
const logger = require("../../utils/logger");
require("dotenv").config({
path: path.resolve(
@@ -72,9 +72,16 @@ const generateBill = (bill) => {
VendorRef: {
FullName: bill.vendor.name,
},
TxnDate: moment(bill.date).format("YYYY-MM-DD"),
DueDate:
bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
TxnDate: moment(bill.date)
//.tz(bill.job.bodyshop.timezone)
.format("YYYY-MM-DD"),
...(!bill.is_credit_memo &&
bill.vendor.due_date && {
DueDate: moment(bill.date)
// .tz(bill.job.bodyshop.timezone)
.add(bill.vendor.due_date, "days")
.format("YYYY-MM-DD"),
}),
RefNumber: bill.invoice_number,
Memo: `RO ${bill.job.ro_number || ""}`,
ExpenseLineAdd: bill.billlines.map((il) =>

View File

@@ -4,7 +4,7 @@ const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
const queries = require("../../graphql-client/queries");
const Dinero = require("dinero.js");
var builder = require("xmlbuilder2");
const moment = require("moment");
const moment = require("moment-timezone");
const QbXmlUtils = require("./qbxml-utils");
const QbxmlReceivables = require("./qbxml-receivables");
const logger = require("../../utils/logger");
@@ -84,7 +84,7 @@ exports.default = async (req, res) => {
QbXmlToExecute.push({
id: i.id,
okStatusCodes: ["0"],
qbxml: generatePayment(i, isThreeTier, twoTierPref),
qbxml: generatePayment(i, isThreeTier, twoTierPref, bodyshop),
});
});
@@ -101,7 +101,7 @@ exports.default = async (req, res) => {
}
};
const generatePayment = (payment, isThreeTier, twoTierPref) => {
const generatePayment = (payment, isThreeTier, twoTierPref, bodyshop) => {
let paymentQbxmlObj;
if (payment.amount > 0) {
paymentQbxmlObj = {
@@ -128,7 +128,9 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => {
FullName:
payment.job.bodyshop.md_responsibility_centers.ar.accountname,
},
TxnDate: moment(payment.date).format("YYYY-MM-DD"), //Trim String
TxnDate: moment(payment.date)
// .tz(bodyshop.timezone)
.format("YYYY-MM-DD"), //Trim String
RefNumber: payment.paymentnum || payment.transactionid,
TotalAmount: Dinero({
amount: Math.round(payment.amount * 100),
@@ -172,7 +174,9 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => {
FullName:
payment.job.bodyshop.md_responsibility_centers.ar.accountname,
},
TxnDate: moment(payment.date).format("YYYY-MM-DD"), //Trim String
TxnDate: moment(payment.date)
//.tz(bodyshop.timezone)
.format("YYYY-MM-DD"), //Trim String
RefNumber:
payment.paymentnum || payment.stripeid || payment.transactionid,

View File

@@ -3,7 +3,7 @@ const path = require("path");
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
const queries = require("../../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment");
const moment = require("moment-timezone");
var builder = require("xmlbuilder2");
const QbXmlUtils = require("./qbxml-utils");
const logger = require("../../utils/logger");
@@ -254,7 +254,9 @@ const generateInvoiceQbxml = (
? { ClassRef: { FullName: jobs_by_pk.class } }
: {}),
TxnDate: moment(jobs_by_pk.date_invoiced).format("YYYY-MM-DD"),
TxnDate: moment(jobs_by_pk.date_invoiced)
.tz(bodyshop.timezone)
.format("YYYY-MM-DD"),
RefNumber: jobs_by_pk.ro_number,
BillAddress: {
Addr1: jobs_by_pk.ownr_co_nm

View File

@@ -13,7 +13,7 @@ const CdkWsdl = require("./cdk-wsdl").default;
const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
const CalcualteAllocations = require("./cdk-calculate-allocations").default;
const moment = require("moment");
const moment = require("moment-timezone");
const replaceSpecialRegex = /[^a-zA-Z0-9 .,\n #]+/g;
@@ -716,6 +716,7 @@ async function InsertDmsVehicle(socket) {
dealerNumber: socket.JobData.bodyshop.cdk_dealerid,
...(socket.txEnvelope.inservicedate && {
inServiceDate: moment(socket.txEnvelope.inservicedate)
//.tz(socket.JobData.bodyshop.timezone)
.startOf("day")
.toISOString(),
}),
@@ -723,7 +724,9 @@ async function InsertDmsVehicle(socket) {
},
manufacturer: {},
vehicle: {
deliveryDate: moment().format("YYYYMMDD"),
deliveryDate: moment()
// .tz(socket.JobData.bodyshop.timezone)
.format("YYYYMMDD"),
licensePlateNo: socket.JobData.plate_no,
make: socket.txEnvelope.dms_make,
modelAbrev: socket.txEnvelope.dms_model,
@@ -850,14 +853,16 @@ async function UpdateDmsVehicle(socket) {
inServiceDate: moment(
socket.DMSVeh.dealer.inServiceDate ||
socket.txEnvelope.inservicedate
).toISOString(),
)
// .tz(socket.JobData.bodyshop.timezone)
.toISOString(),
}),
},
vehicle: {
...socket.DMSVeh.vehicle,
deliveryDate: moment(
socket.DMSVeh.vehicle.deliveryDate
).toISOString(),
deliveryDate: moment(socket.DMSVeh.vehicle.deliveryDate)
//.tz(socket.JobData.bodyshop.timezone)
.toISOString(),
},
owners: ids,
},
@@ -912,10 +917,18 @@ async function InsertServiceVehicleHistory(socket) {
vehID: socket.DMSVid.vehiclesVehId,
roNumber: socket.JobData.ro_number.match(/\d+/g),
mileage: socket.txEnvelope.kmout,
openDate: moment(socket.JobData.actual_in).format("YYYY-MM-DD"),
openTime: moment(socket.JobData.actual_in).format("HH:mm:ss"),
closeDate: moment(socket.JobData.invoice_date).format("YYYY-MM-DD"),
closeTime: moment(socket.JobData.invoice_date).format("HH:mm:ss"),
openDate: moment(socket.JobData.actual_in)
.tz(socket.JobData.bodyshop.timezone)
.format("YYYY-MM-DD"),
openTime: moment(socket.JobData.actual_in)
.tz(socket.JobData.bodyshop.timezone)
.format("HH:mm:ss"),
closeDate: moment(socket.JobData.invoice_date)
.tz(socket.JobData.bodyshop.timezone)
.format("YYYY-MM-DD"),
closeTime: moment(socket.JobData.invoice_date)
.tz(socket.JobData.bodyshop.timezone)
.format("HH:mm:ss"),
comments: socket.txEnvelope.story,
cashierID: socket.JobData.bodyshop.cdk_configuration.cashierid,
},
@@ -966,7 +979,9 @@ async function InsertDmsStartWip(socket) {
arg0: CDK_CREDENTIALS,
arg1: { dealerId: socket.JobData.bodyshop.cdk_dealerid },
arg2: {
acctgDate: moment().format("YYYY-MM-DD"),
acctgDate: moment()
.tz(socket.JobData.bodyshop.timezone)
.format("YYYY-MM-DD"),
//socket.JobData.invoice_date
desc:
socket.txEnvelope.story &&

View File

@@ -1,7 +1,7 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment");
const moment = require("moment-timezone");
const fs = require("fs");
const _ = require("lodash");
@@ -66,24 +66,35 @@ exports.default = async (req, res) => {
// },
RepairEvent: {
CreatedDateTime: (job.date_open
? moment(job.date_open)
? moment(job.date_open).tz(bodyshop.timezone)
: moment()
).format(momentFormat),
ArrivalDateTime:
job.actual_in && moment(job.actual_in).format(momentFormat),
job.actual_in &&
moment(job.actual_in)
.tz(bodyshop.timezone)
.format(momentFormat),
ArrivalOdometerReading: job.kmin,
TargetCompletionDateTime:
job.scheduled_completion &&
moment(job.scheduled_completion).format(momentFormat),
moment(job.scheduled_completion)
.tz(bodyshop.timezone)
.format(momentFormat),
ActualCompletionDateTime:
job.actual_completion &&
moment(job.actual_completion).format(momentFormat),
moment(job.actual_completion)
.tz(bodyshop.timezone)
.format(momentFormat),
ActualPickUpDateTime:
job.actual_delivery &&
moment(job.actual_delivery).format(momentFormat),
moment(job.actual_delivery)
.tz(bodyshop.timezone)
.format(momentFormat),
CloseDateTime:
job.date_exported &&
moment(job.date_exported).format(momentFormat),
moment(job.date_exported)
.tz(bodyshop.timezone)
.format(momentFormat),
},
},
RepairOrderHeader: {
@@ -254,7 +265,8 @@ exports.default = async (req, res) => {
//ProductionDate: "2009-10",
ModelYear:
parseInt(job.v_model_yr) < 1900
? parseInt(job.v_model_yr) < moment().format("YY")
? parseInt(job.v_model_yr) <
moment().tz(bodyshop.timezone).format("YY")
? `20${job.v_model_yr}`
: `19${job.v_model_yr}`
: job.v_model_yr,
@@ -288,7 +300,9 @@ exports.default = async (req, res) => {
Facts: {
LossDateTime:
job.loss_date &&
moment(job.loss_date).format(momentFormat),
moment(job.loss_date)
.tz(bodyshop.timezone)
.format(momentFormat),
LossDescCode: "Collision",
PrimaryPOI: {
POICode: job.area_of_damage && job.area_of_damage.impact1,
@@ -750,13 +764,17 @@ exports.default = async (req, res) => {
ProductionStatus: {
ProductionStage: {
ProductionStageCode: GetProductionStageCode(job, bodyshop),
ProductionStageDateTime: moment().format(momentFormat),
ProductionStageDateTime: moment()
.tz(bodyshop.timezone)
.format(momentFormat),
// ProductionStageStatusComment:
// "Going to be painted this afternoon",
},
RepairStatus: {
RepairStatusCode: GetRepairStatusCode(job),
RepairStatusDateTime: moment().format(momentFormat),
RepairStatusDateTime: moment()
.tz(bodyshop.timezone)
.format(momentFormat),
// RepairStatusMemo: "Waiting on back ordered parts",
},
},

View File

@@ -1,7 +1,7 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment");
const moment = require("moment-timezone");
var builder = require("xmlbuilder2");
const _ = require("lodash");
const logger = require("../utils/logger");
@@ -93,6 +93,7 @@ exports.default = async (req, res) => {
.end({ allowEmptyTags: true });
allxmlsToUpload.push({
count: autoHouseObject.AutoHouseExport.RepairOrder.length,
xml: ret,
filename: `IM_${bodyshop.autohouseid}_${moment().format(
"DDMMYYYY_HHMMSS"
@@ -213,7 +214,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
Street: job.ownr_addr1 || "",
City: job.ownr_city || "",
State: job.ownr_st || "",
Zip: job.ownr_zip || "",
Zip: (job.ownr_zip && job.ownr_zip.substring(0, 3)) || "",
Phone1: job.ownr_ph1 || "",
Phone2: null,
Phone2Extension: null,
@@ -227,7 +228,11 @@ const CreateRepairOrderTag = (job, errorCallback) => {
InsuredorClaimantFlag: null,
},
VehicleInformation: {
Year: job.v_model_yr || "",
Year: job.v_model_yr
? parseInt(job.v_model_yr.match(/\d/g))
? parseInt(job.v_model_yr.match(/\d/g).join(""), 10)
: ""
: "",
Make: job.v_make_desc || "",
Model: job.v_model_desc || "",
VIN: job.v_vin || "",
@@ -279,15 +284,30 @@ const CreateRepairOrderTag = (job, errorCallback) => {
CustomerAuthorizationDate: null,
InsuranceAuthorizationDate: null,
DateOpened:
(job.date_open && moment(job.date_open).format(AhDateFormat)) || "",
(job.date_open &&
moment(job.date_open)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
ScheduledArrivalDate:
(job.scheduled_in && moment(job.scheduled_in).format(AhDateFormat)) ||
(job.scheduled_in &&
moment(job.scheduled_in)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
CarinShop:
(job.actual_in && moment(job.actual_in).format(AhDateFormat)) || "",
(job.actual_in &&
moment(job.actual_in)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
InsInspDate: null,
StartDate:
(job.actual_in && moment(job.actual_in).format(AhDateFormat)) || "",
(job.actual_in &&
moment(job.actual_in)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
PartsOrder: null,
TeardownHold: null,
SupplementSubmittedDate: null,
@@ -303,24 +323,35 @@ const CreateRepairOrderTag = (job, errorCallback) => {
//InsuranceTargetOut: null,
CarComplete:
(job.actual_completion &&
moment(job.actual_completion).format(AhDateFormat)) ||
moment(job.actual_completion)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
DeliveryAppointmentDate:
(job.scheduled_delivery &&
moment(job.scheduled_delivery).format(AhDateFormat)) ||
moment(job.scheduled_delivery)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
DateClosed:
(job.date_invoiced &&
moment(job.date_invoiced).format(AhDateFormat)) ||
moment(job.date_invoiced)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
CustomerPaidInFullDate: null,
InsurancePaidInFullDate: null,
CustPickup:
(job.actual_delivery &&
moment(job.actual_delivery).format(AhDateFormat)) ||
moment(job.actual_delivery)
.tz(job.bodyshop.timezone)
.format(AhDateFormat)) ||
"",
AccountPostedDate:
job.date_exported && moment(job.date_exported).format(AhDateFormat),
job.date_exported &&
moment(job.date_exported)
.tz(job.bodyshop.timezone)
.format(AhDateFormat),
CSIProcessedDate: null,
ThankYouLetterSent: null,
AdditionalFollowUpDate: null,
@@ -457,8 +488,8 @@ const CreateRepairOrderTag = (job, errorCallback) => {
PartsReconditionedCost:
repairCosts.PartsReconditionedCost.toFormat(AHDineroFormat),
PartsRecycled: Dinero(
job.job_totals.parts.parts.list.PAR &&
job.job_totals.parts.parts.list.PAR.total
job.job_totals.parts.parts.list.PAL &&
job.job_totals.parts.parts.list.PAL.total
).toFormat(AHDineroFormat),
PartsRecycledCost:
repairCosts.PartsRecycledCost.toFormat(AHDineroFormat),
@@ -524,7 +555,9 @@ const CreateRepairOrderTag = (job, errorCallback) => {
AHDineroFormat
),
BMTotalCost: repairCosts.BMTotalCost.toFormat(AHDineroFormat),
MiscTotal: 0,
MiscTotal: Dinero(job.job_totals.additional.additionalCosts).toFormat(
AHDineroFormat
),
MiscTotalCost: 0,
TowingTotal: Dinero(job.job_totals.additional.towing).toFormat(
AHDineroFormat
@@ -541,10 +574,12 @@ const CreateRepairOrderTag = (job, errorCallback) => {
.add(Dinero(job.job_totals.totals.federal_tax))
.toFormat(AHDineroFormat),
SalesTaxTotalCost: 0,
GrossTotal: Dinero(job.job_totals.totals.net_repairs).toFormat(
GrossTotal: Dinero(job.job_totals.totals.total_repairs).toFormat(
AHDineroFormat
),
DeductibleTotal: job.ded_amt || 0,
DeductibleTotal: Dinero({
amount: Math.round((job.ded_amt || 0) * 100),
}).toFormat(AHDineroFormat),
DepreciationTotal: Dinero(
job.job_totals.totals.custPayable.dep_taxes
).toFormat(AHDineroFormat),
@@ -554,7 +589,9 @@ const CreateRepairOrderTag = (job, errorCallback) => {
CustomerPay: Dinero(job.job_totals.totals.custPayable.total).toFormat(
AHDineroFormat
),
InsurancePay: 0,
InsurancePay: Dinero(job.job_totals.totals.total_repairs)
.subtract(Dinero(job.job_totals.totals.custPayable))
.toFormat(AHDineroFormat),
Deposit: 0,
AmountDue: 0,
},
@@ -600,7 +637,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
DetailLine:
job.joblines.length > 0
? job.joblines.map((jl) =>
GenerateDetailLines(jl, job.bodyshop.md_order_statuses)
GenerateDetailLines(job, jl, job.bodyshop.md_order_statuses)
)
: [generateNullDetailLine()],
},
@@ -638,7 +675,7 @@ const CreateCosts = (job) => {
});
return bill_acc;
}, {});
const materialsHours = { mapaHrs: 0, mashHrs: 0 };
//If the hourly rates for job costing are set, add them in.
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa) {
if (
@@ -659,9 +696,32 @@ const CreateCosts = (job) => {
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mapa * 100) ||
0,
}).multiply(materialsHours.mapaHrs)
}).multiply(job.job_totals.rates.mapa.hours)
);
}
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
if (
!billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
]
)
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
] = Dinero();
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
].add(
Dinero({
amount:
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mash * 100) ||
0,
}).multiply(job.job_totals.rates.mash.hours)
);
}
const ticketTotalsByCostCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => {
//At the invoice level.
@@ -684,7 +744,10 @@ const CreateCosts = (job) => {
return {
PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
return acc.add(billTotalsByCostCenters[key]);
if (key !== defaultCosts.PAS && key !== defaultCosts.PASL)
return acc.add(billTotalsByCostCenters[key]);
return acc;
}, Dinero()),
PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add(
billTotalsByCostCenters[defaultCosts.PAP] || Dinero()
@@ -694,7 +757,9 @@ const CreateCosts = (job) => {
billTotalsByCostCenters[defaultCosts.PAM] || Dinero(),
PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(),
PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
SubletTotalCost: billTotalsByCostCenters[defaultCosts.PAS] || Dinero(),
SubletTotalCost:
billTotalsByCostCenters[defaultCosts.PAS] ||
Dinero(billTotalsByCostCenters[defaultCosts.PASL] || Dinero()),
BodyLaborTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(),
RefinishLaborTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(),
@@ -747,10 +812,15 @@ const StatusMapping = (status, md_ro_statuses) => {
// default: return "UNDEFINED"
};
const GenerateDetailLines = (line, statuses) => {
const GenerateDetailLines = (job, line, statuses) => {
const ret = {
BackOrdered: line.status === statuses.default_bo ? "1" : "0",
Cost: (line.billlines[0] && line.billlines[0].actual_cost.toFixed(2)) || 0,
Cost:
(line.billlines[0] &&
(line.billlines[0].actual_cost * line.billlines[0].quantity).toFixed(
2
)) ||
0,
//Critical: null,
Description: line.line_desc || "",
DiscountMarkup: line.prt_dsmk_m || 0,
@@ -777,11 +847,7 @@ const GenerateDetailLines = (line, statuses) => {
Vendor: (line.billlines[0] && line.billlines[0].bill.vendor.name) || "",
VendorPaid: null,
VendorPrice:
(line.billlines[0] &&
(line.billlines[0].actual_price * line.billlines[0].quantity).toFixed(
2
)) ||
0,
(line.billlines[0] && line.billlines[0].actual_price.toFixed(2)) || 0,
Deleted: null,
ExpectedOn: null,
ReceivedOn:

View File

@@ -45,6 +45,25 @@ exports.sendServerEmail = async function ({ subject, text }) {
logger.log("server-email-failure", "error", null, null, error);
}
};
exports.sendTaskEmail = async function ({ to, subject, text, attachments }) {
try {
transporter.sendMail(
{
from: `ImEX Online <noreply@imex.online>`,
to: to,
subject: subject,
text: text,
attachments: attachments || null,
},
(err, info) => {
console.log(err || info);
}
);
} catch (error) {
console.log(error);
logger.log("server-email-failure", "error", null, null, error);
}
};
exports.sendEmail = async (req, res) => {
logger.log("send-email", "DEBUG", req.user.email, null, {

View File

@@ -25,6 +25,43 @@ const adminEmail = [
"patrick@thinkimex.com",
];
exports.createUser = (req, res) => {
logger.log("admin-create-user", "WARN", req.user.email, null, {
request: req.body,
});
if (!adminEmail.includes(req.user.email)) {
logger.log(
"admin-create-user-unauthorized",
"ERROR",
req.user.email,
null,
{
request: req.body,
user: req.user,
}
);
res.sendStatus(404);
}
const { email, displayName, password } = req.body;
admin
.auth()
.createUser({ email, displayName, password })
.then((userRecord) => {
// See the UserRecord reference doc for the contents of userRecord.
logger.log("admin-update-user-success", "DEBUG", req.user.email, null, {
userRecord,
});
res.json(userRecord);
})
.catch((error) => {
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
error,
});
res.status(500).json(error);
});
};
exports.updateUser = (req, res) => {
logger.log("admin-update-user", "WARN", req.user.email, null, {
request: req.body,

View File

@@ -178,6 +178,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
md_responsibility_centers
accountingconfig
md_ins_cos
timezone
}
}
`;
@@ -247,6 +248,7 @@ query QUERY_JOBS_FOR_CDK_EXPORT($id: uuid!) {
accountingconfig
cdk_dealerid
cdk_configuration
timezone
}
owner {
accountingid
@@ -341,6 +343,7 @@ query QUERY_JOBS_FOR_PBS_EXPORT($id: uuid!) {
accountingconfig
pbs_serialnumber
pbs_configuration
timezone
}
owner {
id
@@ -385,6 +388,7 @@ query QUERY_BILLS_FOR_PAYABLES_EXPORT($bills: [uuid!]!) {
class
bodyshop{
md_responsibility_centers
timezone
}
}
billlines{
@@ -400,6 +404,7 @@ query QUERY_BILLS_FOR_PAYABLES_EXPORT($bills: [uuid!]!) {
vendor{
id
name
due_date
}
}
}
@@ -411,6 +416,7 @@ exports.QUERY_PAYMENTS_FOR_EXPORT = `
id
md_responsibility_centers
accountingconfig
timezone
}
payments(where: {id: {_in: $payments}}) {
id
@@ -460,6 +466,7 @@ exports.QUERY_UPCOMING_APPOINTMENTS = `query QUERY_UPCOMING_APPOINTMENTS($now: t
ssbuckets
target_touchtime
workingdays
timezone
}
jobhrs: joblines_aggregate(where: {removed: {_eq: false}}) {
aggregate {
@@ -558,7 +565,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
autohouseid
md_responsibility_centers
jc_hourly_rates
timezone
}
jobs(where: {_and: [{converted: {_eq: true}}, {updated_at: {_gt: $start}}, {shopid: {_eq: $bodyshopid}}]}) {
id
@@ -574,7 +581,6 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
ownr_st
ownr_ph1
ownr_zip
referral_source
loss_type
v_model_yr
v_model_desc
@@ -632,7 +638,7 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
job_totals
driveable
parts_tax_rates
ded_amt
joblines(where: {removed: {_eq: false}}) {
id
line_no
@@ -828,6 +834,7 @@ exports.GET_JOB_BY_PK = ` query GET_JOB_BY_PK($id: uuid!) {
loss_desc
kmin
kmout
comment
referral_source
referral_source_extra
unit_number
@@ -1234,6 +1241,7 @@ exports.GET_AUTOHOUSE_SHOPS = `query GET_AUTOHOUSE_SHOPS {
md_responsibility_centers
jc_hourly_rates
imexshopid
timezone
}
}
`;

View File

@@ -31,8 +31,8 @@ async function JobCosting(req, res) {
res.status(200).json(ret);
} catch (error) {
logger.log("job-costing-error", "ERROR", req.user.email, jobid, {
jobid,
error,
message: error.message,
stack: error.stack,
});
res.status(400).send(JSON.stringify(error));
@@ -62,19 +62,29 @@ async function JobCostingMulti(req, res) {
summaryData: {
totalLaborSales: Dinero({ amount: 0 }),
totalPartsSales: Dinero({ amount: 0 }),
totalAdditionalSales: Dinero({ amount: 0 }),
totalSubletSales: Dinero({ amount: 0 }),
totalSales: Dinero({ amount: 0 }),
totalLaborCost: Dinero({ amount: 0 }),
totalPartsCost: Dinero({ amount: 0 }),
totalAdditionalCost: Dinero({ amount: 0 }),
totalSubletCost: Dinero({ amount: 0 }),
totalCost: Dinero({ amount: 0 }),
gpdollars: Dinero({ amount: 0 }),
gppercent: null,
gppercentFormatted: null,
totalLaborGp: Dinero({ amount: 0 }),
totalPartsGp: Dinero({ amount: 0 }),
totalAdditionalGp: Dinero({ amount: 0 }),
totalSubletGp: Dinero({ amount: 0 }),
totalLaborGppercent: null,
totalLaborGppercentFormatted: null,
totalPartsGppercent: null,
totalPartsGppercentFormatted: null,
totalAdditionalGppercent: null,
totalAdditionalGppercentFormatted: null,
totalSubletGppercent: null,
totalSubletGppercentFormatted: null,
},
};
@@ -102,12 +112,24 @@ async function JobCostingMulti(req, res) {
sale_parts_dinero: multiSummary.costCenterData[
CostCenterIndex
].sale_parts_dinero.add(c.sale_parts_dinero),
sale_additional_dinero: multiSummary.costCenterData[
CostCenterIndex
].sale_additional_dinero.add(c.sale_additional_dinero),
sale_sublet_dinero: multiSummary.costCenterData[
CostCenterIndex
].sale_sublet_dinero.add(c.sale_sublet_dinero),
cost_labor_dinero: multiSummary.costCenterData[
CostCenterIndex
].cost_labor_dinero.add(c.cost_labor_dinero),
cost_parts_dinero: multiSummary.costCenterData[
CostCenterIndex
].cost_parts_dinero.add(c.cost_parts_dinero),
cost_additional_dinero: multiSummary.costCenterData[
CostCenterIndex
].cost_additional_dinero.add(c.cost_additional_dinero),
cost_sublet_dinero: multiSummary.costCenterData[
CostCenterIndex
].cost_sublet_dinero.add(c.cost_sublet_dinero),
gpdollars_dinero: multiSummary.costCenterData[
CostCenterIndex
].gpdollars_dinero.add(c.gpdollars_dinero),
@@ -129,6 +151,14 @@ async function JobCostingMulti(req, res) {
multiSummary.summaryData.totalPartsSales.add(
costingData.summaryData.totalPartsSales
);
multiSummary.summaryData.totalAdditionalSales =
multiSummary.summaryData.totalAdditionalSales.add(
costingData.summaryData.totalAdditionalSales
);
multiSummary.summaryData.totalSubletSales =
multiSummary.summaryData.totalSubletSales.add(
costingData.summaryData.totalSubletSales
);
multiSummary.summaryData.totalSales =
multiSummary.summaryData.totalSales.add(
costingData.summaryData.totalSales
@@ -145,6 +175,14 @@ async function JobCostingMulti(req, res) {
multiSummary.summaryData.totalPartsCost.add(
costingData.summaryData.totalPartsCost
);
multiSummary.summaryData.totalAdditionalCost =
multiSummary.summaryData.totalAdditionalCost.add(
costingData.summaryData.totalAdditionalCost
);
multiSummary.summaryData.totalSubletCost =
multiSummary.summaryData.totalSubletCost.add(
costingData.summaryData.totalSubletCost
);
multiSummary.summaryData.totalCost =
multiSummary.summaryData.totalCost.add(
costingData.summaryData.totalCost
@@ -162,6 +200,14 @@ async function JobCostingMulti(req, res) {
multiSummary.summaryData.totalPartsGp.add(
costingData.summaryData.totalPartsGp
);
multiSummary.summaryData.totalAdditionalGp =
multiSummary.summaryData.totalAdditionalGp.add(
costingData.summaryData.totalAdditionalGp
);
multiSummary.summaryData.totalSubletGp =
multiSummary.summaryData.totalSubletGp.add(
costingData.summaryData.totalSubletGp
);
//Take the summary data & add it to total summary data.
});
@@ -182,10 +228,30 @@ async function JobCostingMulti(req, res) {
multiSummary.summaryData.totalPartsSales.getAmount()) *
100
).toFixed(2);
multiSummary.summaryData.totalPartsGppercentFormatted = formatGpPercent(
multiSummary.summaryData.totalPartsGppercent
);
multiSummary.summaryData.totalAdditionalGppercent = (
(multiSummary.summaryData.totalAdditionalGp.getAmount() /
multiSummary.summaryData.totalAdditionalSales.getAmount()) *
100
).toFixed(2);
multiSummary.summaryData.totalAdditionalGppercentFormatted =
formatGpPercent(multiSummary.summaryData.totalAdditionalGppercent);
multiSummary.summaryData.totalSubletGppercent = (
(multiSummary.summaryData.totalSubletGp.getAmount() /
multiSummary.summaryData.totalSubletSales.getAmount()) *
100
).toFixed(2);
multiSummary.summaryData.totalSubletGppercentFormatted = formatGpPercent(
multiSummary.summaryData.totalSubletGppercent
);
multiSummary.summaryData.gppercent = (
(multiSummary.summaryData.gpdollars.getAmount() /
multiSummary.summaryData.totalSales.getAmount()) *
@@ -201,10 +267,16 @@ async function JobCostingMulti(req, res) {
...c,
sale_labor: c.sale_labor_dinero && c.sale_labor_dinero.toFormat(),
sale_parts: c.sale_parts_dinero && c.sale_parts_dinero.toFormat(),
sales: c.sale_labor_dinero.add(c.sale_parts_dinero).toFormat(),
sale_additional:
c.sale_additional_dinero && c.sale_additional_dinero.toFormat(),
sale_sublet: c.sale_sublet_dinero && c.sale_sublet_dinero.toFormat(),
sales: c.sales_dinero.toFormat(),
cost_parts: c.cost_parts_dinero && c.cost_parts_dinero.toFormat(),
cost_labor: c.cost_labor_dinero && c.cost_labor_dinero.toFormat(),
costs: c.cost_parts_dinero.add(c.cost_labor_dinero).toFormat(),
cost_additional:
c.cost_additional_dinero && c.cost_additional_dinero.toFormat(),
cost_sublet: c.cost_sublet_dinero && c.cost_sublet_dinero.toFormat(),
costs: c.costs_dinero.toFormat(),
gpdollars: c.gpdollars_dinero.toFormat(),
gppercent: formatGpPercent(
(
@@ -224,10 +296,10 @@ async function JobCostingMulti(req, res) {
});
} catch (error) {
logger.log("job-costing-multi-error", "ERROR", req.user.email, [jobids], {
jobids,
error,
message: error.message,
stack: error.stack,
});
res.status(400).send(JSON.stringify(error));
res.status(400).send(error);
}
}
@@ -280,7 +352,12 @@ function GenerateCostingData(job) {
}
}
if (val.part_type && val.part_type !== "PAE") {
if (
val.part_type &&
val.part_type !== "PAE" &&
val.part_type !== "PAS" &&
val.part_type !== "PASL"
) {
const partsProfitCenter =
val.profitcenter_part || defaultProfits[val.part_type] || "?";
@@ -312,6 +389,42 @@ function GenerateCostingData(job) {
acc.parts[partsProfitCenter] =
acc.parts[partsProfitCenter].add(partsAmount);
}
if (
val.part_type &&
val.part_type !== "PAE" &&
(val.part_type === "PAS" || val.part_type === "PASL")
) {
const partsProfitCenter =
val.profitcenter_part || defaultProfits[val.part_type] || "?";
if (partsProfitCenter === "?")
console.log("Unknown type", val.line_desc, val.part_type);
if (!partsProfitCenter)
console.log(
"Unknown cost/profit center mapping for sublet.",
val.line_desc,
val.part_type
);
const partsAmount = Dinero({
amount: Math.round((val.act_price || 0) * 100),
})
.multiply(val.part_qty || 1)
.add(
val.prt_dsmk_m && val.prt_dsmk_m !== 0
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
: Dinero({
amount: Math.round(val.act_price * 100),
})
.multiply(val.part_qty || 0)
.percentage(Math.abs(val.prt_dsmk_p || 0))
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
);
if (!acc.sublet[partsProfitCenter])
acc.sublet[partsProfitCenter] = Dinero();
acc.sublet[partsProfitCenter] =
acc.sublet[partsProfitCenter].add(partsAmount);
}
//To deal with additional costs.
if (!val.part_type && !val.mod_lbr_ty) {
@@ -340,33 +453,33 @@ function GenerateCostingData(job) {
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
);
if (!acc.parts[partsProfitCenter])
acc.parts[partsProfitCenter] = Dinero();
acc.parts[partsProfitCenter] =
acc.parts[partsProfitCenter].add(partsAmount);
if (!acc.additional[partsProfitCenter])
acc.additional[partsProfitCenter] = Dinero();
acc.additional[partsProfitCenter] =
acc.additional[partsProfitCenter].add(partsAmount);
}
}
return acc;
},
{ parts: {}, labor: {} }
{ parts: {}, labor: {}, additional: {}, sublet: {} }
);
if (!hasMapaLine) {
if (!jobLineTotalsByProfitCenter.parts[defaultProfits["MAPA"]])
jobLineTotalsByProfitCenter.parts[defaultProfits["MAPA"]] = Dinero();
jobLineTotalsByProfitCenter.parts[defaultProfits["MAPA"]] =
jobLineTotalsByProfitCenter.parts[defaultProfits["MAPA"]].add(
if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]])
jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] = Dinero();
jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]] =
jobLineTotalsByProfitCenter.additional[defaultProfits["MAPA"]].add(
Dinero({
amount: Math.round((job.rate_mapa || 0) * 100),
}).multiply(materialsHours.mapaHrs || 0)
);
}
if (!hasMashLine) {
if (!jobLineTotalsByProfitCenter.parts[defaultProfits["MASH"]])
jobLineTotalsByProfitCenter.parts[defaultProfits["MASH"]] = Dinero();
jobLineTotalsByProfitCenter.parts[defaultProfits["MASH"]] =
jobLineTotalsByProfitCenter.parts[defaultProfits["MASH"]].add(
if (!jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]])
jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] = Dinero();
jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]] =
jobLineTotalsByProfitCenter.additional[defaultProfits["MASH"]].add(
Dinero({
amount: Math.round((job.rate_mash || 0) * 100),
}).multiply(materialsHours.mashHrs || 0)
@@ -381,54 +494,107 @@ function GenerateCostingData(job) {
)) ||
job.bodyshop.md_responsibility_centers.defaults;
const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
//At the bill level.
bill_val.billlines.map((line_val) => {
//At the bill line level.
if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid) {
if (!bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]])
const billTotalsByCostCenters = job.bills.reduce(
(bill_acc, bill_val) => {
//At the bill level.
bill_val.billlines.map((line_val) => {
//At the bill line level.
if (job.bodyshop.pbs_serialnumber || job.bodyshop.cdk_dealerid) {
if (
!bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]]
)
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] =
Dinero();
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] =
Dinero();
bill_acc[
selectedDmsAllocationConfig.costs[line_val.cost_center]
].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1)
);
} else {
const isSubletCostCenter =
line_val.cost_center ===
job.bodyshop.md_responsibility_centers.defaults.costs.PAS ||
line_val.cost_center ===
job.bodyshop.md_responsibility_centers.defaults.costs.PASL;
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] =
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1)
);
} else {
if (!bill_acc[line_val.cost_center])
bill_acc[line_val.cost_center] = Dinero();
const isAdditionalCostCenter =
// line_val.cost_center ===
// job.bodyshop.md_responsibility_centers.defaults.costs.PAS ||
// line_val.cost_center ===
// job.bodyshop.md_responsibility_centers.defaults.costs.PASL ||
line_val.cost_center ===
job.bodyshop.md_responsibility_centers.defaults.costs.TOW ||
line_val.cost_center ===
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA ||
line_val.cost_center ===
job.bodyshop.md_responsibility_centers.defaults.costs.MASH;
bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1)
);
}
if (isAdditionalCostCenter) {
if (!bill_acc.additionalCosts[line_val.cost_center])
bill_acc.additionalCosts[line_val.cost_center] = Dinero();
return null;
});
return bill_acc;
}, {});
bill_acc.additionalCosts[line_val.cost_center] =
bill_acc.additionalCosts[line_val.cost_center].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1)
);
} else if (isSubletCostCenter) {
if (!bill_acc.subletCosts[line_val.cost_center])
bill_acc.subletCosts[line_val.cost_center] = Dinero();
bill_acc.subletCosts[line_val.cost_center] = bill_acc.subletCosts[
line_val.cost_center
].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1)
);
} else {
if (!bill_acc[line_val.cost_center])
bill_acc[line_val.cost_center] = Dinero();
bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1)
);
}
}
return null;
});
return bill_acc;
},
{ additionalCosts: {}, subletCosts: {} }
);
//If the hourly rates for job costing are set, add them in.
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mapa) {
if (
!billTotalsByCostCenters[
!billTotalsByCostCenters.additionalCosts[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
]
)
billTotalsByCostCenters[
billTotalsByCostCenters.additionalCosts[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = Dinero();
billTotalsByCostCenters[
billTotalsByCostCenters.additionalCosts[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = billTotalsByCostCenters[
] = billTotalsByCostCenters.additionalCosts[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
].add(
Dinero({
@@ -442,16 +608,16 @@ function GenerateCostingData(job) {
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
if (
!billTotalsByCostCenters[
!billTotalsByCostCenters.additionalCosts[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
]
)
billTotalsByCostCenters[
billTotalsByCostCenters.additionalCosts[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
] = Dinero();
billTotalsByCostCenters[
billTotalsByCostCenters.additionalCosts[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
] = billTotalsByCostCenters[
] = billTotalsByCostCenters.additionalCosts[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
].add(
Dinero({
@@ -511,18 +677,27 @@ function GenerateCostingData(job) {
const summaryData = {
totalLaborSales: Dinero({ amount: 0 }),
totalPartsSales: Dinero({ amount: 0 }),
totalAdditionalSales: Dinero({ amount: 0 }),
totalSubletSales: Dinero({ amount: 0 }),
totalSales: Dinero({ amount: 0 }),
totalLaborCost: Dinero({ amount: 0 }),
totalPartsCost: Dinero({ amount: 0 }),
totalAdditionalCost: Dinero({ amount: 0 }),
totalSubletCost: Dinero({ amount: 0 }),
totalCost: Dinero({ amount: 0 }),
totalLaborGp: Dinero({ amount: 0 }),
totalPartsGp: Dinero({ amount: 0 }),
totalAdditionalGp: Dinero({ amount: 0 }),
totalSubletGp: Dinero({ amount: 0 }),
gpdollars: Dinero({ amount: 0 }),
totalLaborGppercent: null,
totalLaborGppercentFormatted: null,
totalPartsGppercent: null,
totalPartsGppercentFormatted: null,
totalAdditionalGppercent: null,
totalAdditionalGppercentFormatted: null,
totalSubletGppercent: null,
totalSubletGppercentFormatted: null,
gppercent: null,
gppercentFormatted: null,
};
@@ -533,14 +708,26 @@ function GenerateCostingData(job) {
jobLineTotalsByProfitCenter.labor[ccVal] || Dinero({ amount: 0 });
const sale_parts =
jobLineTotalsByProfitCenter.parts[ccVal] || Dinero({ amount: 0 });
const sale_additional =
jobLineTotalsByProfitCenter.additional[ccVal] || Dinero({ amount: 0 });
const sale_sublet =
jobLineTotalsByProfitCenter.sublet[ccVal] || Dinero({ amount: 0 });
const cost_labor = ticketTotalsByCostCenter[ccVal] || Dinero({ amount: 0 });
const cost_parts = billTotalsByCostCenters[ccVal] || Dinero({ amount: 0 });
const cost_additional =
billTotalsByCostCenters.additionalCosts[ccVal] || Dinero({ amount: 0 });
const cost_sublet =
billTotalsByCostCenters.subletCosts[ccVal] || Dinero({ amount: 0 });
const costs = (billTotalsByCostCenters[ccVal] || Dinero({ amount: 0 })).add(
ticketTotalsByCostCenter[ccVal] || Dinero({ amount: 0 })
);
const totalSales = sale_labor.add(sale_parts);
const costs = cost_labor
.add(cost_parts)
.add(cost_additional)
.add(cost_sublet);
const totalSales = sale_labor
.add(sale_parts)
.add(sale_additional)
.add(sale_sublet);
const gpdollars = totalSales.subtract(costs);
const gppercent = (
(gpdollars.getAmount() / totalSales.getAmount()) *
@@ -550,11 +737,16 @@ function GenerateCostingData(job) {
//Push summary data to avoid extra loop.
summaryData.totalLaborSales = summaryData.totalLaborSales.add(sale_labor);
summaryData.totalPartsSales = summaryData.totalPartsSales.add(sale_parts);
summaryData.totalSales = summaryData.totalSales
.add(sale_labor)
.add(sale_parts);
summaryData.totalAdditionalSales =
summaryData.totalAdditionalSales.add(sale_additional);
summaryData.totalSubletSales =
summaryData.totalSubletSales.add(sale_sublet);
summaryData.totalSales = summaryData.totalSales.add(totalSales);
summaryData.totalLaborCost = summaryData.totalLaborCost.add(cost_labor);
summaryData.totalPartsCost = summaryData.totalPartsCost.add(cost_parts);
summaryData.totalAdditionalCost =
summaryData.totalAdditionalCost.add(cost_additional);
summaryData.totalSubletCost = summaryData.totalSubletCost.add(cost_sublet);
summaryData.totalCost = summaryData.totalCost.add(costs);
return {
@@ -564,14 +756,22 @@ function GenerateCostingData(job) {
sale_labor_dinero: sale_labor,
sale_parts: sale_parts && sale_parts.toFormat(),
sale_parts_dinero: sale_parts,
sales: sale_labor.add(sale_parts).toFormat(),
sales_dinero: sale_labor.add(sale_parts),
sale_additional: sale_additional && sale_additional.toFormat(),
sale_additional_dinero: sale_additional,
sale_sublet: sale_sublet && sale_sublet.toFormat(),
sale_sublet_dinero: sale_sublet,
sales: totalSales.toFormat(),
sales_dinero: totalSales,
cost_parts: cost_parts && cost_parts.toFormat(),
cost_parts_dinero: cost_parts,
cost_labor: cost_labor && cost_labor.toFormat(),
cost_labor_dinero: cost_labor,
costs: cost_parts.add(cost_labor).toFormat(),
costs_dinero: cost_parts.add(cost_labor),
cost_additional: cost_additional && cost_additional.toFormat(),
cost_additional_dinero: cost_additional,
cost_sublet: cost_sublet && cost_sublet.toFormat(),
cost_sublet_dinero: cost_sublet,
costs: costs.toFormat(),
costs_dinero: costs,
gpdollars_dinero: gpdollars,
gpdollars: gpdollars.toFormat(),
gppercent: formatGpPercent(gppercent),
@@ -592,12 +792,20 @@ function GenerateCostingData(job) {
sale_labor_dinero: Dinero(),
sale_parts: Dinero().toFormat(),
sale_parts_dinero: Dinero(),
sale_additional: Dinero(),
sale_additional_dinero: Dinero(),
sale_sublet: Dinero(),
sale_sublet_dinero: Dinero(),
sales: Dinero().toFormat(),
sales_dinero: Dinero(),
cost_parts: Dinero().toFormat(),
cost_parts_dinero: Dinero(),
cost_labor: Adjustment.toFormat(),
cost_labor_dinero: Adjustment,
cost_additional: Dinero(),
cost_additional_dinero: Dinero(),
cost_sublet: Dinero(),
cost_sublet_dinero: Dinero(),
costs: Adjustment.toFormat(),
costs_dinero: Adjustment,
gpdollars_dinero: Dinero(),
@@ -631,6 +839,28 @@ function GenerateCostingData(job) {
summaryData.totalPartsGppercentFormatted = formatGpPercent(
summaryData.totalPartsGppercent
);
summaryData.totalAdditionalGp = summaryData.totalAdditionalSales.subtract(
summaryData.totalAdditionalCost
);
summaryData.totalAdditionalGppercent = (
(summaryData.totalAdditionalGp.getAmount() /
summaryData.totalAdditionalSales.getAmount()) *
100
).toFixed(2);
summaryData.totalAdditionalGppercentFormatted = formatGpPercent(
summaryData.totalAdditionalGppercent
);
summaryData.totalSubletGp = summaryData.totalSubletSales.subtract(
summaryData.totalSubletCost
);
summaryData.totalSubletGppercent = (
(summaryData.totalSubletGp.getAmount() /
summaryData.totalSubletSales.getAmount()) *
100
).toFixed(2);
summaryData.totalSubletGppercentFormatted = formatGpPercent(
summaryData.totalSubletGppercent
);
summaryData.gpdollars = summaryData.totalSales.subtract(
summaryData.totalCost
@@ -667,7 +897,7 @@ const formatGpPercent = (gppercent) => {
//Verify that this stays in line with jobs-close-auto-allocate logic from the application.
const getAdditionalCostCenter = (jl, profitCenters) => {
if (!jl.part_type && !jl.mod_lbr_ty) {
const lineDesc = jl.line_desc.toLowerCase();
const lineDesc = jl.line_desc ? jl.line_desc.toLowerCase() : "";
if (lineDesc.includes("shop mat")) {
return profitCenters["MASH"];
@@ -675,6 +905,8 @@ const getAdditionalCostCenter = (jl, profitCenters) => {
return profitCenters["MAPA"];
} else if (lineDesc.includes("ats amount")) {
return profitCenters["ATS"];
} else if (lineDesc.includes("towing")) {
return profitCenters["TOW"];
} else {
return null;
}

View File

@@ -442,8 +442,8 @@ function CalculateAdditional(job) {
ret.total = ret.additionalCosts
.add(ret.adjustments) //IO-813 Adjustment takes care of GST & PST at labor rate.
.add(ret.towing)
.add(ret.storage)
.add(ret.pvrt);
.add(ret.storage);
//.add(ret.pvrt);
return ret;
}
@@ -453,6 +453,7 @@ function CalculateTaxesTotals(job, otherTotals) {
.add(otherTotals.parts.sublets.subtotal)
.add(otherTotals.rates.subtotal) //No longer using just rates subtotal to include mapa/mash.
.add(otherTotals.additional.total);
// .add(Dinero({ amount: (job.towing_payable || 0) * 100 }))
// .add(Dinero({ amount: (job.storage_payable || 0) * 100 }));
@@ -522,7 +523,13 @@ function CalculateTaxesTotals(job, otherTotals) {
let ret = {
subtotal: subtotal,
federal_tax: subtotal.percentage((job.federal_tax_rate || 0) * 100),
federal_tax: subtotal
.percentage((job.federal_tax_rate || 0) * 100)
.add(
otherTotals.additional.pvrt.percentage(
(job.federal_tax_rate || 0) * 100
)
),
statePartsTax,
state_tax: statePartsTax
.add(
@@ -540,12 +547,14 @@ function CalculateTaxesTotals(job, otherTotals) {
otherTotals.additional.storage.percentage((job.tax_str_rt || 0) * 100)
)
.add(additionalItemsTax),
// .add(otherTotals.additional.pvrt),
local_tax: subtotal.percentage((job.local_tax_rate || 0) * 100),
};
ret.total_repairs = ret.subtotal
.add(ret.federal_tax)
.add(ret.state_tax)
.add(ret.local_tax);
.add(ret.local_tax)
.add(otherTotals.additional.pvrt);
ret.custPayable = {
deductible: Dinero({ amount: Math.round((job.ded_amt || 0) * 100) }) || 0,

View File

@@ -2,7 +2,7 @@ const GraphQLClient = require("graphql-request").GraphQLClient;
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment");
const moment = require("moment-timezone");
const logger = require("../utils/logger");
const _ = require("lodash");
require("dotenv").config({
@@ -32,7 +32,7 @@ exports.job = async (req, res) => {
});
const { jobs_by_pk, blockedDays, prodJobs, arrJobs, compJobs } = result;
const { ssbuckets, workingdays } = result.jobs_by_pk.bodyshop;
const { ssbuckets, workingdays, timezone } = result.jobs_by_pk.bodyshop;
const jobHrs = result.jobs_by_pk.jobhrs.aggregate.sum.mod_lb_hrs;
const JobBucket = ssbuckets.filter(
@@ -68,7 +68,9 @@ exports.job = async (req, res) => {
);
filteredArrJobs.forEach((item) => {
const itemDate = moment(item.scheduled_in).format("yyyy-MM-DD");
const itemDate = moment(item.scheduled_in)
.tz(timezone)
.format("yyyy-MM-DD");
if (!!load[itemDate]) {
load[itemDate].hoursIn =
(load[itemDate].hoursIn || 0) +
@@ -100,20 +102,18 @@ exports.job = async (req, res) => {
//Job isn't found in production or coming in.
//is it going today or scheduled to go today?
if (
moment(item.actual_completion || item.scheduled_completion).isSame(
moment(),
"day"
)
moment(item.actual_completion || item.scheduled_completion)
.tz(timezone)
.isSame(moment().tz(timezone), "day")
) {
console.log("Job is going today anyways, ignore it.", item);
return;
}
if (
moment(item.actual_completion || item.scheduled_completion).isBefore(
moment(),
"day"
)
moment(item.actual_completion || item.scheduled_completion)
.tz(timezone)
.isBefore(moment().tz(timezone), "day")
) {
console.log("Job should have already gone. Ignoring it.", item);
return;
@@ -127,7 +127,9 @@ exports.job = async (req, res) => {
} else {
const itemDate = moment(
item.actual_completion || item.scheduled_completion
).format("yyyy-MM-DD");
)
.tz(timezone)
.format("yyyy-MM-DD");
if (!!load[itemDate]) {
load[itemDate].hoursOut =
(load[itemDate].hoursOut || 0) +
@@ -146,20 +148,26 @@ exports.job = async (req, res) => {
});
//Propagate the expected load to each day.
const yesterday = moment().subtract(1, "day");
const today = moment().startOf("day");
const yesterday = moment().tz(timezone).subtract(1, "day");
const today = moment().tz(timezone);
const end = moment.max([
...filteredArrJobs.map((a) => moment(a.scheduled_in)),
...filteredArrJobs.map((a) => moment(a.scheduled_in).tz(timezone)),
...filteredCompJobs
.map((p) => moment(p.actual_completion || p.scheduled_completion))
.map((p) =>
moment(p.actual_completion || p.scheduled_completion).tz(timezone)
)
.filter((p) => p.isValid() && p.isAfter(yesterday)),
moment().add(5, "days"),
moment().tz(timezone).add(15, "days"),
]);
const range = Math.round(moment.duration(end.diff(today)).asDays());
for (var day = 0; day < range; day++) {
const current = moment(today).add(day, "days").format("yyyy-MM-DD");
const current = moment(today)
.tz(timezone)
.add(day, "days")
.format("yyyy-MM-DD");
const prev = moment(today)
.tz(timezone)
.add(day - 1, "days")
.format("yyyy-MM-DD");
if (!!!load[current]) {
@@ -187,13 +195,13 @@ exports.job = async (req, res) => {
blockedDays.forEach((b) => {
//Find it in the load, set it as blocked.
const startIsoFormat = moment(b.start).format("YYYY-MM-DD");
const startIsoFormat = moment(b.start).tz(timezone).format("YYYY-MM-DD");
if (load[startIsoFormat]) load[startIsoFormat].blocked = true;
else {
load[startIsoFormat] = { blocked: true };
}
});
// //Propose the first 5 dates where we are below target.
// //Propose the first 10 dates where we are below target.
const possibleDates = [];
delete load.productionTotal;
@@ -215,10 +223,10 @@ exports.job = async (req, res) => {
possibleDates.push(new Date(loadKey).toISOString().substr(0, 10));
});
if (possibleDates.length < 6) {
if (possibleDates.length < 11) {
res.json(possibleDates);
} else {
res.json(possibleDates.slice(0, 5));
res.json(possibleDates.slice(0, 10));
}
} catch (error) {
logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, {

92
server/tasks/tasks.js Normal file
View File

@@ -0,0 +1,92 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const axios = require("axios");
const client = require("../graphql-client/graphql-client").client;
const emailer = require("../email/sendemail");
const logger = require("../utils/logger");
const moment = require("moment-timezone");
exports.taskHandler = async (req, res) => {
try {
const { bodyshopid, query, variables, text, to, subject, timezone } =
req.body;
//Run the query
//Check the variables to see if they are an object.
Object.keys(variables).forEach((key) => {
if (typeof variables[key] === "object") {
if (variables[key].function) {
variables[key] = functionMapper(variables[key].function, timezone);
}
}
});
const response = await client.request(query, variables);
//Massage the data
//Send the email
const rootElement = response[Object.keys(response)[0]]; //This element shoudl always be an array.
let converter = require("json-2-csv");
converter.json2csv(
rootElement,
(err, csv) => {
if (err) {
res.status(500).json(err);
}
emailer.sendTaskEmail({
to,
subject,
text,
attachments: [{ filename: "query.csv", content: csv }],
});
res.status(200).send(csv);
},
{ emptyFieldValue: "" }
);
} catch (error) {
res.status(500).json({ error: error.message, stack: error.stackTrace });
}
};
const isoformat = "YYYY-MM-DD";
function functionMapper(f, timezone) {
switch (f) {
case "date.today":
return moment().tz(timezone).format(isoformat);
case "date.now":
return moment().tz(timezone);
case "date.yesterday":
return moment().tz(timezone).subtract(1, "day").format(isoformat);
case "date.3daysago":
return moment().tz(timezone).subtract(3, "days").format(isoformat);
case "date.7daysago":
return moment().tz(timezone).subtract(7, "days").format(isoformat);
case "date.tomorrow":
return moment().tz(timezone).add(1, "day").format(isoformat);
case "date.3daysfromnow":
return moment().tz(timezone).add(3, "days").format(isoformat);
case "date.7daysfromnow":
return moment().tz(timezone).add(7, "days").format(isoformat);
case "date.yesterdaytz":
return moment().tz(timezone).subtract(1, "day");
case "date.3daysagotz":
return moment().tz(timezone).subtract(3, "days");
case "date.7daysagotz":
return moment().tz(timezone).subtract(7, "days");
case "date.tomorrowtz":
return moment().tz(timezone).add(1, "day");
case "date.3daysfromnowtz":
return moment().tz(timezone).add(3, "days");
case "date.7daysfromnowtz":
return moment().tz(timezone).add(7, "days");
case "date.now":
return moment().tz(timezone);
default:
return f;
}
}

View File

@@ -1172,6 +1172,11 @@ decode-uri-component@^0.2.0:
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
deeks@2.5.1:
version "2.5.1"
resolved "https://registry.yarnpkg.com/deeks/-/deeks-2.5.1.tgz#c4e18a00cac3f8ff95a306463db8f533c879f436"
integrity sha512-fqrBeUz7f1UqaXDRzVB5RG2EfPk15HJRrb2pMZj8mLlSTtz4tRPsK5leFOskoHFPuyZ6+7aRM9j657fvXLkJ7Q==
deep-is@^0.1.3, deep-is@~0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
@@ -1213,6 +1218,11 @@ dinero.js@^1.9.1:
resolved "https://registry.yarnpkg.com/dinero.js/-/dinero.js-1.9.1.tgz#64b10ce7277a07805dac9c8cd6500e9b7d0aee96"
integrity sha512-1HXiF2vv3ZeRQ23yr+9lFxj/PbZqutuYWJnE0qfCB9xYBPnuaJ8lXtli1cJM0TvUXW1JTOaePldmqN5JVNxKSA==
doc-path@3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/doc-path/-/doc-path-3.0.2.tgz#a7df9d58eadce28a2ddd4eda5aa392628fd75b6d"
integrity sha512-VRlA2OKSjTbHWj6wmSanxJ338fE/YN8pqmZ0FIWK5JWkIJMFRc4KmD35JtOrnjvVG0WrzOtDDNHx1lN1tkb+lA==
doctrine@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
@@ -2346,6 +2356,14 @@ jsbn@~0.1.0:
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
json-2-csv@^3.17.0:
version "3.17.0"
resolved "https://registry.yarnpkg.com/json-2-csv/-/json-2-csv-3.17.0.tgz#d886e4ec382bdb96dd31cbf87e0206173176d3c7"
integrity sha512-mLnrYK3TX/mDSiBduA6vuSuVlvkYgDMv+Ef8HXCriyz73BBc6I7pWljdRfR+FKPzYKcnz/rUvzbqJQx/tbEoMQ==
dependencies:
deeks "2.5.1"
doc-path "3.0.2"
json-bigint@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/json-bigint/-/json-bigint-1.0.0.tgz#ae547823ac0cad8398667f8cd9ef4730f5b01ff1"
@@ -2740,7 +2758,14 @@ mkdirp@^0.5.1:
dependencies:
minimist "^1.2.5"
moment@^2.29.1:
moment-timezone@^0.5.34:
version "0.5.34"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c"
integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==
dependencies:
moment ">= 2.9.0"
"moment@>= 2.9.0", moment@^2.29.1:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==