Compare commits

...

79 Commits

Author SHA1 Message Date
Patrick Fic
7595cbf11d IO-2792 Resolve bill receive of credit memo and noline 2024-05-29 14:10:32 -07:00
Dave Richer
786f2f212c Merged in release/2024-05-24 (pull request #1448)
Release/2024 05 24
2024-05-24 20:44:00 +00:00
Patrick Fic
bdfbe52244 Remove API from master deployment. 2024-05-24 10:18:58 -07:00
Patrick Fic
0ca75298d8 Merge branch 'release/2024-05-17' into release/2024-05-24 2024-05-24 10:17:42 -07:00
Dave Richer
1ffad4a8b0 Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-05-14 12:37:33 -04:00
Dave Richer
cac7df66e1 Fix issue with global search 2024-05-14 12:19:58 -04:00
Patrick Fic
cb50ca7897 Merged in release/2024-04-26 (pull request #1439)
Release/2024 04 26
2024-05-03 21:35:26 +00:00
Allan Carr
a4f4f45251 Merged in feature/IO-2717-Paint-Material-from-Scale-for-CDK (pull request #1438)
IO-2717 CDK Use Scale Data for Paint Materials Cost
2024-05-02 05:53:35 +00:00
Allan Carr
acc91abc0c IO-2717 CDK Use Scale Data for Paint Materials Cost
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-05-01 13:26:24 -07:00
Patrick Fic
d6d6ced7a4 CC modifications. 2024-05-01 11:47:42 -07:00
Patrick Fic
17928741e3 Merge branch 'feature/IO-2766-intellipay-postback-refactor' into release/2024-04-26 2024-04-25 16:44:55 -07:00
Patrick Fic
a6b825ffdf Move intellipay to server side processing. 2024-04-25 16:43:49 -07:00
Allan Carr
f2914868e2 Merged in feature/IO-2761-Actual-Complete-in-Vehicle-Owner-Details (pull request #1434)
IO-2761 Actual Completion in Vehicle and Owners Job Details lists

Approved-by: Dave Richer
2024-04-23 17:40:10 +00:00
Allan Carr
561f0313f9 Merged in feature/IO-2762-Return-From-Bill-Reference (pull request #1435)
IO-2762 Return from Bill Reference in Parts Return Drawer

Approved-by: Dave Richer
2024-04-23 17:39:27 +00:00
Allan Carr
02a49efbea IO-2762 Return from Bill Reference in Parts Return Drawer
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-22 14:14:54 -07:00
Allan Carr
4fb9c37c0d IO-2761 Actual Completion in Vehicle and Owners Job Details lists
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-22 09:15:59 -07:00
Patrick Fic
1620b94a7b Merged in release/2024-04-19 (pull request #1414)
Resolve schedule header display.
2024-04-15 20:47:48 +00:00
Patrick Fic
81f94eac6c Resolve schedule header display. 2024-04-15 13:47:19 -07:00
Allan Carr
ec7509670d Merged in release/2024-04-12 (pull request #1413)
Release/2024 04 12

Approved-by: Dave Richer
2024-04-12 17:08:21 +00:00
Allan Carr
0acfd3c4b1 Merged in feature/IO-2609-Calendar-BPT-HRS (pull request #1409)
IO-2609 Fix Spelling Mistake in object name
2024-04-11 21:39:06 +00:00
Allan Carr
bfc4cb1ad9 IO-2609 Fix Spelling Mistake in object name
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-11 14:38:08 -07:00
Allan Carr
1f42be2e54 Merged in feature/IO-2753-Qty-Parts-Order-Modal (pull request #1405)
IO-2753 Parts Order/Return Quantity restrict to above 0
2024-04-11 21:22:37 +00:00
Allan Carr
30d344af6b Merged in feature/IO-2752-BO-ETA-Jobline-Expander (pull request #1404)
IO-2752 BO ETA Jobline Expander
2024-04-11 21:22:19 +00:00
Allan Carr
3ca989fd8c Merged in release/2024-04-05 (pull request #1401)
Release/2024 04 05

Approved-by: Dave Richer
2024-04-11 21:22:03 +00:00
Allan Carr
ce2086a480 IO-2753 Parts Order/Return Quantity restrict to above 0
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-11 12:43:14 -07:00
Allan Carr
63f7106d2b IO-2752 BO ETA Jobline Expander
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-11 11:55:55 -07:00
Allan Carr
e75d8d1874 Merged in feature/IO-2609-Calendar-BPT-HRS (pull request #1402)
IO-2609 Body & Refinish Times included in Calendar View

Approved-by: Dave Richer
2024-04-11 16:27:02 +00:00
Allan Carr
07bf84ed69 IO-2609 Body & Refinish Times included in Calendar View
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-09 11:55:20 -07:00
Allan Carr
c2cc7b1e9e Merged in feature/IO-2731-Payment-Edit (pull request #1399)
IO-2731 Payment Edit
2024-04-08 19:56:43 +00:00
Allan Carr
fe55eccbf9 Merged in feature/IO-2749-Parts-Return-Pass-Jobs-Data (pull request #1398)
IO-2749 Pass Jobs data from Parts Return to Parts Order Modal
2024-04-08 19:55:49 +00:00
Allan Carr
88a71dd647 IO-2731 Payment Edit
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-08 12:23:04 -07:00
Allan Carr
c3b395c99e IO-2749 Pass Jobs data from Parts Return to Parts Order Modal
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-08 09:53:20 -07:00
Allan Carr
33d5d9b462 Merged in feature/IO-2568-Payment-Modal-Button-Spacing (pull request #1396)
IO-2568 Button Padding in Print Center Label Modal
2024-04-05 19:03:09 +00:00
Allan Carr
b5a371d0cf IO-2568 Button Padding in Print Center Label Modal
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-05 12:01:26 -07:00
Allan Carr
3b35f38ad5 Merged in feature/IO-2750-Missing-Jobline-Fields (pull request #1393)
IO-2750 Missing Mutation return fields
2024-04-05 15:55:13 +00:00
Allan Carr
1f5c1b9658 IO-2750 Missing Mutation return fields
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-04 17:00:30 -07:00
Patrick Fic
37196e65c3 Add schema changes for RO Guard to bodyshop table. 2024-04-03 09:46:52 -07:00
Allan Carr
b20c605c85 Merged in feature/IO-2568-Payment-Modal-Button-Spacing (pull request #1384)
IO-2568 Payment Modal Button Spacing

Approved-by: Dave Richer
2024-04-02 19:03:49 +00:00
Allan Carr
d8ac708536 Merged in feature/IO-2552-PVRT-Button-Spacing (pull request #1386)
IO-2552 PVRT Button Spacing and Alignment

Approved-by: Dave Richer
2024-04-02 19:03:36 +00:00
Allan Carr
8a32fe50f3 Merged in feature/IO-2730-Bill-Search-Result-Align (pull request #1383)
IO-2730 Bill Search Result Align

Approved-by: Dave Richer
2024-04-02 19:03:06 +00:00
Allan Carr
7897a490bd Merged in feature/IO-2563-Repair-Line-Expander-Bills-Transaltion (pull request #1385)
IO-2563 Repair LIne Expander Bills Translation

Approved-by: Dave Richer
2024-04-02 19:02:50 +00:00
Allan Carr
17d73fc6d7 Merged in feature/IO-2553-Edit-CC-Unsaved-Changes (pull request #1388)
IO-2553 Unsaved Changes on Edit CC

Approved-by: Dave Richer
2024-04-02 19:02:28 +00:00
Allan Carr
7d1910086e IO-2553 Unsaved Changes on Edit CC
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-02 10:39:41 -07:00
Allan Carr
817c41afb9 IO-2552 PVRT Button Spacing and Alignment
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-01 14:27:25 -07:00
Allan Carr
04315a9045 IO-2563 Repair LIne Expander Bills Translation
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-01 14:07:58 -07:00
Allan Carr
d0871ffe21 IO-2568 Payment Modal Button Spacing
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-01 13:52:25 -07:00
Allan Carr
dca587d6e0 IO-2730 Bill Search Result Align
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-01 13:20:30 -07:00
Allan Carr
19ec4cb021 Merged in release/2024-03-28 (pull request #1382)
Release/2024 03 28
2024-03-28 21:15:25 +00:00
Allan Carr
346e82bdbc Merged in feature/IO-2702-Limit-Production-Colors-to-Production-Statuses (pull request #1380)
IO-2702 Adjust concat
2024-03-28 20:24:19 +00:00
Allan Carr
fd9575e5a5 IO-2702 Adjust concat
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-28 13:25:13 -07:00
Patrick Fic
af6bc0fb5a Resolve hasura API url again. 2024-03-28 10:23:22 -07:00
Allan Carr
d0e289757f Merged in feature/IO-2705-Insurance-Co-ID (pull request #1375)
IO-2705 Insurance Co Id and correct Select Insurance and Layout for concistency

Approved-by: Dave Richer
2024-03-28 16:59:52 +00:00
Allan Carr
becc54f7b3 Merged in feature/IO-2714-New-Payment-Cache (pull request #1376)
IO-2714 New Payment Cache

Approved-by: Dave Richer
2024-03-28 16:51:59 +00:00
Allan Carr
e3fabdac91 Merged in feature/IO-2727-Payment-Export-Re-Export-Button (pull request #1377)
IO-2727 Payment Re-export/Export button

Approved-by: Dave Richer
2024-03-28 16:51:20 +00:00
Allan Carr
40621db556 Merged in feature/IO-2702-Limit-Production-Colors-to-Production-Statuses (pull request #1374)
IO-2702 Limit Production Colors to Production Statuses

Approved-by: Dave Richer
2024-03-28 16:50:51 +00:00
Patrick Fic
c2e64a124d Cherry picked from Rome changes to apply to IO. 2024-03-27 12:21:28 -07:00
Allan Carr
1fa83d124d IO-2727 Payment Re-export/Export button
Send data back to page

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-27 12:14:47 -07:00
Allan Carr
cc734d3981 IO-2714 New Payment Cache
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-26 16:04:05 -07:00
Allan Carr
b41d69593d IO-2705 Insurance Co Id and correct Select Insurance and Layout for concistency
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-26 14:53:52 -07:00
Allan Carr
b7055aac84 IO-2702 Limit Production Colors to Production Statuses
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-26 13:45:04 -07:00
Dave Richer
09e505399c Merged in release/2024-03-22 (pull request #1372)
Release/2024 03 22

Approved-by: Patrick Fic
2024-03-22 18:27:16 +00:00
Allan Carr
42f9f275c7 Merged in feature/IO-2721-Owners-Note-in-Owners-Card (pull request #1370)
IO-2721 Owners Note in Owners Card

Approved-by: Dave Richer
2024-03-22 16:18:23 +00:00
Allan Carr
39b119b2e8 IO-2721 Owners Note in Owners Card
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-21 17:49:46 -07:00
Allan Carr
5bc224206c Merged in feature/IO-2711-Check-Box-Visibility (pull request #1362)
IO-2711 Check Box Visibility

Approved-by: Patrick Fic
2024-03-21 21:42:43 +00:00
Allan Carr
190da863c2 Merged in feature/IO-2698-Fuel-Level-&-Sorter (pull request #1363)
IO-2698 Fuel Level & Sorter

Approved-by: Patrick Fic
2024-03-21 21:41:12 +00:00
Allan Carr
2711b5fce5 Merged in feature/IO-2686-Disable-Return-on-isinhouse (pull request #1364)
IO-2686 Disable Return Item button on Bill Drawer if InHouse

Approved-by: Patrick Fic
2024-03-21 21:40:39 +00:00
Allan Carr
8e9358cd6f Merged in feature/IO-2713-Bills-&-Media-Visual-Seperation (pull request #1365)
IO-2713 Visually Seperate Bill Entry & Media Areas

Approved-by: Patrick Fic
2024-03-21 21:40:12 +00:00
Allan Carr
0e31bbb789 Merged in feature/IO-2710-Job-Assignment (pull request #1366)
IO-2710 Job Assignment

Approved-by: Patrick Fic
2024-03-21 21:39:36 +00:00
Allan Carr
3b635aeed3 Merged in feature/IO-2719-Missing-CC-Filter (pull request #1367)
IO-2719 Missing CC Filter

Approved-by: Patrick Fic
2024-03-21 21:39:05 +00:00
Allan Carr
1f896b1ede IO-2719 Missing CC Filter
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-21 11:09:13 -07:00
Allan Carr
9d479d4b4d IO-2713 Visually Seperate Bill Entry & Media Areas
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-19 17:19:22 -07:00
Allan Carr
23b5b740cb IO-2686 Disable Return Item button on Bill Drawer if InHouse
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-19 17:17:15 -07:00
Allan Carr
8f9b05b974 IO-2698 Fuel Level & Sorter
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-19 14:41:34 -07:00
Allan Carr
d1132e7d45 IO-2711 Check Box Visibility
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-19 14:30:31 -07:00
Dave Richer
960b0b4d09 Merged in feature/IO-2650-Bugfix-On-Statuses (pull request #1361)
- Hasura Migration Changes
2024-03-19 20:26:09 +00:00
Dave Richer
f35ea026b8 - Hasura Migration Changes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-19 16:25:21 -04:00
Patrick Fic
b1fc2828c8 Merge branch 'feature/IO-2682-Hasura-Migrations-For-Tasks' into release/2024-03-22 2024-03-18 15:51:14 -07:00
Patrick Fic
bc25c23982 Resolve event env var. 2024-03-18 15:41:32 -07:00
Dave Richer
2215c8439e - Hasura Migration Changes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-15 16:12:27 -04:00
55 changed files with 1324 additions and 463 deletions

View File

@@ -134,10 +134,6 @@ jobs:
workflows: workflows:
deploy_and_build: deploy_and_build:
jobs: jobs:
- api-deploy:
filters:
branches:
only: master
- app-build: - app-build:
filters: filters:
branches: branches:

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1"> <babeledit_project be_version="2.7.1" version="1.2">
<!-- <!--
BabelEdit project file BabelEdit project file
@@ -3540,6 +3540,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>nobilllines</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>noneselected</name> <name>noneselected</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -3624,6 +3645,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>returnfrombill</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>savewithdiscrepancy</name> <name>savewithdiscrepancy</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -13786,6 +13828,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>unavailable</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -14476,6 +14539,27 @@
<folder_node> <folder_node>
<name>titles</name> <name>titles</name>
<children> <children>
<concept_node>
<name>joblifecycle</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>labhours</name> <name>labhours</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -18101,6 +18185,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>media</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>message</name> <name>message</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -19992,6 +20097,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>human_readable</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>percentage</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>relative_end</name> <name>relative_end</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20055,6 +20202,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>status</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>status_count</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>value</name> <name>value</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20081,6 +20270,27 @@
<folder_node> <folder_node>
<name>content</name> <name>content</name>
<children> <children>
<concept_node>
<name>calculated_based_on</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>current_status_accumulated_time</name> <name>current_status_accumulated_time</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20123,6 +20333,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>joblifecycle</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>jobs_in_since</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>legend_title</name> <name>legend_title</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -20319,6 +20571,53 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>titles</name>
<children>
<concept_node>
<name>dashboard</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>top_durations</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>
</children> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>
@@ -20531,6 +20830,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>hint</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>payer</name> <name>payer</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -31013,6 +31333,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>labor_hrs</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>labor_rates_subtotal</name> <name>labor_rates_subtotal</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -39283,6 +39624,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>paymentupdate</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>stripe</name> <name>stripe</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -45374,6 +45736,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>job_lifecycle_date_detail</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>job_lifecycle_date_summary</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>jobs_completed_not_invoiced</name> <name>jobs_completed_not_invoiced</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -1,5 +1,5 @@
import { useMutation, useQuery } from "@apollo/client"; import { useMutation, useQuery } from "@apollo/client";
import { Button, Form, PageHeader, Popconfirm, Space } from "antd"; import { Button, Divider, Form, PageHeader, Popconfirm, Space } from "antd";
import moment from "moment"; import moment from "moment";
import queryString from "query-string"; import queryString from "query-string";
import React, { useState } from "react"; import React, { useState } from "react";
@@ -208,7 +208,7 @@ export function BillDetailEditcontainer({
layout="vertical" layout="vertical"
> >
<BillFormContainer form={form} billEdit disabled={exported} /> <BillFormContainer form={form} billEdit disabled={exported} />
<Divider orientation="left">{t("general.labels.media")}</Divider>
{bodyshop.uselocalmediaserver ? ( {bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery <JobsDocumentsLocalGallery
job={{ id: data ? data.bills_by_pk.jobid : null }} job={{ id: data ? data.bills_by_pk.jobid : null }}

View File

@@ -45,6 +45,7 @@ export function BillDetailEditReturn({
actions: {}, actions: {},
context: { context: {
jobId: data.bills_by_pk.jobid, jobId: data.bills_by_pk.jobid,
job: data.bills_by_pk.job,
vendorId: data.bills_by_pk.vendorid, vendorId: data.bills_by_pk.vendorid,
returnFromBill: data.bills_by_pk.id, returnFromBill: data.bills_by_pk.id,
invoiceNumber: data.bills_by_pk.invoice_number, invoiceNumber: data.bills_by_pk.invoice_number,
@@ -173,7 +174,11 @@ export function BillDetailEditReturn({
</Form> </Form>
</Modal> </Modal>
<Button <Button
disabled={data.bills_by_pk.is_credit_memo || disabled} disabled={
data.bills_by_pk.is_credit_memo ||
data.bills_by_pk.isinhouse ||
disabled
}
onClick={() => { onClick={() => {
setVisible(true); setVisible(true);
}} }}

View File

@@ -504,10 +504,11 @@ export function BillFormComponent({
billEdit={billEdit} billEdit={billEdit}
/> />
)} )}
<Divider orientation="left" style={{ display: billEdit ? "none" : null }}>
{t("documents.labels.upload")}
</Divider>
<Form.Item <Form.Item
name="upload" name="upload"
label="Upload"
style={{ display: billEdit ? "none" : null }} style={{ display: billEdit ? "none" : null }}
valuePropName="fileList" valuePropName="fileList"
getValueFromEvent={(e) => { getValueFromEvent={(e) => {

View File

@@ -60,7 +60,7 @@ export function BillsListTableComponent({
)} )}
<BillDeleteButton bill={record} jobid={job.id} /> <BillDeleteButton bill={record} jobid={job.id} />
<BillDetailEditReturnComponent <BillDetailEditReturnComponent
data={{ bills_by_pk: { ...record, jobid: job.id } }} data={{ bills_by_pk: { ...record, jobid: job.id, job: job } }}
disabled={ disabled={
record.is_credit_memo || record.is_credit_memo ||
record.vendorid === bodyshop.inhousevendorid || record.vendorid === bodyshop.inhousevendorid ||

View File

@@ -1,8 +1,8 @@
import React, { useState } from "react";
import { Button, Form, InputNumber, Popover } from "antd";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { useTranslation } from "react-i18next";
import { CalculatorFilled } from "@ant-design/icons"; import { CalculatorFilled } from "@ant-design/icons";
import { Button, Form, InputNumber, Popover, Space } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
export default function CABCpvrtCalculator({ disabled, form }) { export default function CABCpvrtCalculator({ disabled, form }) {
const [visibility, setVisibility] = useState(false); const [visibility, setVisibility] = useState(false);
@@ -26,10 +26,14 @@ export default function CABCpvrtCalculator({ disabled, form }) {
<Form.Item name="days" label={t("jobs.labels.ca_bc_pvrt.days")}> <Form.Item name="days" label={t("jobs.labels.ca_bc_pvrt.days")}>
<InputNumber precision={0} min={0} /> <InputNumber precision={0} min={0} />
</Form.Item> </Form.Item>
<Button type="primary" htmlType="submit"> <div style={{ display: "flex", justifyContent: "flex-end" }}>
{t("general.actions.calculate")} <Space>
</Button> <Button type="primary" htmlType="submit">
<Button onClick={() => setVisibility(false)}>Close</Button> {t("general.actions.calculate")}
</Button>
<Button onClick={() => setVisibility(false)}>Close</Button>
</Space>
</div>
</Form> </Form>
</div> </div>
); );

View File

@@ -13,7 +13,6 @@ import {
notification, notification,
} from "antd"; } from "antd";
import axios from "axios"; import axios from "axios";
import moment from "moment";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -22,7 +21,6 @@ import {
INSERT_PAYMENT_RESPONSE, INSERT_PAYMENT_RESPONSE,
QUERY_RO_AND_OWNER_BY_JOB_PKS, QUERY_RO_AND_OWNER_BY_JOB_PKS,
} from "../../graphql/payment_response.queries"; } from "../../graphql/payment_response.queries";
import { INSERT_NEW_PAYMENT } from "../../graphql/payments.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors"; import { selectCardPayment } from "../../redux/modals/modals.selectors";
@@ -48,12 +46,12 @@ const CardPaymentModalComponent = ({
toggleModalVisible, toggleModalVisible,
insertAuditTrail, insertAuditTrail,
}) => { }) => {
const { context } = cardPaymentModal; const { context, actions } = cardPaymentModal;
const [form] = Form.useForm(); const [form] = Form.useForm();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT); // const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE); const [insertPaymentResponse] = useMutation(INSERT_PAYMENT_RESPONSE);
const { t } = useTranslation(); const { t } = useTranslation();
@@ -65,7 +63,6 @@ const CardPaymentModalComponent = ({
} }
); );
console.log("🚀 ~ file: card-payment-modal.component..jsx:61 ~ data:", data);
//Initialize the intellipay window. //Initialize the intellipay window.
const SetIntellipayCallbackFunctions = () => { const SetIntellipayCallbackFunctions = () => {
console.log("*** Set IntelliPay callback functions."); console.log("*** Set IntelliPay callback functions.");
@@ -74,16 +71,20 @@ const CardPaymentModalComponent = ({
}); });
window.intellipay.runOnApproval(async function (response) { window.intellipay.runOnApproval(async function (response) {
console.warn("*** Running On Approval Script ***"); //2024-04-25: Nothing is going to happen here anymore. We'll completely rely on the callback.
form.setFieldValue("paymentResponse", response); //Add a slight delay to allow the refetch to properly get the data.
form.submit(); setTimeout(() => {
if (actions && actions.refetch && typeof actions.refetch === "function")
actions.refetch();
setLoading(false);
toggleModalVisible();
}, 750);
}); });
window.intellipay.runOnNonApproval(async function (response) { window.intellipay.runOnNonApproval(async function (response) {
// Mutate unsuccessful payment // Mutate unsuccessful payment
const { payments } = form.getFieldsValue(); const { payments } = form.getFieldsValue();
await insertPaymentResponse({ await insertPaymentResponse({
variables: { variables: {
paymentResponse: payments.map((payment) => ({ paymentResponse: payments.map((payment) => ({
@@ -108,50 +109,9 @@ const CardPaymentModalComponent = ({
}); });
}; };
const handleFinish = async (values) => {
try {
await insertPayment({
variables: {
paymentInput: values.payments.map((payment) => ({
amount: payment.amount,
transactionid: (values.paymentResponse.paymentid || "").toString(),
payer: t("payments.labels.customer"),
type: values.paymentResponse.cardbrand,
jobid: payment.jobid,
date: moment(Date.now()),
payment_responses: {
data: [
{
amount: payment.amount,
bodyshopid: bodyshop.id,
jobid: payment.jobid,
declinereason: values.paymentResponse.declinereason,
ext_paymentid: values.paymentResponse.paymentid.toString(),
successful: true,
response: values.paymentResponse,
},
],
},
})),
},
refetchQueries: ["GET_JOB_BY_PK"],
});
toggleModalVisible();
} catch (error) {
console.error(error);
notification.open({
type: "error",
message: t("payments.errors.inserting", { error: error.message }),
});
} finally {
setLoading(false);
}
};
const handleIntelliPayCharge = async () => { const handleIntelliPayCharge = async () => {
setLoading(true); setLoading(true);
//Validate //Validate
try { try {
await form.validateFields(); await form.validateFields();
@@ -164,6 +124,7 @@ const CardPaymentModalComponent = ({
const response = await axios.post("/intellipay/lightbox_credentials", { const response = await axios.post("/intellipay/lightbox_credentials", {
bodyshop, bodyshop,
refresh: !!window.intellipay, refresh: !!window.intellipay,
paymentSplitMeta: form.getFieldsValue(),
}); });
if (window.intellipay) { if (window.intellipay) {
@@ -192,7 +153,6 @@ const CardPaymentModalComponent = ({
<Card title="Card Payment"> <Card title="Card Payment">
<Spin spinning={loading}> <Spin spinning={loading}>
<Form <Form
onFinish={handleFinish}
form={form} form={form}
layout="vertical" layout="vertical"
initialValues={{ initialValues={{
@@ -273,23 +233,14 @@ const CardPaymentModalComponent = ({
} }
> >
{() => { {() => {
console.log("Updating the owner info section.");
//If all of the job ids have been fileld in, then query and update the IP field. //If all of the job ids have been fileld in, then query and update the IP field.
const { payments } = form.getFieldsValue(); const { payments } = form.getFieldsValue();
if ( if (
payments?.length > 0 && payments?.length > 0 &&
payments?.filter((p) => p?.jobid).length === payments?.length payments?.filter((p) => p?.jobid).length === payments?.length
) { ) {
console.log("**Calling refetch.");
refetch({ jobids: payments.map((p) => p.jobid) }); refetch({ jobids: payments.map((p) => p.jobid) });
} }
console.log(
"Acc info",
data,
payments && data && data.jobs.length > 0
? data.jobs.map((j) => j.ro_number).join(", ")
: null
);
return ( return (
<> <>
<Input <Input
@@ -344,6 +295,13 @@ const CardPaymentModalComponent = ({
value={totalAmountToCharge?.toFixed(2)} value={totalAmountToCharge?.toFixed(2)}
hidden hidden
/> />
<Input
className="ipayfield"
data-ipayname="comment"
//type="hidden"
value={btoa(JSON.stringify(payments))}
hidden
/>
<Button <Button
type="primary" type="primary"
// data-ipayname="submit" // data-ipayname="submit"
@@ -358,11 +316,6 @@ const CardPaymentModalComponent = ({
); );
}} }}
</Form.Item> </Form.Item>
{/* Lightbox payment response when it is completed */}
<Form.Item name="paymentResponse" hidden>
<Input type="hidden" />
</Form.Item>
</Form> </Form>
</Spin> </Spin>
</Card> </Card>

View File

@@ -10,11 +10,15 @@ import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component"; import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-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 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 CurrencyInput from "../form-items-formatted/currency-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
export default function CourtesyCarCreateFormComponent({ form, saveLoading }) { export default function CourtesyCarCreateFormComponent({
form,
saveLoading,
newCC,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const client = useApolloClient(); const client = useApolloClient();
@@ -33,7 +37,7 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
} }
/> />
{/* <FormFieldsChanged form={form} /> */} {newCC ? null : <FormFieldsChanged form={form} />}
<LayoutFormRow header={t("courtesycars.labels.vehicle")}> <LayoutFormRow header={t("courtesycars.labels.vehicle")}>
<Form.Item <Form.Item
label={t("courtesycars.fields.year")} label={t("courtesycars.fields.year")}

View File

@@ -37,6 +37,9 @@ const CourtesyCarStatusComponent = ({ value, onChange }, ref) => {
<Option value="courtesycars.status.leasereturn"> <Option value="courtesycars.status.leasereturn">
{t("courtesycars.status.leasereturn")} {t("courtesycars.status.leasereturn")}
</Option> </Option>
<Option value="courtesycars.status.unavailable">
{t("courtesycars.status.unavailable")}
</Option>
</Select> </Select>
); );
}; };

View File

@@ -61,6 +61,10 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
text: t("courtesycars.status.in"), text: t("courtesycars.status.in"),
value: "courtesycars.status.in", value: "courtesycars.status.in",
}, },
{
text: t("courtesycars.status.inservice"),
value: "courtesycars.status.inservice",
},
{ {
text: t("courtesycars.status.out"), text: t("courtesycars.status.out"),
value: "courtesycars.status.out", value: "courtesycars.status.out",
@@ -73,8 +77,12 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
text: t("courtesycars.status.leasereturn"), text: t("courtesycars.status.leasereturn"),
value: "courtesycars.status.leasereturn", value: "courtesycars.status.leasereturn",
}, },
{
text: t("courtesycars.status.unavailable"),
value: "courtesycars.status.unavailable",
},
], ],
onFilter: (value, record) => value.includes(record.status), onFilter: (value, record) => record.status === value,
sortOrder: sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order, state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
@@ -178,7 +186,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
title: t("courtesycars.fields.fuel"), title: t("courtesycars.fields.fuel"),
dataIndex: "fuel", dataIndex: "fuel",
key: "fuel", key: "fuel",
sorter: (a, b) => alphaSort(a.fuel, b.fuel), sorter: (a, b) => a.fuel - b.fuel,
sortOrder: sortOrder:
state.sortedInfo.columnKey === "fuel" && state.sortedInfo.order, state.sortedInfo.columnKey === "fuel" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
@@ -187,12 +195,14 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
return t("courtesycars.labels.fuel.full"); return t("courtesycars.labels.fuel.full");
case 88: case 88:
return t("courtesycars.labels.fuel.78"); return t("courtesycars.labels.fuel.78");
case 75:
return t("courtesycars.labels.fuel.34");
case 63: case 63:
return t("courtesycars.labels.fuel.58"); return t("courtesycars.labels.fuel.58");
case 50: case 50:
return t("courtesycars.labels.fuel.12"); return t("courtesycars.labels.fuel.12");
case 38: case 38:
return t("courtesycars.labels.fuel.34"); return t("courtesycars.labels.fuel.38");
case 25: case 25:
return t("courtesycars.labels.fuel.14"); return t("courtesycars.labels.fuel.14");
case 13: case 13:

View File

@@ -19,7 +19,7 @@ export default function JobLifecycleDashboardComponent({data, bodyshop, ...cardP
setLoading(true); setLoading(true);
const response = await axios.post("/job/lifecycle", { const response = await axios.post("/job/lifecycle", {
jobids: data.job_lifecycle.map(x => x.id), jobids: data.job_lifecycle.map(x => x.id),
statuses: bodyshop.md_order_statuses statuses: bodyshop.md_ro_statuses
}); });
setLifecycleData(response.data.durations); setLifecycleData(response.data.durations);
setLoading(false); setLoading(false);

View File

@@ -3,16 +3,13 @@ import axios from "axios";
import _ from "lodash"; import _ from "lodash";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useHistory } from "react-router-dom"; import { Link } from "react-router-dom";
import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay, { import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearchOs() { export default function GlobalSearchOs() {
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [data, setData] = useState(false); const [data, setData] = useState(false);
@@ -21,7 +18,7 @@ export default function GlobalSearchOs() {
try { try {
setLoading(true); setLoading(true);
const searchData = await axios.post("/search", { const searchData = await axios.post("/search", {
search: v, search: v
}); });
const resultsByType = { const resultsByType = {
@@ -29,7 +26,7 @@ export default function GlobalSearchOs() {
jobs: [], jobs: [],
bills: [], bills: [],
owners: [], owners: [],
vehicles: [], vehicles: []
}; };
searchData.data.hits.hits.forEach((hit) => { searchData.data.hits.hits.forEach((hit) => {
@@ -50,16 +47,14 @@ export default function GlobalSearchOs() {
<span> <span>
<OwnerNameDisplay ownerObject={job} /> <OwnerNameDisplay ownerObject={job} />
</span> </span>
<span>{`${job.v_model_yr || ""} ${ <span>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`}</span>
job.v_make_desc || ""
} ${job.v_model_desc || ""}`}</span>
<span>{`${job.clm_no || ""}`}</span> <span>{`${job.clm_no || ""}`}</span>
<span>{`${job.plate_no || ""}`}</span> <span>{`${job.plate_no || ""}`}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.owners")), label: renderTitle(t("menus.header.search.owners")),
@@ -69,53 +64,39 @@ export default function GlobalSearchOs() {
value: OwnerNameDisplayFunction(owner), value: OwnerNameDisplayFunction(owner),
label: ( label: (
<Link to={`/manage/owners/${owner.id}`}> <Link to={`/manage/owners/${owner.id}`}>
<Space <Space size="small" split={<Divider type="vertical" />} wrap>
size="small"
split={<Divider type="vertical" />}
wrap
>
<span> <span>
<OwnerNameDisplay ownerObject={owner} /> <OwnerNameDisplay ownerObject={owner} />
</span> </span>
<PhoneNumberFormatter> <PhoneNumberFormatter>{owner.ownr_ph1}</PhoneNumberFormatter>
{owner.ownr_ph1} <PhoneNumberFormatter>{owner.ownr_ph2}</PhoneNumberFormatter>
</PhoneNumberFormatter>
<PhoneNumberFormatter>
{owner.ownr_ph2}
</PhoneNumberFormatter>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.vehicles")), label: renderTitle(t("menus.header.search.vehicles")),
options: resultsByType.vehicles.map((vehicle) => { options: resultsByType.vehicles.map((vehicle) => {
return { return {
key: vehicle.id, key: vehicle.id,
value: `${vehicle.v_model_yr || ""} ${ value: `${vehicle.v_model_yr || ""} ${vehicle.v_make_desc || ""} ${vehicle.v_model_desc || ""}`,
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`,
label: ( label: (
<Link to={`/manage/vehicles/${vehicle.id}`}> <Link to={`/manage/vehicles/${vehicle.id}`}>
<Space size="small" split={<Divider type="vertical" />}> <Space size="small" split={<Divider type="vertical" />}>
<span> <span>
{`${vehicle.v_model_yr || ""} ${ {`${vehicle.v_model_yr || ""} ${vehicle.v_make_desc || ""} ${vehicle.v_model_desc || ""}`}
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`}
</span> </span>
<span>{vehicle.plate_no || ""}</span> <span>{vehicle.plate_no || ""}</span>
<span> <span>
<VehicleVinDisplay> <VehicleVinDisplay>{vehicle.v_vin || ""}</VehicleVinDisplay>
{vehicle.v_vin || ""}
</VehicleVinDisplay>
</span> </span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.payments")), label: renderTitle(t("menus.header.search.payments")),
@@ -133,9 +114,9 @@ export default function GlobalSearchOs() {
<span>{payment.transactionid || ""}</span> <span>{payment.transactionid || ""}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.bills")), label: renderTitle(t("menus.header.search.bills")),
@@ -151,10 +132,10 @@ export default function GlobalSearchOs() {
<span>{bill.date}</span> <span>{bill.date}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, }
// { // {
// label: renderTitle(t("menus.header.search.phonebook")), // label: renderTitle(t("menus.header.search.phonebook")),
// options: resultsByType.search_phonebook.map((pb) => { // options: resultsByType.search_phonebook.map((pb) => {
@@ -196,15 +177,7 @@ export default function GlobalSearchOs() {
}; };
return ( return (
<AutoComplete <AutoComplete options={data} onSearch={handleSearch} defaultActiveFirstOption onClear={() => setData([])}>
options={data}
onSearch={handleSearch}
defaultActiveFirstOption
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
onClear={() => setData([])}
>
<Input.Search <Input.Search
size="large" size="large"
placeholder={t("general.labels.globalsearch")} placeholder={t("general.labels.globalsearch")}

View File

@@ -3,28 +3,19 @@ import { AutoComplete, Divider, Input, Space } from "antd";
import _ from "lodash"; import _ from "lodash";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useHistory } from "react-router-dom"; import { Link } from "react-router-dom";
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries"; import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter"; import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import OwnerNameDisplay, { import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearch() { export default function GlobalSearch() {
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory(); const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
const [callSearch, { loading, error, data }] =
useLazyQuery(GLOBAL_SEARCH_QUERY);
const executeSearch = (v) => { const executeSearch = (v) => {
if ( if (v && v.variables.search && v.variables.search !== "" && v.variables.search.length >= 3) callSearch(v);
v &&
v.variables.search &&
v.variables.search !== "" &&
v.variables.search.length >= 3
)
callSearch(v);
}; };
const debouncedExecuteSearch = _.debounce(executeSearch, 750); const debouncedExecuteSearch = _.debounce(executeSearch, 750);
@@ -53,15 +44,13 @@ export default function GlobalSearch() {
<span> <span>
<OwnerNameDisplay ownerObject={job} /> <OwnerNameDisplay ownerObject={job} />
</span> </span>
<span>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${ <span>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`}</span>
job.v_model_desc || ""
}`}</span>
<span>{`${job.clm_no || ""}`}</span> <span>{`${job.clm_no || ""}`}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.owners")), label: renderTitle(t("menus.header.search.owners")),
@@ -75,45 +64,35 @@ export default function GlobalSearch() {
<span> <span>
<OwnerNameDisplay ownerObject={owner} /> <OwnerNameDisplay ownerObject={owner} />
</span> </span>
<PhoneNumberFormatter> <PhoneNumberFormatter>{owner.ownr_ph1}</PhoneNumberFormatter>
{owner.ownr_ph1} <PhoneNumberFormatter>{owner.ownr_ph2}</PhoneNumberFormatter>
</PhoneNumberFormatter>
<PhoneNumberFormatter>
{owner.ownr_ph2}
</PhoneNumberFormatter>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.vehicles")), label: renderTitle(t("menus.header.search.vehicles")),
options: data.search_vehicles.map((vehicle) => { options: data.search_vehicles.map((vehicle) => {
return { return {
key: vehicle.id, key: vehicle.id,
value: `${vehicle.v_model_yr || ""} ${ value: `${vehicle.v_model_yr || ""} ${vehicle.v_make_desc || ""} ${vehicle.v_model_desc || ""}`,
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`,
label: ( label: (
<Link to={`/manage/vehicles/${vehicle.id}`}> <Link to={`/manage/vehicles/${vehicle.id}`}>
<Space size="small" split={<Divider type="vertical" />}> <Space size="small" split={<Divider type="vertical" />}>
<span> <span>
{`${vehicle.v_model_yr || ""} ${ {`${vehicle.v_model_yr || ""} ${vehicle.v_make_desc || ""} ${vehicle.v_model_desc || ""}`}
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`}
</span> </span>
<span>{vehicle.plate_no || ""}</span> <span>{vehicle.plate_no || ""}</span>
<span> <span>
<VehicleVinDisplay> <VehicleVinDisplay>{vehicle.v_vin || ""}</VehicleVinDisplay>
{vehicle.v_vin || ""}
</VehicleVinDisplay>
</span> </span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.payments")), label: renderTitle(t("menus.header.search.payments")),
@@ -131,9 +110,9 @@ export default function GlobalSearch() {
<span>{payment.transactionid || ""}</span> <span>{payment.transactionid || ""}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.bills")), label: renderTitle(t("menus.header.search.bills")),
@@ -149,46 +128,35 @@ export default function GlobalSearch() {
<span>{bill.date}</span> <span>{bill.date}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, },
{ {
label: renderTitle(t("menus.header.search.phonebook")), label: renderTitle(t("menus.header.search.phonebook")),
options: data.search_phonebook.map((pb) => { options: data.search_phonebook.map((pb) => {
return { return {
key: pb.id, key: pb.id,
value: `${pb.firstname || ""} ${pb.lastname || ""} ${ value: `${pb.firstname || ""} ${pb.lastname || ""} ${pb.company || ""}`,
pb.company || ""
}`,
label: ( label: (
<Link to={`/manage/phonebook?phonebookentry=${pb.id}`}> <Link to={`/manage/phonebook?phonebookentry=${pb.id}`}>
<Space size="small" split={<Divider type="vertical" />}> <Space size="small" split={<Divider type="vertical" />}>
<span>{`${pb.firstname || ""} ${pb.lastname || ""} ${ <span>{`${pb.firstname || ""} ${pb.lastname || ""} ${pb.company || ""}`}</span>
pb.company || ""
}`}</span>
<PhoneNumberFormatter>{pb.phone1}</PhoneNumberFormatter> <PhoneNumberFormatter>{pb.phone1}</PhoneNumberFormatter>
<span>{pb.email}</span> <span>{pb.email}</span>
</Space> </Space>
</Link> </Link>
), )
}; };
}), })
}, }
] ]
: []; : [];
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
return ( return (
<AutoComplete <AutoComplete options={options} onSearch={handleSearch} defaultActiveFirstOption>
options={options}
onSearch={handleSearch}
defaultActiveFirstOption
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
>
<Input.Search <Input.Search
size="large" size="large"
placeholder={t("general.labels.globalsearch")} placeholder={t("general.labels.globalsearch")}

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Col, Divider, Row, Skeleton, Space, Timeline, Typography } from "antd"; import { Col, Row, Skeleton, Timeline, Typography } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@@ -31,15 +31,27 @@ export default function JobLinesExpander({ jobline, jobid }) {
{data.parts_order_lines.length > 0 ? ( {data.parts_order_lines.length > 0 ? (
data.parts_order_lines.map((line) => ( data.parts_order_lines.map((line) => (
<Timeline.Item key={line.id}> <Timeline.Item key={line.id}>
<Space split={<Divider type="vertical" />} wrap> <Row wrap>
<Link <Col span={4}>
to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`} <Link
> to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}
{line.parts_order.order_number} >
</Link> {line.parts_order.order_number}
<DateFormatter>{line.parts_order.order_date}</DateFormatter> </Link>
{line.parts_order.vendor.name} </Col>
</Space> <Col span={4}>
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
</Col>
<Col span={4}>{line.parts_order.vendor.name}</Col>
{line.backordered_eta ? (
<Col span={4}>
<span>
{`${t("parts_orders.fields.backordered_eta")}: `}
<DateFormatter>{line.backordered_eta}</DateFormatter>
</span>
</Col>
) : null}
</Row>
</Timeline.Item> </Timeline.Item>
)) ))
) : ( ) : (
@@ -71,7 +83,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
</Col> </Col>
<Col span={4}> <Col span={4}>
<span> <span>
{`${t("billlines.fields.actual_cost")}: `} {`${t("billlines.fields.actual_cost")}: `}
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter> <CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
</span> </span>
</Col> </Col>
@@ -84,7 +96,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
)) ))
) : ( ) : (
<Timeline.Item> <Timeline.Item>
{t("parts_orders.labels.notyetordered")} {t("bills.labels.nobilllines")}
</Timeline.Item> </Timeline.Item>
)} )}
</Timeline> </Timeline>

View File

@@ -82,7 +82,7 @@ export default function JobReconciliationBillsTable({
state.sortedInfo.order, state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => (
<Checkbox disabled checked={record.bill.is_credit_memo} /> <Checkbox checked={record.bill.is_credit_memo} />
), ),
}, },
]; ];

View File

@@ -1,4 +1,12 @@
import { Collapse, Form, Input, InputNumber, Select, Switch } from "antd"; import {
Collapse,
Form,
Input,
InputNumber,
Select,
Space,
Switch,
} from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -10,6 +18,8 @@ import FormItemEmail from "../form-items-formatted/email-form-item.component";
import FormItemPhone, { import FormItemPhone, {
PhoneItemFormatterValidation, PhoneItemFormatterValidation,
} from "../form-items-formatted/phone-form-item.component"; } from "../form-items-formatted/phone-form-item.component";
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
import JobsDetailChangeFilehandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component"; import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
import JobsDetailRatesParts from "../jobs-detail-rates/jobs-detail-rates.parts.component"; import JobsDetailRatesParts from "../jobs-detail-rates/jobs-detail-rates.parts.component";
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component"; import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
@@ -25,6 +35,15 @@ const mapDispatchToProps = (dispatch) => ({
export function JobsCreateJobsInfo({ bodyshop, form, selected }) { export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { getFieldValue } = form; const { getFieldValue } = form;
const handleInsCoChange = (value) => {
const selectedCompany = bodyshop.md_ins_cos.find((s) => s.name === value);
if (selectedCompany) {
form.setFieldValue("ins_addr1", selectedCompany.street1);
form.setFieldValue("ins_city", selectedCompany.city);
}
};
return ( return (
<div> <div>
<Collapse defaultActiveKey="insurance"> <Collapse defaultActiveKey="insurance">
@@ -34,26 +53,20 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
forceRender forceRender
> >
<LayoutFormRow> <LayoutFormRow>
<Form.Item label={t("jobs.fields.ins_co_id")} name="ins_co_id"> <Form.Item label={t("jobs.fields.clm_no")} name="clm_no">
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no"> <Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.clm_no")} name="clm_no">
<Input />
</Form.Item>
<Form.Item <Form.Item
label={t("jobs.fields.regie_number")} label={t("jobs.fields.regie_number")}
name="regie_number" name="regie_number"
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
<FormDatePicker />
</Form.Item>
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm"> <Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
<Select> <Select onChange={handleInsCoChange}>
{bodyshop.md_ins_cos.map((s) => ( {bodyshop.md_ins_cos.map((s) => (
<Select.Option key={s.name} value={s.name}> <Select.Option key={s.name} value={s.name}>
{s.name} {s.name}
@@ -67,7 +80,15 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
<Form.Item label={t("jobs.fields.ins_city")} name="ins_city"> <Form.Item label={t("jobs.fields.ins_city")} name="ins_city">
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.ins_ct_ln")} name="ins_ct_ln"> <Form.Item
label={
<Space>
{t("jobs.fields.ins_ct_ln")}
<JobsDetailChangeFilehandler form={form} />
</Space>
}
name="ins_ct_ln"
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.ins_ct_fn")} name="ins_ct_fn"> <Form.Item label={t("jobs.fields.ins_ct_fn")} name="ins_ct_fn">
@@ -95,11 +116,24 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
> >
<FormItemEmail email={getFieldValue("ins_ea")} /> <FormItemEmail email={getFieldValue("ins_ea")} />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
<FormDatePicker />
</Form.Item>
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm"> <Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm">
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.est_ct_fn")} name="est_ct_fn"> <Form.Item
label={
<Space>
{t("jobs.fields.est_ct_fn")}
<JobsDetailChangeEstimator form={form} />
</Space>
}
name="est_ct_fn"
>
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.est_ct_ln")} name="est_ct_ln"> <Form.Item label={t("jobs.fields.est_ct_ln")} name="est_ct_ln">

View File

@@ -37,6 +37,15 @@ const lossColDamage = { sm: { span: 24 }, md: { span: 6 }, lg: { span: 4 } };
export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) { export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
const { getFieldValue } = form; const { getFieldValue } = form;
const { t } = useTranslation(); const { t } = useTranslation();
const handleInsCoChange = (value) => {
const selectedCompany = bodyshop.md_ins_cos.find((s) => s.name === value);
if (selectedCompany) {
form.setFieldValue("ins_addr1", selectedCompany.street1);
form.setFieldValue("ins_city", selectedCompany.city);
}
};
return ( return (
<div> <div>
<FormRow header={t("jobs.forms.claiminfo")}> <FormRow header={t("jobs.forms.claiminfo")}>
@@ -71,7 +80,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
</Form.Item> </Form.Item>
<Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm"> <Form.Item label={t("jobs.fields.ins_co_nm")} name="ins_co_nm">
<Select disabled={jobRO}> <Select disabled={jobRO} onChange={handleInsCoChange}>
{bodyshop.md_ins_cos.map((s) => ( {bodyshop.md_ins_cos.map((s) => (
<Select.Option key={s.name} value={s.name}> <Select.Option key={s.name} value={s.name}>
{s.name} {s.name}

View File

@@ -304,7 +304,7 @@ export function JobsDetailHeaderActions({
disabled={!job.converted} disabled={!job.converted}
onClick={() => { onClick={() => {
setCardPaymentContext({ setCardPaymentContext({
actions: {}, actions: { refetch },
context: { jobid: job.id }, context: { jobid: job.id },
}); });
}} }}

View File

@@ -5,6 +5,7 @@ import {
WarningFilled, WarningFilled,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Card, Col, Divider, Row, Space, Tag, Tooltip } from "antd"; import { Card, Col, Divider, Row, Space, Tag, Tooltip } from "antd";
import moment from "moment";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -26,7 +27,6 @@ import ProductionListColumnComment from "../production-list-columns/production-l
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component"; import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
import "./jobs-detail-header.styles.scss"; import "./jobs-detail-header.styles.scss";
import moment from "moment";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
@@ -101,7 +101,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
{job.status === bodyshop.md_ro_statuses.default_scheduled && {job.status === bodyshop.md_ro_statuses.default_scheduled &&
job.scheduled_in ? ( job.scheduled_in ? (
<Tag> <Tag>
<Link to={`/manage/schedule?date=${moment(job.scheduled_in).format('YYYY-MM-DD')}`}> <Link
to={`/manage/schedule?date=${moment(
job.scheduled_in
).format("YYYY-MM-DD")}`}
>
<DateTimeFormatter>{job.scheduled_in}</DateTimeFormatter> <DateTimeFormatter>{job.scheduled_in}</DateTimeFormatter>
</Link> </Link>
</Tag> </Tag>
@@ -221,6 +225,12 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
{job.owner?.tax_number || ""} {job.owner?.tax_number || ""}
</DataLabel> </DataLabel>
)} )}
<DataLabel
label={t("owners.fields.note")}
valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}
>
{job.owner?.note || ""}
</DataLabel>
</div> </div>
</Card> </Card>
</Col> </Col>

View File

@@ -6,7 +6,8 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, statusSort } from "../../utils/sorters"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component"; import OwnerDetailUpdateJobsComponent from "../owner-detail-update-jobs/owner-detail-update-jobs.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -86,7 +87,18 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
})), })),
onFilter: (value, record) => value.includes(record.status), onFilter: (value, record) => value.includes(record.status),
}, },
{
title: t("jobs.fields.actual_completion"),
dataIndex: "actual_completion",
key: "actual_completion",
render: (text, record) => (
<DateTimeFormatter>{record.actual_completion}</DateTimeFormatter>
),
sorter: (a, b) => dateSort(a.actual_completion, b.actual_completion),
sortOrder:
state.sortedInfo.columnKey === "actual_completion" &&
state.sortedInfo.order,
},
{ {
title: t("jobs.fields.clm_total"), title: t("jobs.fields.clm_total"),
dataIndex: "clm_total", dataIndex: "clm_total",

View File

@@ -1,5 +1,5 @@
import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons"; import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useLazyQuery, useMutation } from "@apollo/client";
import { import {
Button, Button,
Card, Card,
@@ -13,20 +13,21 @@ import {
Table, Table,
} from "antd"; } from "antd";
import queryString from "query-string"; import queryString from "query-string";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { QUERY_BILL_BY_PK } from "../../graphql/bills.queries";
import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries"; import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component"; import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component"; import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component";
@@ -78,19 +79,46 @@ export function PartsOrderListTableComponent({
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
}); });
const [returnfrombill, setReturnFromBill] = useState();
const [billData, setBillData] = useState();
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const selectedpartsorder = search.partsorderid; const selectedpartsorder = search.partsorderid;
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER); const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : []; const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
const { refetch } = billsQuery; const { refetch } = billsQuery;
useEffect(() => {
if (returnfrombill === null) {
setBillData(null);
} else {
const fetchData = async () => {
const result = await billQuery({
variables: { billid: returnfrombill },
});
setBillData(result.data);
};
fetchData();
}
}, [returnfrombill, billQuery]);
const recordActions = (record, showView = false) => ( const recordActions = (record, showView = false) => (
<Space wrap> <Space wrap>
{showView && ( {showView && (
<Button onClick={() => handleOnRowClick(record)}> <Button
onClick={() => {
if (record.returnfrombill) {
setReturnFromBill(record.returnfrombill);
} else {
setReturnFromBill(null);
}
handleOnRowClick(record);
}}
>
<EyeFilled /> <EyeFilled />
</Button> </Button>
)} )}
@@ -166,7 +194,7 @@ export function PartsOrderListTableComponent({
is_credit_memo: record.return, is_credit_memo: record.return,
billlines: record.parts_order_lines.map((pol) => { billlines: record.parts_order_lines.map((pol) => {
return { return {
joblineid: pol.job_line_id, joblineid: pol.job_line_id || "noline",
line_desc: pol.line_desc, line_desc: pol.line_desc,
quantity: pol.quantity, quantity: pol.quantity,
@@ -417,7 +445,11 @@ export function PartsOrderListTableComponent({
return ( return (
<div> <div>
<PageHeader <PageHeader
title={record && `${record.vendor.name} - ${record.order_number}`} title={
billData
? `${record.vendor.name} - ${record.order_number} - ${t("bills.labels.returnfrombill")}: ${billData.bills_by_pk.invoice_number}`
: `${record.vendor.name} - ${record.order_number}`
}
extra={recordActions(record)} extra={recordActions(record)}
/> />
<Table <Table

View File

@@ -1,17 +1,17 @@
import { DeleteFilled, WarningFilled, DownOutlined } from "@ant-design/icons"; import { DeleteFilled, DownOutlined, WarningFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useTreatments } from "@splitsoftware/splitio-react";
import { import {
Checkbox,
Divider, Divider,
Dropdown,
Form, Form,
Input, Input,
InputNumber, InputNumber,
Menu,
Radio, Radio,
Select,
Space, Space,
Tag, Tag,
Select,
Menu,
Dropdown,
Checkbox,
} from "antd"; } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -255,7 +255,7 @@ export function PartsOrderModalComponent({
}, },
]} ]}
> >
<InputNumber /> <InputNumber min={1} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("parts_orders.fields.act_price")} label={t("parts_orders.fields.act_price")}

View File

@@ -139,8 +139,8 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
contentStyle={{ fontWeight: "600" }} contentStyle={{ fontWeight: "600" }}
column={4} column={4}
> >
<Descriptions.Item label={t("job_payments.titles.payer")}> <Descriptions.Item label={t("job_payments.titles.hint")}>
{record.payer} {payment_response?.response?.methodhint}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.payername")}> <Descriptions.Item label={t("job_payments.titles.payername")}>
{payment_response?.response?.nameOnCard ?? ""} {payment_response?.response?.nameOnCard ?? ""}
@@ -155,7 +155,7 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
{record.transactionid} {record.transactionid}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymentid")}> <Descriptions.Item label={t("job_payments.titles.paymentid")}>
{payment_response?.response?.paymentreferenceid ?? ""} {payment_response?.ext_paymentid ?? ""}
</Descriptions.Item> </Descriptions.Item>
<Descriptions.Item label={t("job_payments.titles.paymenttype")}> <Descriptions.Item label={t("job_payments.titles.paymenttype")}>
{record.type} {record.type}

View File

@@ -1,16 +1,18 @@
import React from "react";
import { Button, notification } from "antd";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectPayment } from "../../redux/modals/modals.selectors";
import { selectCurrentUser } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
paymentModal: selectPayment,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -24,6 +26,7 @@ const PaymentMarkForExportButton = ({
refetch, refetch,
setPaymentContext, setPaymentContext,
currentUser, currentUser,
paymentModal,
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [insertExportLog, { loading: exportLogLoading }] = const [insertExportLog, { loading: exportLogLoading }] =
@@ -46,7 +49,6 @@ const PaymentMarkForExportButton = ({
], ],
}, },
}); });
const paymentUpdateResponse = await updatePayment({ const paymentUpdateResponse = await updatePayment({
variables: { variables: {
paymentId: payment.id, paymentId: payment.id,
@@ -55,25 +57,33 @@ const PaymentMarkForExportButton = ({
}, },
}, },
}); });
if (!!!paymentUpdateResponse.errors) { if (!!!paymentUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "paymentsuccessmarkforexport", key: "paymentsuccessmarkforexport",
message: t("payments.successes.markexported"), message: t("payments.successes.markexported"),
}); });
if (refetch) refetch();
setPaymentContext({ setPaymentContext({
actions: { actions: {
refetch, refetch,
}, },
context: { context: {
...paymentModal.context,
...payment, ...payment,
exportedat: today, exportedat: today,
}, },
}); });
if (refetch) {
if (paymentModal.context.refetchRequiresContext) {
refetch(
paymentUpdateResponse &&
paymentUpdateResponse.data.update_payments.returning[0]
);
} else {
refetch();
}
}
} else { } else {
notification["error"]({ notification["error"]({
message: t("payments.errors.exporting", { message: t("payments.errors.exporting", {

View File

@@ -1,5 +1,4 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Form, Modal, notification, Space } from "antd"; import { Button, Form, Modal, notification, Space } from "antd";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -19,8 +18,8 @@ import {
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import PaymentForm from "../payment-form/payment-form.component"; import PaymentForm from "../payment-form/payment-form.component";
import PaymentReexportButton from "../payment-reexport-button/payment-reexport-button.component";
import PaymentMarkForExportButton from "../payment-mark-export-button/payment-mark-export-button-component"; import PaymentMarkForExportButton from "../payment-mark-export-button/payment-mark-export-button-component";
import PaymentReexportButton from "../payment-reexport-button/payment-reexport-button.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
paymentModal: selectPayment, paymentModal: selectPayment,
@@ -97,16 +96,21 @@ function PaymentModalContainer({
}); });
if (!!!updatedPayment.errors) { if (!!!updatedPayment.errors) {
notification["success"]({ message: t("payments.successes.payment") }); notification["success"]({ message: t("payments.successes.paymentupdate") });
} else { } else {
notification["error"]({ message: t("payments.errors.payment") }); notification["error"]({ message: t("payments.errors.paymentupdate") });
} }
} }
if (actions.refetch) if (actions.refetch) {
actions.refetch( if (context.refetchRequiresContext) {
updatedPayment && updatedPayment.data.update_payments.returning[0] actions.refetch(
); updatedPayment && updatedPayment.data.update_payments.returning[0]
);
} else {
actions.refetch();
}
}
if (enterAgain) { if (enterAgain) {
const prev = form.getFieldsValue(["date"]); const prev = form.getFieldsValue(["date"]);
@@ -159,7 +163,7 @@ function PaymentModalContainer({
}} }}
afterClose={() => form.resetFields()} afterClose={() => form.resetFields()}
footer={ footer={
<span> <Space>
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button> <Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
<Button loading={loading} onClick={() => form.submit()}> <Button loading={loading} onClick={() => form.submit()}>
{t("general.actions.save")} {t("general.actions.save")}
@@ -175,7 +179,7 @@ function PaymentModalContainer({
{t("general.actions.saveandnew")} {t("general.actions.saveandnew")}
</Button> </Button>
)} )}
</span> </Space>
} }
> >
{!context || (context && !context.id) ? null : ( {!context || (context && !context.id) ? null : (
@@ -194,7 +198,6 @@ function PaymentModalContainer({
autoComplete={"off"} autoComplete={"off"}
form={form} form={form}
layout="vertical" layout="vertical"
initialValues={context || {}}
disabled={context?.exportedat} disabled={context?.exportedat}
> >
<PaymentForm form={form} /> <PaymentForm form={form} />

View File

@@ -1,17 +1,27 @@
import React from "react";
import { Button, notification } from "antd";
import { useTranslation } from "react-i18next";
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { setModalContext } from "../../redux/modals/modals.actions"; import { Button, notification } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectPayment } from "../../redux/modals/modals.selectors";
const mapStateToProps = createStructuredSelector({
paymentModal: selectPayment,
});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPaymentContext: (context) => setPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "payment" })), dispatch(setModalContext({ context: context, modal: "payment" })),
}); });
const PaymentReexportButton = ({ payment, refetch, setPaymentContext }) => { const PaymentReexportButton = ({
paymentModal,
payment,
refetch,
setPaymentContext,
}) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [updatePayment, { loading }] = useMutation(UPDATE_PAYMENT); const [updatePayment, { loading }] = useMutation(UPDATE_PAYMENT);
@@ -24,25 +34,32 @@ const PaymentReexportButton = ({ payment, refetch, setPaymentContext }) => {
}, },
}, },
}); });
if (!!!paymentUpdateResponse.errors) { if (!!!paymentUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "paymentsuccessexport", key: "paymentsuccessexport",
message: t("payments.successes.markreexported"), message: t("payments.successes.markreexported"),
}); });
if (refetch) refetch();
setPaymentContext({ setPaymentContext({
actions: { actions: {
refetch, refetch,
}, },
context: { context: {
...paymentModal.context,
...payment, ...payment,
exportedat: null, exportedat: null,
}, },
}); });
if (refetch) {
if (paymentModal.context.refetchRequiresContext) {
refetch(
paymentUpdateResponse &&
paymentUpdateResponse.data.update_payments.returning[0]
);
} else {
refetch();
}
}
} else { } else {
notification["error"]({ notification["error"]({
message: t("payments.errors.exporting", { message: t("payments.errors.exporting", {
@@ -63,4 +80,7 @@ const PaymentReexportButton = ({ payment, refetch, setPaymentContext }) => {
); );
}; };
export default connect(null, mapDispatchToProps)(PaymentReexportButton); export default connect(
mapStateToProps,
mapDispatchToProps
)(PaymentReexportButton);

View File

@@ -14,11 +14,11 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter"; import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { pageLimit } from "../../utils/config";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container"; import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -184,7 +184,10 @@ export function PaymentsListPaginated({
} }
: refetch, : refetch,
}, },
context: apolloResults ? apolloResults : record, context: {
...(apolloResults ? apolloResults : record),
refetchRequiresContext: true,
},
}); });
}} }}
> >

View File

@@ -6,6 +6,7 @@ import {
notification, notification,
Popover, Popover,
Radio, Radio,
Space,
} from "antd"; } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -115,10 +116,16 @@ export function PrintCenterJobsLabels({ bodyshop, jobId }) {
> >
<InputNumber min={1} precision={0} max={99} /> <InputNumber min={1} precision={0} max={99} />
</Form.Item> </Form.Item>
<Button type="primary" loading={loading} onClick={handleOk}> <div style={{ display: "flex", justifyContent: "flex-end" }}>
{t("general.actions.print")} <Space>
</Button> <Button type="primary" loading={loading} onClick={handleOk}>
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button> {t("general.actions.print")}
</Button>
<Button onClick={handleCancel}>
{t("general.actions.cancel")}
</Button>
</Space>
</div>
</Form> </Form>
</Card> </Card>
); );

View File

@@ -18,8 +18,8 @@ const sortByParentId = (arr) => {
//console.log("sortByParentId -> byParentsIdsList", byParentsIdsList); //console.log("sortByParentId -> byParentsIdsList", byParentsIdsList);
while (byParentsIdsList[parentId]) { while (byParentsIdsList[parentId]) {
sortedList.push(byParentsIdsList[parentId][0]); sortedList.push(...byParentsIdsList[parentId]); //Spread in the whole list in case several items have the same parents.
parentId = byParentsIdsList[parentId][0].id; parentId = byParentsIdsList[parentId][byParentsIdsList[parentId].length -1].id; //Grab the ID from the last one.
} }
if (byParentsIdsList["null"]) if (byParentsIdsList["null"])

View File

@@ -302,7 +302,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
onFilter: (value, record) => onFilter: (value, record) =>
value.includes(record.special_coverage_policy), value.includes(record.special_coverage_policy),
render: (text, record) => ( render: (text, record) => (
<Checkbox disabled checked={record.special_coverage_policy} /> <Checkbox checked={record.special_coverage_policy} />
), ),
}, },

View File

@@ -1,5 +1,5 @@
import Icon from "@ant-design/icons"; import Icon from "@ant-design/icons";
import { Popover } from "antd"; import { Popover, Space } from "antd";
import _ from "lodash"; import _ from "lodash";
import moment from "moment"; import moment from "moment";
import React, { useMemo } from "react"; import React, { useMemo } from "react";
@@ -69,19 +69,22 @@ export function ScheduleCalendarHeaderComponent({
{loadData && loadData.allJobsOut ? ( {loadData && loadData.allJobsOut ? (
loadData.allJobsOut.map((j) => ( loadData.allJobsOut.map((j) => (
<tr key={j.id}> <tr key={j.id}>
<td> <td style={{ padding: "2.5px" }}>
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link> <Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link> (
{j.status})
</td> </td>
<td> <td style={{ padding: "2.5px" }}>
<OwnerNameDisplay ownerObject={j} /> <OwnerNameDisplay ownerObject={j} />
</td> </td>
<td> <td style={{ padding: "2.5px" }}>
{`(${( {`(${j.labhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0}/${
j.labhrs.aggregate.sum.mod_lb_hrs + j.larhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0
j.larhrs.aggregate.sum.mod_lb_hrs }/${(
j.labhrs.aggregate?.sum?.mod_lb_hrs +
j.larhrs.aggregate?.sum?.mod_lb_hrs
).toFixed(1)} ${t("general.labels.hours")})`} ).toFixed(1)} ${t("general.labels.hours")})`}
</td> </td>
<td> <td style={{ padding: "2.5px" }}>
<DateTimeFormatter> <DateTimeFormatter>
{j.scheduled_completion} {j.scheduled_completion}
</DateTimeFormatter> </DateTimeFormatter>
@@ -90,7 +93,9 @@ export function ScheduleCalendarHeaderComponent({
)) ))
) : ( ) : (
<tr> <tr>
<td>{t("appointments.labels.nocompletingjobs")}</td> <td style={{ padding: "2.5px" }}>
{t("appointments.labels.nocompletingjobs")}
</td>
</tr> </tr>
)} )}
</tbody> </tbody>
@@ -105,27 +110,30 @@ export function ScheduleCalendarHeaderComponent({
{loadData && loadData.allJobsIn ? ( {loadData && loadData.allJobsIn ? (
loadData.allJobsIn.map((j) => ( loadData.allJobsIn.map((j) => (
<tr key={j.id}> <tr key={j.id}>
<td> <td style={{ padding: "2.5px" }}>
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link> <Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
{j.status}
</td> </td>
<td> <td style={{ padding: "2.5px" }}>
<OwnerNameDisplay ownerObject={j} /> <OwnerNameDisplay ownerObject={j} />
</td> </td>
<td> <td style={{ padding: "2.5px" }}>
{`(${( {`(${j.labhrs?.aggregate?.sum.mod_lb_hrs?.toFixed(1) || 0}/${
j.labhrs.aggregate.sum.mod_lb_hrs + j.larhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0
j.larhrs.aggregate.sum.mod_lb_hrs }/${(
j.labhrs?.aggregate?.sum?.mod_lb_hrs +
j.larhrs?.aggregate?.sum?.mod_lb_hrs
).toFixed(1)} ${t("general.labels.hours")})`} ).toFixed(1)} ${t("general.labels.hours")})`}
</td> </td>
<td> <td style={{ padding: "2.5px" }}>
<DateTimeFormatter>{j.scheduled_in}</DateTimeFormatter> <DateTimeFormatter>{j.scheduled_in}</DateTimeFormatter>
</td> </td>
</tr> </tr>
)) ))
) : ( ) : (
<tr> <tr>
<td>{t("appointments.labels.noarrivingjobs")}</td> <td style={{ padding: "2.5px" }}>
{t("appointments.labels.noarrivingjobs")}
</td>
</tr> </tr>
)} )}
</tbody> </tbody>
@@ -136,25 +144,32 @@ export function ScheduleCalendarHeaderComponent({
const LoadComponent = loadData ? ( const LoadComponent = loadData ? (
<div> <div>
<div className="imex-flex-row imex-flex-row__flex-space-around"> <div className="imex-flex-row imex-flex-row__flex-space-around">
<Popover <Space>
placement={"bottom"} <Popover
content={jobsInPopup} placement={"bottom"}
trigger="hover" content={jobsInPopup}
title={t("appointments.labels.arrivingjobs")} trigger="hover"
> title={t("appointments.labels.arrivingjobs")}
<Icon component={MdFileDownload} style={{ color: "green" }} /> >
{(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(2)} <Icon component={MdFileDownload} style={{ color: "green" }} />
</Popover> {(loadData.allHoursInBody || 0) &&
<Popover loadData.allHoursInBody.toFixed(1)}
placement={"bottom"} /
content={jobsOutPopup} {(loadData.allHoursInRefinish || 0) &&
trigger="hover" loadData.allHoursInRefinish.toFixed(1)}
title={t("appointments.labels.completingjobs")} /{(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(1)}
> </Popover>
<Icon component={MdFileUpload} style={{ color: "red" }} /> <Popover
{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(2)} placement={"bottom"}
</Popover> content={jobsOutPopup}
<ScheduleCalendarHeaderGraph loadData={loadData} /> trigger="hover"
title={t("appointments.labels.completingjobs")}
>
<Icon component={MdFileUpload} style={{ color: "red" }} />
{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(1)}
</Popover>
<ScheduleCalendarHeaderGraph loadData={loadData} />
</Space>
</div> </div>
<div> <div>
<ul style={{ listStyleType: "none", columns: "2 auto", padding: 0 }}> <ul style={{ listStyleType: "none", columns: "2 auto", padding: 0 }}>

View File

@@ -39,8 +39,23 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
form.getFieldValue(["md_ro_statuses", "statuses"]) || [] form.getFieldValue(["md_ro_statuses", "statuses"]) || []
); );
const [productionStatus, setProductionStatus] = useState(
(
form.getFieldValue(["md_ro_statuses", "production_statuses"]) || []
).concat(
form.getFieldValue(["md_ro_statuses", "additional_board_statuses"]) || []
) || []
);
const handleBlur = () => { const handleBlur = () => {
setOptions(form.getFieldValue(["md_ro_statuses", "statuses"])); setOptions(form.getFieldValue(["md_ro_statuses", "statuses"]));
setProductionStatus(
form
.getFieldValue(["md_ro_statuses", "production_statuses"])
.concat(
form.getFieldValue(["md_ro_statuses", "additional_board_statuses"])
)
);
}; };
return ( return (
@@ -346,7 +361,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
]} ]}
> >
<Select> <Select>
{options.map((item, idx) => ( {productionStatus.map((item, idx) => (
<Select.Option key={idx} value={item}> <Select.Option key={idx} value={item}>
{item} {item}
</Select.Option> </Select.Option>

View File

@@ -6,7 +6,8 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, statusSort } from "../../utils/sorters"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import OwnerNameDisplay, { import OwnerNameDisplay, {
OwnerNameDisplayFunction, OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component"; } from "../owner-name-display/owner-name-display.component";
@@ -79,7 +80,18 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
})), })),
onFilter: (value, record) => value.includes(record.status), onFilter: (value, record) => value.includes(record.status),
}, },
{
title: t("jobs.fields.actual_completion"),
dataIndex: "actual_completion",
key: "actual_completion",
render: (text, record) => (
<DateTimeFormatter>{record.actual_completion}</DateTimeFormatter>
),
sorter: (a, b) => dateSort(a.actual_completion, b.actual_completion),
sortOrder:
state.sortedInfo.columnKey === "actual_completion" &&
state.sortedInfo.order,
},
{ {
title: t("jobs.fields.clm_total"), title: t("jobs.fields.clm_total"),
dataIndex: "clm_total", dataIndex: "clm_total",

View File

@@ -73,6 +73,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
order_date order_date
deliver_by deliver_by
return return
returnfrombill
orderedby orderedby
parts_order_lines { parts_order_lines {
id id

View File

@@ -46,7 +46,7 @@ export const QUERY_AVAILABLE_CC = gql`
`; `;
export const CHECK_CC_FLEET_NUMBER = gql` export const CHECK_CC_FLEET_NUMBER = gql`
query CHECK_VENDOR_NAME($name: String!) { query CHECK_CC_FLEET_NUMBER($name: String!) {
courtesycars_aggregate(where: { fleetnumber: { _ilike: $name } }) { courtesycars_aggregate(where: { fleetnumber: { _ilike: $name } }) {
aggregate { aggregate {
count count

View File

@@ -172,6 +172,12 @@ export const UPDATE_JOB_LINE = gql`
id id
notes notes
mod_lbr_ty mod_lbr_ty
mod_lb_hrs
part_type
op_code_desc
prt_dsmk_m
prt_dsmk_p
tax_part
part_qty part_qty
db_price db_price
act_price act_price

View File

@@ -704,6 +704,7 @@ export const GET_JOB_BY_PK = gql`
other_amount_payable other_amount_payable
owner { owner {
id id
note
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_co_nm ownr_co_nm
@@ -2204,6 +2205,8 @@ export const GET_JOB_LINE_ORDERS = gql`
parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) { parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) {
id id
act_price act_price
backordered_eta
backordered_on
parts_order { parts_order {
id id
order_date order_date

View File

@@ -71,6 +71,7 @@ export const QUERY_OWNER_BY_ID = gql`
tax_number tax_number
jobs(order_by: { date_open: desc }) { jobs(order_by: { date_open: desc }) {
id id
actual_completion
ro_number ro_number
clm_no clm_no
status status

View File

@@ -30,6 +30,7 @@ export const QUERY_VEHICLE_BY_ID = gql`
notes notes
jobs(order_by: { date_open: desc }) { jobs(order_by: { date_open: desc }) {
id id
actual_completion
ro_number ro_number
ownr_co_nm ownr_co_nm
ownr_fn ownr_fn

View File

@@ -13,8 +13,8 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { pageLimit } from "../../utils/config";
import { alphaSort, dateSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
import {pageLimit} from "../../utils/config";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) => setPartsOrderContext: (context) =>
@@ -125,9 +125,7 @@ export function BillsListPage({
sortOrder: sortOrder:
state.sortedInfo.columnKey === "is_credit_memo" && state.sortedInfo.columnKey === "is_credit_memo" &&
state.sortedInfo.order, state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => <Checkbox checked={record.is_credit_memo} />,
<Checkbox disabled checked={record.is_credit_memo} />
),
}, },
{ {
title: t("bills.fields.exported"), title: t("bills.fields.exported"),
@@ -136,7 +134,7 @@ export function BillsListPage({
sorter: (a, b) => a.exported - b.exported, sorter: (a, b) => a.exported - b.exported,
sortOrder: sortOrder:
state.sortedInfo.columnKey === "exported" && state.sortedInfo.order, state.sortedInfo.columnKey === "exported" && state.sortedInfo.order,
render: (text, record) => <Checkbox disabled checked={record.exported} />, render: (text, record) => <Checkbox checked={record.exported} />,
}, },
{ {
title: t("general.labels.actions"), title: t("general.labels.actions"),
@@ -243,7 +241,7 @@ export function BillsListPage({
extra={ extra={
<Space wrap> <Space wrap>
{search.search && ( {search.search && (
<> <Space align="center">
<Typography.Title level={4}> <Typography.Title level={4}>
{t("general.labels.searchresults", { search: search.search })} {t("general.labels.searchresults", { search: search.search })}
</Typography.Title> </Typography.Title>
@@ -256,7 +254,7 @@ export function BillsListPage({
> >
{t("general.actions.clear")} {t("general.actions.clear")}
</Button> </Button>
</> </Space>
)} )}
<Button onClick={() => refetch()}> <Button onClick={() => refetch()}>
<SyncOutlined /> <SyncOutlined />

View File

@@ -76,7 +76,11 @@ export function CourtesyCarCreateContainer({
onFinish={handleFinish} onFinish={handleFinish}
layout="vertical" layout="vertical"
> >
<CourtesyCarFormComponent form={form} saveLoading={loading} /> <CourtesyCarFormComponent
form={form}
saveLoading={loading}
newCC={true}
/>
</Form> </Form>
</RbacWrapper> </RbacWrapper>
); );

View File

@@ -167,9 +167,7 @@ export function ExportLogsPageComponent({ bodyshop }) {
{ text: "False", value: false }, { text: "False", value: false },
], ],
onFilter: (value, record) => record.successful === value, onFilter: (value, record) => record.successful === value,
render: (text, record) => ( render: (text, record) => <Checkbox checked={record.successful} />,
<Checkbox disabled checked={record.successful} />
),
}, },
{ {
title: t("general.labels.message"), title: t("general.labels.message"),

View File

@@ -131,7 +131,12 @@ export function* calculateScheduleLoad({ payload: end }) {
(load[itemDate].allHoursIn || 0) + (load[itemDate].allHoursIn || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs + item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs; item.larhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].allHoursInBody =
(load[itemDate].allHoursInBody || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].allHoursInRefinish =
(load[itemDate].allHoursInRefinish || 0) +
item.larhrs.aggregate.sum.mod_lb_hrs;
//If the job hasn't already arrived, add it to the jobs in list. //If the job hasn't already arrived, add it to the jobs in list.
// Make sure it also hasn't already been completed, or isn't an in and out job. // Make sure it also hasn't already been completed, or isn't an in and out job.
//This prevents the duplicate counting. //This prevents the duplicate counting.
@@ -142,6 +147,12 @@ export function* calculateScheduleLoad({ payload: end }) {
(load[itemDate].hoursIn || 0) + (load[itemDate].hoursIn || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs + item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs; item.larhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].hoursInBody =
(load[itemDate].hoursInBody || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].hoursInRefinish =
(load[itemDate].hoursInRefinish || 0) +
item.larhrs.aggregate.sum.mod_lb_hrs;
} }
} else { } else {
load[itemDate] = { load[itemDate] = {
@@ -152,10 +163,18 @@ export function* calculateScheduleLoad({ payload: end }) {
allHoursIn: allHoursIn:
item.labhrs.aggregate.sum.mod_lb_hrs + item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs, item.larhrs.aggregate.sum.mod_lb_hrs,
allHoursInBody: item.labhrs.aggregate.sum.mod_lb_hrs,
allHoursInRefinish: item.larhrs.aggregate.sum.mod_lb_hrs,
hoursIn: AddJobForSchedulingCalc hoursIn: AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs + ? item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs item.larhrs.aggregate.sum.mod_lb_hrs
: 0, : 0,
hoursInBody: AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs
: 0,
hoursInRefinish: AddJobForSchedulingCalc
? item.larhrs.aggregate.sum.mod_lb_hrs
: 0,
}; };
} }
}); });
@@ -179,6 +198,12 @@ export function* calculateScheduleLoad({ payload: end }) {
(load[itemDate].allHoursOut || 0) + (load[itemDate].allHoursOut || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs + item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs; item.larhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].allHoursOutBody =
(load[itemDate].allHoursOutBody || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].allHoursOutRefinish =
(load[itemDate].allHoursOutRefinish || 0) +
item.larhrs.aggregate.sum.mod_lb_hrs;
//Add only the jobs that are still in production to get rid of. //Add only the jobs that are still in production to get rid of.
//If it's not in production, we'd subtract unnecessarily. //If it's not in production, we'd subtract unnecessarily.
load[itemDate].allJobsOut.push(item); load[itemDate].allJobsOut.push(item);
@@ -189,6 +214,12 @@ export function* calculateScheduleLoad({ payload: end }) {
(load[itemDate].hoursOut || 0) + (load[itemDate].hoursOut || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs + item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs; item.larhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].hoursOutBody =
(load[itemDate].hoursOutBody || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].hoursOutRefinish =
(load[itemDate].hoursOutRefinish || 0) +
item.larhrs.aggregate.sum.mod_lb_hrs;
} }
} else { } else {
load[itemDate] = { load[itemDate] = {
@@ -201,6 +232,8 @@ export function* calculateScheduleLoad({ payload: end }) {
allHoursOut: allHoursOut:
item.labhrs.aggregate.sum.mod_lb_hrs + item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs, item.larhrs.aggregate.sum.mod_lb_hrs,
allHoursOutBody: item.labhrs.aggregate.sum.mod_lb_hrs,
allHoursOutRefinish: item.larhrs.aggregate.sum.mod_lb_hrs,
}; };
} }
}); });

View File

@@ -217,10 +217,12 @@
"markexported": "Mark Exported", "markexported": "Mark Exported",
"markforreexport": "Mark for Re-export", "markforreexport": "Mark for Re-export",
"new": "New Bill", "new": "New Bill",
"nobilllines": "This part has not yet been recieved.",
"noneselected": "No bill selected.", "noneselected": "No bill selected.",
"onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.", "onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.",
"printlabels": "Print Labels", "printlabels": "Print Labels",
"retailtotal": "Bills Retail Total", "retailtotal": "Bills Retail Total",
"returnfrombill": "Return From Bill",
"savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.", "savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.",
"state_tax": "Provincial/State Tax", "state_tax": "Provincial/State Tax",
"subtotal": "Subtotal", "subtotal": "Subtotal",
@@ -826,10 +828,11 @@
}, },
"status": { "status": {
"in": "Available", "in": "Available",
"inservice": "In Service", "inservice": "Service/Maintenance",
"leasereturn": "Lease Returned", "leasereturn": "Lease Returned",
"out": "Rented", "out": "Rented",
"sold": "Sold" "sold": "Sold",
"unavailable": "Unavailable"
}, },
"successes": { "successes": {
"saved": "Courtesy Car saved successfully." "saved": "Courtesy Car saved successfully."
@@ -884,6 +887,7 @@
"refhrs": "Refinish Hrs" "refhrs": "Refinish Hrs"
}, },
"titles": { "titles": {
"joblifecycle": "Job Lifecycle",
"labhours": "Total Body Hours", "labhours": "Total Body Hours",
"larhours": "Total Refinish Hours", "larhours": "Total Refinish Hours",
"monthlyemployeeefficiency": "Monthly Employee Efficiency", "monthlyemployeeefficiency": "Monthly Employee Efficiency",
@@ -898,8 +902,7 @@
"scheduledindate": "Sheduled In Today: {{date}}", "scheduledindate": "Sheduled In Today: {{date}}",
"scheduledintoday": "Sheduled In Today", "scheduledintoday": "Sheduled In Today",
"scheduledoutdate": "Sheduled Out Today: {{date}}", "scheduledoutdate": "Sheduled Out Today: {{date}}",
"scheduledouttoday": "Sheduled Out Today", "scheduledouttoday": "Sheduled Out Today"
"joblifecycle": "Job Lifecycle"
} }
}, },
"dms": { "dms": {
@@ -1114,6 +1117,7 @@
"loadingshop": "Loading shop data...", "loadingshop": "Loading shop data...",
"loggingin": "Authorizing...", "loggingin": "Authorizing...",
"markedexported": "Manually marked as exported.", "markedexported": "Manually marked as exported.",
"media": "Media",
"message": "Message", "message": "Message",
"monday": "Monday", "monday": "Monday",
"na": "N/A", "na": "N/A",
@@ -1234,22 +1238,21 @@
"columns": { "columns": {
"duration": "Duration", "duration": "Duration",
"end": "End", "end": "End",
"human_readable": "Human Readable",
"percentage": "Percentage",
"relative_end": "Relative End", "relative_end": "Relative End",
"relative_start": "Relative Start", "relative_start": "Relative Start",
"start": "Start", "start": "Start",
"value": "Value",
"status": "Status", "status": "Status",
"percentage": "Percentage", "status_count": "In Status",
"human_readable": "Human Readable", "value": "Value"
"status_count": "In Status"
},
"titles": {
"dashboard": "Job Lifecycle",
"top_durations": "Top Durations"
}, },
"content": { "content": {
"calculated_based_on": "Calculated based on",
"current_status_accumulated_time": "Current Status Accumulated Time", "current_status_accumulated_time": "Current Status Accumulated Time",
"data_unavailable": " There is currently no Lifecycle data for this Job.", "data_unavailable": " There is currently no Lifecycle data for this Job.",
"joblifecycle": "",
"jobs_in_since": "Jobs in since",
"legend_title": "Legend", "legend_title": "Legend",
"loading": "Loading Job Timelines....", "loading": "Loading Job Timelines....",
"not_available": "N/A", "not_available": "N/A",
@@ -1257,12 +1260,14 @@
"title": "Job Lifecycle Component", "title": "Job Lifecycle Component",
"title_durations": "Historical Status Durations", "title_durations": "Historical Status Durations",
"title_loading": "Loading", "title_loading": "Loading",
"title_transitions": "Transitions", "title_transitions": "Transitions"
"calculated_based_on": "Calculated based on",
"jobs_in_since": "Jobs in since"
}, },
"errors": { "errors": {
"fetch": "Error getting Job Lifecycle Data" "fetch": "Error getting Job Lifecycle Data"
},
"titles": {
"dashboard": "Job Lifecycle",
"top_durations": "Top Durations"
} }
}, },
"job_payments": { "job_payments": {
@@ -1282,6 +1287,7 @@
"amount": "Amount", "amount": "Amount",
"dateOfPayment": "Date of Payment", "dateOfPayment": "Date of Payment",
"descriptions": "Payment Details", "descriptions": "Payment Details",
"hint": "Hint",
"payer": "Payer", "payer": "Payer",
"payername": "Payer Name", "payername": "Payer Name",
"paymentid": "Payment Reference ID", "paymentid": "Payment Reference ID",
@@ -1572,7 +1578,7 @@
"federal_tax_payable": "Federal Tax Payable", "federal_tax_payable": "Federal Tax Payable",
"federal_tax_rate": "Federal Tax Rate", "federal_tax_rate": "Federal Tax Rate",
"ins_addr1": "Insurance Co. Address", "ins_addr1": "Insurance Co. Address",
"ins_city": "Insurance City", "ins_city": "Insurance Co. City",
"ins_co_id": "Insurance Co. ID", "ins_co_id": "Insurance Co. ID",
"ins_co_nm": "Insurance Company Name", "ins_co_nm": "Insurance Company Name",
"ins_co_nm_short": "Ins. Co.", "ins_co_nm_short": "Ins. Co.",
@@ -2331,6 +2337,7 @@
"markexported": "Payment(s) marked exported.", "markexported": "Payment(s) marked exported.",
"markreexported": "Payment marked for re-export successfully", "markreexported": "Payment marked for re-export successfully",
"payment": "Payment created successfully. ", "payment": "Payment created successfully. ",
"paymentupdate": "Payment updated successfully. ",
"stripe": "Credit card transaction charged successfully." "stripe": "Credit card transaction charged successfully."
} }
}, },

View File

@@ -217,10 +217,12 @@
"markexported": "", "markexported": "",
"markforreexport": "", "markforreexport": "",
"new": "", "new": "",
"nobilllines": "",
"noneselected": "", "noneselected": "",
"onlycmforinvoiced": "", "onlycmforinvoiced": "",
"printlabels": "", "printlabels": "",
"retailtotal": "", "retailtotal": "",
"returnfrombill": "",
"savewithdiscrepancy": "", "savewithdiscrepancy": "",
"state_tax": "", "state_tax": "",
"subtotal": "", "subtotal": "",
@@ -829,7 +831,8 @@
"inservice": "", "inservice": "",
"leasereturn": "", "leasereturn": "",
"out": "", "out": "",
"sold": "" "sold": "",
"unavailable": ""
}, },
"successes": { "successes": {
"saved": "" "saved": ""
@@ -884,6 +887,7 @@
"refhrs": "" "refhrs": ""
}, },
"titles": { "titles": {
"joblifecycle": "",
"labhours": "", "labhours": "",
"larhours": "", "larhours": "",
"monthlyemployeeefficiency": "", "monthlyemployeeefficiency": "",
@@ -898,8 +902,7 @@
"scheduledindate": "", "scheduledindate": "",
"scheduledintoday": "", "scheduledintoday": "",
"scheduledoutdate": "", "scheduledoutdate": "",
"scheduledouttoday": "", "scheduledouttoday": ""
"joblifecycle": ""
} }
}, },
"dms": { "dms": {
@@ -1114,6 +1117,7 @@
"loadingshop": "Cargando datos de la tienda ...", "loadingshop": "Cargando datos de la tienda ...",
"loggingin": "Iniciando sesión ...", "loggingin": "Iniciando sesión ...",
"markedexported": "", "markedexported": "",
"media": "",
"message": "", "message": "",
"monday": "", "monday": "",
"na": "N / A", "na": "N / A",
@@ -1234,22 +1238,21 @@
"columns": { "columns": {
"duration": "", "duration": "",
"end": "", "end": "",
"human_readable": "",
"percentage": "",
"relative_end": "", "relative_end": "",
"relative_start": "", "relative_start": "",
"start": "", "start": "",
"value": "",
"status": "", "status": "",
"percentage": "", "status_count": "",
"human_readable": "", "value": ""
"status_count": ""
},
"titles": {
"dashboard": "",
"top_durations": ""
}, },
"content": { "content": {
"calculated_based_on": "",
"current_status_accumulated_time": "", "current_status_accumulated_time": "",
"data_unavailable": "", "data_unavailable": "",
"joblifecycle": "",
"jobs_in_since": "",
"legend_title": "", "legend_title": "",
"loading": "", "loading": "",
"not_available": "", "not_available": "",
@@ -1257,12 +1260,14 @@
"title": "", "title": "",
"title_durations": "", "title_durations": "",
"title_loading": "", "title_loading": "",
"title_transitions": "", "title_transitions": ""
"calculated_based_on": "",
"jobs_in_since": ""
}, },
"errors": { "errors": {
"fetch": "Error al obtener los datos del ciclo de vida del trabajo" "fetch": "Error al obtener los datos del ciclo de vida del trabajo"
},
"titles": {
"dashboard": "",
"top_durations": ""
} }
}, },
"job_payments": { "job_payments": {
@@ -1282,6 +1287,7 @@
"amount": "", "amount": "",
"dateOfPayment": "", "dateOfPayment": "",
"descriptions": "", "descriptions": "",
"hint": "",
"payer": "", "payer": "",
"payername": "", "payername": "",
"paymentid": "", "paymentid": "",
@@ -2331,6 +2337,7 @@
"markexported": "", "markexported": "",
"markreexported": "", "markreexported": "",
"payment": "", "payment": "",
"paymentupdate": "",
"stripe": "" "stripe": ""
} }
}, },

View File

@@ -217,10 +217,12 @@
"markexported": "", "markexported": "",
"markforreexport": "", "markforreexport": "",
"new": "", "new": "",
"nobilllines": "",
"noneselected": "", "noneselected": "",
"onlycmforinvoiced": "", "onlycmforinvoiced": "",
"printlabels": "", "printlabels": "",
"retailtotal": "", "retailtotal": "",
"returnfrombill": "",
"savewithdiscrepancy": "", "savewithdiscrepancy": "",
"state_tax": "", "state_tax": "",
"subtotal": "", "subtotal": "",
@@ -829,7 +831,8 @@
"inservice": "", "inservice": "",
"leasereturn": "", "leasereturn": "",
"out": "", "out": "",
"sold": "" "sold": "",
"unavailable": ""
}, },
"successes": { "successes": {
"saved": "" "saved": ""
@@ -884,6 +887,7 @@
"refhrs": "" "refhrs": ""
}, },
"titles": { "titles": {
"joblifecycle": "",
"labhours": "", "labhours": "",
"larhours": "", "larhours": "",
"monthlyemployeeefficiency": "", "monthlyemployeeefficiency": "",
@@ -1113,6 +1117,7 @@
"loadingshop": "Chargement des données de la boutique ...", "loadingshop": "Chargement des données de la boutique ...",
"loggingin": "Vous connecter ...", "loggingin": "Vous connecter ...",
"markedexported": "", "markedexported": "",
"media": "",
"message": "", "message": "",
"monday": "", "monday": "",
"na": "N / A", "na": "N / A",
@@ -1233,22 +1238,21 @@
"columns": { "columns": {
"duration": "", "duration": "",
"end": "", "end": "",
"human_readable": "",
"percentage": "",
"relative_end": "", "relative_end": "",
"relative_start": "", "relative_start": "",
"start": "", "start": "",
"value": "",
"status": "", "status": "",
"percentage": "", "status_count": "",
"human_readable": "", "value": ""
"status_count": ""
},
"titles": {
"dashboard": "",
"top_durations": ""
}, },
"content": { "content": {
"calculated_based_on": "",
"current_status_accumulated_time": "", "current_status_accumulated_time": "",
"data_unavailable": "", "data_unavailable": "",
"joblifecycle": "",
"jobs_in_since": "",
"legend_title": "", "legend_title": "",
"loading": "", "loading": "",
"not_available": "", "not_available": "",
@@ -1256,13 +1260,14 @@
"title": "", "title": "",
"title_durations": "", "title_durations": "",
"title_loading": "", "title_loading": "",
"title_transitions": "", "title_transitions": ""
"calculated_based_on": "",
"jobs_in_since": "",
"joblifecycle": ""
}, },
"errors": { "errors": {
"fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches" "fetch": "Erreur lors de l'obtention des données du cycle de vie des tâches"
},
"titles": {
"dashboard": "",
"top_durations": ""
} }
}, },
"job_payments": { "job_payments": {
@@ -1282,6 +1287,7 @@
"amount": "", "amount": "",
"dateOfPayment": "", "dateOfPayment": "",
"descriptions": "", "descriptions": "",
"hint": "",
"payer": "", "payer": "",
"payername": "", "payername": "",
"paymentid": "", "paymentid": "",
@@ -2331,6 +2337,7 @@
"markexported": "", "markexported": "",
"markreexported": "", "markreexported": "",
"payment": "", "payment": "",
"paymentupdate": "",
"stripe": "" "stripe": ""
} }
}, },

View File

@@ -569,6 +569,13 @@
table: table:
name: parts_orders name: parts_orders
schema: public schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: billid
table:
name: tasks
schema: public
insert_permissions: insert_permissions:
- role: user - role: user
permission: permission:
@@ -818,6 +825,13 @@
table: table:
name: inventory name: inventory
schema: public schema: public
- name: ioevents
using:
foreign_key_constraint_on:
column: bodyshopid
table:
name: ioevents
schema: public
- name: jobs - name: jobs
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -846,6 +860,13 @@
table: table:
name: phonebook name: phonebook
schema: public schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: bodyshopid
table:
name: tasks
schema: public
- name: timetickets - name: timetickets
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -948,6 +969,7 @@
- md_rbac - md_rbac
- md_referral_sources - md_referral_sources
- md_responsibility_centers - md_responsibility_centers
- md_ro_guard
- md_ro_statuses - md_ro_statuses
- md_tasks_presets - md_tasks_presets
- md_to_emails - md_to_emails
@@ -1047,6 +1069,7 @@
- md_rbac - md_rbac
- md_referral_sources - md_referral_sources
- md_responsibility_centers - md_responsibility_centers
- md_ro_guard
- md_ro_statuses - md_ro_statuses
- md_tasks_presets - md_tasks_presets
- md_to_emails - md_to_emails
@@ -2675,6 +2698,13 @@
- table: - table:
name: ioevents name: ioevents
schema: public schema: public
object_relationships:
- name: bodyshop
using:
foreign_key_constraint_on: bodyshopid
- name: user
using:
foreign_key_constraint_on: useremail
- table: - table:
name: job_ar_schema name: job_ar_schema
schema: public schema: public
@@ -2824,6 +2854,13 @@
table: table:
name: parts_order_lines name: parts_order_lines
schema: public schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: joblineid
table:
name: tasks
schema: public
insert_permissions: insert_permissions:
- role: user - role: user
permission: permission:
@@ -3311,6 +3348,13 @@
table: table:
name: scoreboard name: scoreboard
schema: public schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: jobid
table:
name: tasks
schema: public
- name: timetickets - name: timetickets
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -4200,7 +4244,7 @@
interval_sec: 10 interval_sec: 10
num_retries: 0 num_retries: 0
timeout_sec: 60 timeout_sec: 60
webhook: https://worktest.home.irony.online webhook_from_env: HASURA_API_URL
headers: headers:
- name: event-secret - name: event-secret
value_from_env: EVENT_SECRET value_from_env: EVENT_SECRET
@@ -5008,6 +5052,13 @@
table: table:
name: parts_order_lines name: parts_order_lines
schema: public schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: partsorderid
table:
name: tasks
schema: public
insert_permissions: insert_permissions:
- role: user - role: user
permission: permission:
@@ -5623,6 +5674,129 @@
_eq: X-Hasura-User-Id _eq: X-Hasura-User-Id
- active: - active:
_eq: true _eq: true
- table:
name: tasks
schema: public
object_relationships:
- name: bill
using:
foreign_key_constraint_on: billid
- name: bodyshop
using:
foreign_key_constraint_on: bodyshopid
- name: job
using:
foreign_key_constraint_on: jobid
- name: jobline
using:
foreign_key_constraint_on: joblineid
- name: parts_order
using:
foreign_key_constraint_on: partsorderid
- name: user
using:
foreign_key_constraint_on: assigned_to
- name: userByCreatedBy
using:
foreign_key_constraint_on: created_by
insert_permissions:
- role: user
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- completed
- deleted
- priority
- assigned_to
- created_by
- description
- title
- completed_at
- created_at
- deleted_at
- due_date
- remind_at
- updated_at
- billid
- bodyshopid
- id
- jobid
- joblineid
- partsorderid
select_permissions:
- role: user
permission:
columns:
- completed
- deleted
- priority
- assigned_to
- created_by
- description
- title
- completed_at
- created_at
- deleted_at
- due_date
- remind_at
- updated_at
- billid
- bodyshopid
- id
- jobid
- joblineid
- partsorderid
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
allow_aggregations: true
update_permissions:
- role: user
permission:
columns:
- completed
- deleted
- priority
- assigned_to
- created_by
- description
- title
- completed_at
- created_at
- deleted_at
- due_date
- remind_at
- updated_at
- billid
- bodyshopid
- id
- jobid
- joblineid
- partsorderid
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
check: null
- table: - table:
name: timetickets name: timetickets
schema: public schema: public
@@ -6006,6 +6180,13 @@
table: table:
name: exportlog name: exportlog
schema: public schema: public
- name: ioevents
using:
foreign_key_constraint_on:
column: useremail
table:
name: ioevents
schema: public
- name: messages - name: messages
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:
@@ -6034,6 +6215,20 @@
table: table:
name: parts_orders name: parts_orders
schema: public schema: public
- name: tasks
using:
foreign_key_constraint_on:
column: assigned_to
table:
name: tasks
schema: public
- name: tasksByCreatedBy
using:
foreign_key_constraint_on:
column: created_by
table:
name: tasks
schema: public
- name: timetickets - name: timetickets
using: using:
foreign_key_constraint_on: foreign_key_constraint_on:

View File

@@ -0,0 +1 @@
DROP TABLE "public"."tasks";

View File

@@ -0,0 +1,18 @@
CREATE TABLE "public"."tasks" ("id" uuid NOT NULL DEFAULT gen_random_uuid(), "created_at" timestamptz NOT NULL DEFAULT now(), "updated_at" timestamptz NOT NULL DEFAULT now(), "title" text NOT NULL, "description" Text, "deleted" boolean NOT NULL DEFAULT false, "deleted_at" timestamptz, "due_date" timestamptz, "created_by" text NOT NULL, "assigned_to" Text, "completed" boolean NOT NULL DEFAULT false, "completed_at" timestamptz, "remind_at" timestamptz, "priority" numeric, "bodyshopid" UUID NOT NULL, "jobid" UUID NOT NULL, "joblineid" UUID, "partsorderid" UUID, "billid" UUID, PRIMARY KEY ("id") , FOREIGN KEY ("created_by") REFERENCES "public"."users"("email") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("assigned_to") REFERENCES "public"."users"("email") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("bodyshopid") REFERENCES "public"."bodyshops"("id") ON UPDATE restrict ON DELETE restrict, FOREIGN KEY ("jobid") REFERENCES "public"."jobs"("id") ON UPDATE cascade ON DELETE cascade, FOREIGN KEY ("joblineid") REFERENCES "public"."joblines"("id") ON UPDATE set null ON DELETE set null, FOREIGN KEY ("partsorderid") REFERENCES "public"."parts_orders"("id") ON UPDATE set null ON DELETE set null, FOREIGN KEY ("billid") REFERENCES "public"."bills"("id") ON UPDATE set null ON DELETE set null);
CREATE OR REPLACE FUNCTION "public"."set_current_timestamp_updated_at"()
RETURNS TRIGGER AS $$
DECLARE
_new record;
BEGIN
_new := NEW;
_new."updated_at" = NOW();
RETURN _new;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER "set_public_tasks_updated_at"
BEFORE UPDATE ON "public"."tasks"
FOR EACH ROW
EXECUTE PROCEDURE "public"."set_current_timestamp_updated_at"();
COMMENT ON TRIGGER "set_public_tasks_updated_at" ON "public"."tasks"
IS 'trigger to set value of column "updated_at" to current timestamp on row update';
CREATE EXTENSION IF NOT EXISTS pgcrypto;

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 "md_ro_guard" jsonb
-- null default jsonb_build_object();

View File

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

View File

@@ -224,11 +224,34 @@ exports.default = async function (socket, jobid) {
if (mapaAccount) { if (mapaAccount) {
if (!costCenterHash[mapaAccountName]) if (!costCenterHash[mapaAccountName])
costCenterHash[mapaAccountName] = Dinero(); costCenterHash[mapaAccountName] = Dinero();
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add( if (job.bodyshop.use_paint_scale_data === true) {
Dinero(job.job_totals.rates.mapa.total).percentage( if (job.mixdata.length > 0) {
bodyshop?.cdk_configuration?.sendmaterialscosting costCenterHash[mapaAccountName] = costCenterHash[
) mapaAccountName
); ].add(
Dinero({
amount: Math.round(
((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) *
100
),
})
);
} else {
costCenterHash[mapaAccountName] = costCenterHash[
mapaAccountName
].add(
Dinero(job.job_totals.rates.mapa.total).percentage(
bodyshop?.cdk_configuration?.sendmaterialscosting
)
);
}
} else {
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
Dinero(job.job_totals.rates.mapa.total).percentage(
bodyshop?.cdk_configuration?.sendmaterialscosting
)
);
}
} else { } else {
//console.log("NO MAPA ACCOUNT FOUND!!"); //console.log("NO MAPA ACCOUNT FOUND!!");
} }

View File

@@ -1793,6 +1793,7 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
md_responsibility_centers md_responsibility_centers
cdk_configuration cdk_configuration
pbs_configuration pbs_configuration
use_paint_scale_data
} }
ro_number ro_number
dms_allocation dms_allocation
@@ -1900,6 +1901,10 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
line_ref line_ref
unq_seq unq_seq
} }
mixdata(limit: 1, order_by: {updated_at: desc}) {
jobid
totalliquidcost
}
} }
}`; }`;
@@ -2175,4 +2180,12 @@ exports.COMPLETE_SURVEY = `mutation COMPLETE_SURVEY($surveyId: uuid!, $survey: c
update_csi(where: { id: { _eq: $surveyId } }, _set: $survey) { update_csi(where: { id: { _eq: $surveyId } }, _set: $survey) {
affected_rows affected_rows
} }
}`; }`;
exports.GET_JOBS_BY_PKS = `query GET_JOBS_BY_PKS($ids: [uuid!]!) {
jobs(where: {id: {_in: $ids}}) {
id
shopid
}
}
`;

View File

@@ -8,21 +8,15 @@ const moment = require("moment");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
require("dotenv").config({ require("dotenv").config({
path: path.resolve( path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
}); });
const domain = process.env.NODE_ENV ? "secure" : "test"; const domain = process.env.NODE_ENV ? "secure" : "test";
const { const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
SecretsManagerClient,
GetSecretValueCommand,
} = require("@aws-sdk/client-secrets-manager");
const client = new SecretsManagerClient({ const client = new SecretsManagerClient({
region: "ca-central-1", region: "ca-central-1" //TODO-AIO: instance manager required when merged to master-AIO
}); });
const gqlClient = require("../graphql-client/graphql-client").client; const gqlClient = require("../graphql-client/graphql-client").client;
@@ -32,7 +26,7 @@ const getShopCredentials = async (bodyshop) => {
if (process.env.NODE_ENV === undefined) { if (process.env.NODE_ENV === undefined) {
return { return {
merchantkey: process.env.INTELLIPAY_MERCHANTKEY, merchantkey: process.env.INTELLIPAY_MERCHANTKEY,
apikey: process.env.INTELLIPAY_APIKEY, apikey: process.env.INTELLIPAY_APIKEY
}; };
} }
@@ -42,26 +36,20 @@ const getShopCredentials = async (bodyshop) => {
const secret = await client.send( const secret = await client.send(
new GetSecretValueCommand({ new GetSecretValueCommand({
SecretId: `intellipay-credentials-${bodyshop.imexshopid}`, SecretId: `intellipay-credentials-${bodyshop.imexshopid}`,
VersionStage: "AWSCURRENT", // VersionStage defaults to AWSCURRENT if unspecified VersionStage: "AWSCURRENT" // VersionStage defaults to AWSCURRENT if unspecified
}) })
); );
return JSON.parse(secret.SecretString); return JSON.parse(secret.SecretString);
} catch (error) { } catch (error) {
return { return {
error: error.message, error: error.message
}; };
} }
} }
}; };
exports.lightbox_credentials = async (req, res) => { exports.lightbox_credentials = async (req, res) => {
logger.log( logger.log("intellipay-lightbox-credentials", "DEBUG", req.user?.email, null, null);
"intellipay-lightbox-credentials",
"DEBUG",
req.user?.email,
null,
null
);
const shopCredentials = await getShopCredentials(req.body.bodyshop); const shopCredentials = await getShopCredentials(req.body.bodyshop);
@@ -75,11 +63,9 @@ exports.lightbox_credentials = async (req, res) => {
headers: { "content-type": "application/x-www-form-urlencoded" }, headers: { "content-type": "application/x-www-form-urlencoded" },
data: qs.stringify({ data: qs.stringify({
...shopCredentials, ...shopCredentials,
operatingenv: "businessattended", operatingenv: "businessattended"
}), }),
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal${ url: `https://${domain}.cpteller.com/api/custapi.cfc?method=autoterminal${req.body.refresh ? "_refresh" : ""}` //autoterminal_refresh
req.body.refresh ? "_refresh" : ""
}`, //autoterminal_refresh
}; };
const response = await axios(options); const response = await axios(options);
@@ -87,13 +73,9 @@ exports.lightbox_credentials = async (req, res) => {
res.send(response.data); res.send(response.data);
} catch (error) { } catch (error) {
console.log(error); console.log(error);
logger.log( logger.log("intellipay-lightbox-credentials-error", "ERROR", req.user?.email, null, {
"intellipay-lightbox-credentials-error", error: JSON.stringify(error)
"ERROR", });
req.user?.email,
null,
{ error: JSON.stringify(error) }
);
res.json({ error }); res.json({ error });
} }
}; };
@@ -112,9 +94,9 @@ exports.payment_refund = async (req, res) => {
method: "payment_refund", method: "payment_refund",
...shopCredentials, ...shopCredentials,
paymentid: req.body.paymentid, paymentid: req.body.paymentid,
amount: req.body.amount, amount: req.body.amount
}), }),
url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`, url: `https://${domain}.cpteller.com/api/26/webapi.cfc?method=payment_refund`
}; };
const response = await axios(options); const response = await axios(options);
@@ -123,7 +105,7 @@ exports.payment_refund = async (req, res) => {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, { logger.log("intellipay-refund-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error), error: JSON.stringify(error)
}); });
res.json({ error }); res.json({ error });
} }
@@ -141,15 +123,13 @@ exports.generate_payment_url = async (req, res) => {
data: qs.stringify({ data: qs.stringify({
...shopCredentials, ...shopCredentials,
//...req.body, //...req.body,
amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat( amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat("0.00"),
"0.00"
),
account: req.body.account, account: req.body.account,
invoice: req.body.invoice, invoice: req.body.invoice,
createshorturl: true, createshorturl: true
//The postback URL is set at the CP teller global terminal settings page. //The postback URL is set at the CP teller global terminal settings page.
}), }),
url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url`, url: `https://${domain}.cpteller.com/api/custapi.cfc?method=generate_lightbox_url`
}; };
const response = await axios(options); const response = await axios(options);
@@ -158,56 +138,100 @@ exports.generate_payment_url = async (req, res) => {
} catch (error) { } catch (error) {
console.log(error); console.log(error);
logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, { logger.log("intellipay-payment-url-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error), error: JSON.stringify(error)
}); });
res.json({ error }); res.json({ error });
} }
}; };
exports.postback = async (req, res) => { exports.postback = async (req, res) => {
logger.log("intellipay-postback", "ERROR", req.user?.email, null, req.body); logger.log("intellipay-postback", "DEBUG", req.user?.email, null, req.body);
const { body: values } = req; const { body: values } = req;
if (!values.invoice) { const comment = Buffer.from(values?.comment, "base64").toString();
if ((!values.invoice || values.invoice === "") && !comment) {
//invoice is specified through the pay link. Comment by IO.
logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, req.body);
res.sendStatus(200); res.sendStatus(200);
return; return;
} }
// TODO query job by account name
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
id: values.invoice,
});
// TODO add mutation to database
try { try {
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, { if (values.invoice) {
paymentInput: { //This is a link email that's been sent out.
amount: values.total, const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
transactionid: `C00 ${values.authcode}`, id: values.invoice
payer: "Customer", });
type: values.cardtype,
jobid: values.invoice,
date: moment(Date.now()),
},
});
await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, { const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentResponse: { paymentInput: {
amount: values.total, amount: values.total,
bodyshopid: job.jobs_by_pk.shopid, transactionid: values.authcode,
paymentid: paymentResult.id, payer: "Customer",
jobid: values.invoice, type: values.cardtype,
declinereason: "Approved", jobid: values.invoice,
ext_paymentid: values.paymentid, date: moment(Date.now())
successful: true, }
response: values, });
},
});
res.send({ message: "Postback Successful" }); const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
paymentResponse: {
amount: values.total,
bodyshopid: job.jobs_by_pk.shopid,
paymentid: paymentResult.id,
jobid: values.invoice,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
});
logger.log("intellipay-postback-link-success", "DEBUG", req.user?.email, null, {
iprequest: values,
responseResults,
paymentResult
});
res.sendStatus(200);
} else if (comment) {
//This has been triggered by IO and may have multiple jobs.
const partialPayments = JSON.parse(comment);
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
ids: partialPayments.map((p) => p.jobid)
});
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: partialPayments.map((p) => ({
amount: p.amount,
transactionid: values.authcode,
payer: "Customer",
type: values.cardtype,
jobid: p.jobid,
date: moment(Date.now()),
payment_responses: {
data: {
amount: values.total,
bodyshopid: jobs.jobs[0].shopid,
jobid: p.jobid,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
}
}))
});
logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, null, {
iprequest: values,
paymentResult
});
res.sendStatus(200);
}
} catch (error) { } catch (error) {
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, { logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error), error: JSON.stringify(error),
body: req.body, body: req.body
}); });
res.status(400).json({ succesful: false, error: error.message }); res.status(400).json({ succesful: false, error: error.message });
} }