Compare commits

...

243 Commits

Author SHA1 Message Date
Allan Carr
b667524a04 Merged in release/2024-08-23-LEGACY (pull request #1639)
IO-2887 Null out BillData if returnfrombill is not available
2024-08-20 23:17:11 +00:00
Allan Carr
011cb1b349 Merged in feature/IO-2887-Returnfrombill-Parts-Drawer-LEGACY (pull request #1638)
IO-2887 Null out BillData if returnfrombill is not available
2024-08-20 23:16:45 +00:00
Allan Carr
9b2cdddb88 IO-2887 Null out BillData if returnfrombill is not available
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-20 16:17:04 -07:00
Allan Carr
5a4c0e4f65 Merged in release/2024-08-23-LEGACY (pull request #1637)
IO-2887 Returnfrombill Parts Drawer Legacy
2024-08-20 22:47:06 +00:00
Allan Carr
c71db5133d Merged in feature/IO-2887-Returnfrombill-Parts-Drawer-LEGACY (pull request #1636)
IO-2887 Returnfrombill Parts Drawer Legacy
2024-08-20 22:46:36 +00:00
Allan Carr
ee733434e6 IO-2887 Returnfrombill Parts Drawer Legacy
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-20 15:45:21 -07:00
Dave Richer
c2050e7e26 Merged in release/2024-08-16-LEGACY (pull request #1626)
Release/2024 08 16 LEGACY

Approved-by: Allan Carr
2024-08-16 23:31:21 +00:00
Dave Richer
5f9c756172 Merged in release/2024-08-16-LEGACY (pull request #1624)
IO-2884 Production List Filter LEGACY
2024-08-16 16:17:42 +00:00
Allan Carr
c0096a0dec Merged in feature/IO-2884-Production-List-Filter-LEGACY (pull request #1621)
IO-2884 Production List Filter LEGACY

Approved-by: Dave Richer
2024-08-16 16:16:29 +00:00
Allan Carr
41e40b9165 IO-2884 Production List Filter LEGACY
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-16 09:16:32 -07:00
Dave Richer
6528509288 Merged in release/2024-08-16-LEGACY (pull request #1609)
- Improve handle beta code (AIO Version)
2024-08-15 15:21:48 +00:00
Dave Richer
1e566c82cd Merged in feature/IO-2878-Enhance-Beta-Switch-legacy (pull request #1608)
- Improve handle beta code (AIO Version)
2024-08-15 15:21:28 +00:00
Dave Richer
d45be60668 - Improve handle beta code (AIO Version)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-15 11:19:26 -04:00
Allan Carr
a18e40a63c Merged in release/2024-08-16-LEGACY (pull request #1605)
IO-2876 OpenSearch Sorters LEGACY
2024-08-14 19:40:24 +00:00
Allan Carr
62d5511337 Merged in feature/IO-2876-OpenSearch-Sorters-LEGACY (pull request #1604)
IO-2876 OpenSearch Sorters LEGACY

Approved-by: Dave Richer
2024-08-14 19:37:26 +00:00
Allan Carr
ac37074666 IO-2876 OpenSearch Sorters LEGACY
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-14 11:47:34 -07:00
Dave Richer
c171847555 Merged in release/2024-08-16-LEGACY (pull request #1602)
- Improve handle beta code (AIO Version)
2024-08-14 18:42:09 +00:00
Dave Richer
9467c57c8f Merged in feature/IO-2878-Enhance-Beta-Switch-legacy (pull request #1600)
- Improve handle beta code (AIO Version)
2024-08-14 17:41:59 +00:00
Dave Richer
87f591ce1b - Improve handle beta code (AIO Version)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-08-14 13:33:27 -04:00
Allan Carr
c3a950088e Merged in release/2024-08-16-LEGACY (pull request #1592)
Release/2024 08 16 LEGACY

Approved-by: Dave Richer
2024-08-12 23:54:09 +00:00
Allan Carr
7e60e2c699 Merged in release/2024-08-16-LEGACY (pull request #1591)
Release/2024 08 16 LEGACY
2024-08-12 22:23:44 +00:00
Allan Carr
704e35e0b0 Merged in feature/IO-2564-LEGACY-Row-Expander-Drawers (pull request #1589)
IO-2564 LEGACY Row Expander Drawers
2024-08-12 22:17:22 +00:00
Allan Carr
d616d204d0 Merged in feature/IO-2861-LEGACY-Disable-Vendor-and-Invoice-#-on-Edit-of-InHouse (pull request #1590)
IO-2861 LEGACY Disable Editing of InHouse Invoice for Vendor and Invoice Number
2024-08-12 22:16:24 +00:00
Allan Carr
8eae657bb3 IO-2564 LEGACY Row Expander Drawers
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-12 15:04:42 -07:00
Allan Carr
492cee29e7 IO-2861 LEGACY Disable Editing of InHouse Invoice for Vendor and Invoice Number
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-08-12 12:36:31 -07:00
Dave Richer
b4cd016008 Merged in release/2024-05-31 (pull request #1455)
IO-2792 Resolve bill receive of credit memo and noline
2024-06-01 17:21:19 +00:00
Dave Richer
e685ca5ad8 Merged in release/2024-05-31 (pull request #1454)
IO-2792 Resolve bill receive of credit memo and noline
2024-06-01 17:20:41 +00:00
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
Dave Richer
0fa110cc51 Merged in release/2024-05-24 (pull request #1446)
Release/2024 05 24
2024-05-24 17:22:07 +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
76441e49e1 Merge branch 'test' of bitbucket.org:snaptsoft/bodyshop into test 2024-05-03 14:39:26 -07:00
Patrick Fic
524385af7d Merge branch 'release/2024-04-26' into test 2024-05-03 14:39:06 -07: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
ded7393aec Merged in release/2024-04-26 (pull request #1437)
Release/2024 04 26
2024-04-26 16:06:44 +00: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
b269d54f2c Merge branch 'release/2024-04-19' into test 2024-04-16 16:11:10 -07:00
Patrick Fic
069d508528 Update cron trigger timing. 2024-04-16 16:09:54 -07:00
Patrick Fic
d213c382c9 Merge branch 'release/2024-04-19' into test 2024-04-16 16:03:42 -07:00
Patrick Fic
878e81dc8f Hasura schema changes for tasks. 2024-04-16 16:02:25 -07:00
Dave Richer
deab90b326 Merged in release/2024-04-19 (pull request #1417)
Release/2024 04 19
2024-04-16 19:55:26 +00:00
Dave Richer
a1f7e7b755 Merged in feature/IO-2667-Migrations-For-Remind-At-Sent (pull request #1416)
- Migrations for remind_at_sent
2024-04-16 19:52:13 +00:00
Dave Richer
c8f8a86a98 - Migrations for remind_at_sent
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-16 15:45:56 -04: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
8c74276b98 Merged in release/2024-04-12 (pull request #1410)
IO-2609 Fix Spelling Mistake in object name
2024-04-11 21:39:59 +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
ac1ad12ac4 Merged in release/2024-04-12 (pull request #1407)
Release/2024 04 12
2024-04-11 21:23:28 +00: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
dedc594b58 Merged in release/2024-04-05 (pull request #1400)
Release/2024 04 05
2024-04-08 19:58:02 +00: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
02747f4144 Merged in release/2024-04-05 (pull request #1397)
IO-2568 Button Padding in Print Center Label Modal
2024-04-05 19:03:48 +00: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
05b1078e27 Merged in release/2024-04-05 (pull request #1394)
IO-2750 Missing Mutation return fields
2024-04-05 15:55:38 +00: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
Allan Carr
cc57eb40cf Merged in release/2024-04-05 (pull request #1391)
Release/2024 04 05
2024-04-04 19:27:21 +00: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
33518e5c25 Merged in release/2024-03-28 (pull request #1381)
Release/2024 03 28
2024-03-28 20:26:17 +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
Dave Richer
b34909b7f3 Merged in release/2024-03-28 (pull request #1378)
Release/2024 03 28
2024-03-28 17:00:21 +00: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
Dave Richer
bd599cfea4 Merged in release/2024-03-22 (pull request #1371)
IO-2721 Owners Note in Owners Card
2024-03-22 16:19:15 +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
Patrick Fic
e8917f5736 Merged in release/2024-03-22 (pull request #1368)
Release/2024 03 22
2024-03-21 21:43:06 +00: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
b1ca09bd4f IO-2710 Prettierr
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-19 17:55:59 -07:00
Allan Carr
f9ca36ec89 IO-2710 Job Assignment
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-19 17:52:47 -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
Dave Richer
f98c9e6f71 Merged in release/2024-03-15 (pull request #1354)
Release - 2024 03 15

Approved-by: Allan Carr
2024-03-15 18:50:55 +00:00
Patrick Fic
b7ee95059d Merged in release/2024-03-15 (pull request #1360)
Release/2024 03 15
2024-03-15 17:28:36 +00:00
Patrick Fic
28f2e8ad30 Merged in feature/IO-2679-interactivity-tracking (pull request #1358)
Feature/IO-2679 interactivity tracking
2024-03-15 17:25:59 +00:00
Patrick Fic
c27e206687 Add index to audit trail. 2024-03-15 10:24:00 -07:00
Patrick Fic
01fd253f1d Manual modification to hasura migration. 2024-03-15 10:23:13 -07:00
Patrick Fic
e67bc0d953 Merged in feature/IO-2679-interactivity-tracking (pull request #1356)
Add ioevent logging for events.
2024-03-15 16:56:37 +00:00
Patrick Fic
3eab3e2fb6 Add ioevent logging for events. 2024-03-15 09:55:14 -07:00
Dave Richer
f59d369529 Merged in release/2024-03-15 (pull request #1352)
- Implement
2024-03-15 14:49:47 +00:00
Dave Richer
3adf6b649b Merged in feature/IO-2678-Linkable-schedule (pull request #1351)
- Implement
2024-03-15 14:49:06 +00:00
Dave Richer
f8243aa2b3 - Implement
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-15 10:47:46 -04:00
Dave Richer
eefac38d9c Merged in release/2024-03-15 (pull request #1349)
- Fix bug
2024-03-14 19:00:06 +00:00
Dave Richer
3c3f50d138 Merged in feature/IO-2650-Lifecycle-V2 (pull request #1348)
- Fix bug
2024-03-14 18:59:37 +00:00
Dave Richer
4f7e1b81ac - Fix bug
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-14 14:59:06 -04:00
Dave Richer
249b74a597 Merged in release/2024-03-15 (pull request #1347)
Release/2024 03 15
2024-03-14 16:39:05 +00:00
Dave Richer
806bdc4c70 Merged in feature/IO-2650-Lifecycle-V2 (pull request #1346)
- Missing translation
2024-03-14 16:38:12 +00:00
Dave Richer
a0572a0cec - Missing translation
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-14 12:37:38 -04:00
Dave Richer
04cdf13e86 Merged in feature/IO-2650-Lifecycle-V2 (pull request #1344)
Feature/IO-2650 Lifecycle V2
2024-03-14 16:33:14 +00:00
Dave Richer
50349e91dc - remove duplicated code
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-14 12:32:40 -04:00
Dave Richer
9998a8f154 - Fix bug on humanReadable field we have not consumed until now.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-14 12:28:07 -04:00
Allan Carr
9dcbcb2a43 Merged in feature/IO-2671-Vehicle-Detail-Table (pull request #1343)
IO-2671 Add in appropriate sorters at same time

Approved-by: Dave Richer
2024-03-14 15:58:46 +00:00
Allan Carr
5773f7a0f3 Merged in feature/IO-2650-Lifecycle-Report (pull request #1345)
IO-2650 Job Lifecycle Report Center Reports

Approved-by: Dave Richer
2024-03-14 15:58:17 +00:00
Allan Carr
8614d88e71 Merged in feature/IO-2630-Parts-Queue-Mods (pull request #1342)
IO-2630 Adjust for onRow selection

Approved-by: Dave Richer
2024-03-14 15:57:45 +00:00
Allan Carr
fa5e26c52a IO-2650 Job Lifecycle Report Center Reports
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-13 17:22:42 -07:00
Dave Richer
90e1cbd390 - Job Lifecycle Dashboard Component.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-13 19:20:10 -04:00
Allan Carr
947a3c6a88 IO-2671 Add in appropriate sorters at same time
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-13 12:31:51 -07:00
Allan Carr
a33662e6f0 IO-2630 Adjust for oRow selection
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-13 12:04:45 -07:00
Patrick Fic
6d1f04369e Merged in feature/IO-2674-reorder-resp-centers (pull request #1340)
IO-2674 Add reordering arrows for responsibility centers in IO.

Approved-by: Allan Carr
2024-03-13 17:07:09 +00:00
Patrick Fic
4a27726ef3 Adjusted label for payers & added reorder to payers. 2024-03-13 12:54:31 -04:00
Allan Carr
eedba97237 Merged in feature/IO-2625-BPT-Hrs-in-Employee-Assignment (pull request #1339)
IO-2625 B/P/T Hrs Display in Employee Assignment Block

Approved-by: Dave Richer
2024-03-13 16:50:47 +00:00
Allan Carr
bcf095ed4f Merged in feature/IO-2669-Next-Contact-Date-formating (pull request #1337)
IO-2669 Next Contact Formating

Approved-by: Dave Richer
2024-03-13 16:50:00 +00:00
Allan Carr
c3f9e268c7 Merged in feature/IO-2671-Vehicle-Detail-Table (pull request #1338)
IO-2671 Missing field in Vehicle query

Approved-by: Dave Richer
2024-03-13 16:49:36 +00:00
Allan Carr
c8442f0750 Merged in feature/IO-2570-Totals-Card-on-Draw-have-Customer-Owing (pull request #1341)
IO-2570 Change Ded to Customer Owing amount

Approved-by: Dave Richer
2024-03-13 16:49:13 +00:00
Allan Carr
da5b446c30 Merge branch 'master' into feature/IO-2630-Parts-Queue-Mods
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-13 09:49:02 -07:00
Allan Carr
e13b2bb969 Merged in feature/IO-2520-Kaizen-Data-Pump (pull request #1336)
IO-2520 Change where email notification occurs

Approved-by: Dave Richer
2024-03-13 16:48:01 +00:00
Allan Carr
e8969c4698 IO-2570 Change Ded to Customer Owing amount
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-13 09:47:33 -07:00
Patrick Fic
346d32a2bb IO-2674 Add reordering arrows for responsibility centers in IO. 2024-03-13 08:02:20 -04:00
Dave Richer
6ba00a90be Merge branch 'master' into feature/IO-2650-Lifecycle-V2 2024-03-12 18:11:08 -04:00
Dave Richer
4293d20313 - Backend Changes for Lifecycle Data
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-12 18:10:09 -04:00
Allan Carr
706c70c509 IO-2625 B/P/T Hrs Display in Employee Assignment Block
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-12 14:49:12 -07:00
Allan Carr
e872b1bf0a IO-2671 Missing field in Vehicle query
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-12 11:55:07 -07:00
Allan Carr
379fa060d8 IO-2669 Next Contact Formating
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-12 11:24:57 -07:00
Allan Carr
a6258c6456 Merged in feature/IO-2650-Lifecycle-Report (pull request #1335)
IO-2650 Lifecycle Report for Print Center

Approved-by: Dave Richer
2024-03-12 17:01:19 +00:00
Allan Carr
d6bf0a225b IO-2520 Change where email notification occurs
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-11 17:46:19 -07:00
Allan Carr
ec2b914e5e Merge branch 'master' into feature/IO-2520-Kaizen-Data-Pump
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-11 17:45:26 -07:00
Allan Carr
309a20148a IO-2650 Lifecycle Report for Print Center
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-11 17:41:37 -07:00
Dave Richer
013b56778b Merged in release/2024-03-08 (pull request #1331)
Release into Master for 03 08 Release

Approved-by: Allan Carr
2024-03-11 17:47:51 +00:00
Allan Carr
4fb491697c Merged in release/2024-03-08 (pull request #1329)
Release/2024 03 08
2024-03-08 19:39:07 +00:00
Allan Carr
9dec4a3a61 Merged in feature/IO-2663-Export-Pages-Sorters-and-Filters (pull request #1327)
Correct Sorters
2024-03-08 19:36:40 +00:00
Allan Carr
189b4db90f Merged in feature/IO-2575-Production-Special-Coverage (pull request #1328)
Bring in line with other components
2024-03-08 19:36:27 +00:00
Allan Carr
c49fa1c527 Bring in line with other components
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-08 11:32:48 -08:00
Allan Carr
53ef048f6f Correct Sorters
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-08 11:24:41 -08:00
Dave Richer
09da3f349b Merged in release/2024-03-08 (pull request #1326)
- Add new special filters
2024-03-08 17:48:15 +00:00
Dave Richer
1cc7eed983 Merged in feature/IO-2662-New-Report-Reflectors (pull request #1325)
- Add new special filters
2024-03-08 17:47:43 +00:00
Dave Richer
1c8f377212 - Add new special filters
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-08 12:47:11 -05:00
Dave Richer
e8f4f30cc4 Merged in release/2024-03-08 (pull request #1324)
Release/2024 03 08
2024-03-08 17:39:02 +00:00
Dave Richer
c050947276 Merged in feature/IO-2662-New-Report-Reflectors (pull request #1323)
- Add new special filters
2024-03-08 17:37:10 +00:00
Dave Richer
9bde1f820d - Add new special filters
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-08 12:36:29 -05:00
Allan Carr
393765640c Merged in feature/IO-2663-Export-Pages-Sorters-and-Filters (pull request #1321)
IO-2663 Export Pages Sorters and Filters

Approved-by: Dave Richer
2024-03-08 01:14:40 +00:00
Allan Carr
69c2836425 Merged in feature/IO-2665-Jobs-sorters-and-query (pull request #1322)
IO-2665 Jobs page sorters and query adjustment

Approved-by: Dave Richer
2024-03-08 01:14:07 +00:00
Allan Carr
ec9edf30eb Merged in feature/IO-2651-Audit-Log-Extension (pull request #1320)
IO-2651 Audit Log Extension

Approved-by: Dave Richer
2024-03-08 01:13:42 +00:00
Allan Carr
6a812f9ea7 IO-2665 Jobs page sorters and query adjustment
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-07 11:53:34 -08:00
Allan Carr
fa86254bfd Prettierr
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-06 15:30:56 -08:00
Allan Carr
58ab7afbb3 IO-2663 Export Pages Sorters and Filters
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-06 15:29:24 -08:00
Allan Carr
fa7d90d2a9 IO-2651 Audit Log Extension
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-05 15:28:22 -08:00
Allan Carr
fbf9047974 Merged in feature/IO-2660-Phonebook-Draw-Title-Bug (pull request #1317)
IO-2660 Phonebook Drawer Title

Approved-by: Dave Richer
2024-03-05 20:32:29 +00:00
Allan Carr
3e9b795052 Merged in feature/IO-2575-Production-Special-Coverage (pull request #1318)
IO-2575 Special Coverage and Sorters & Filters

Approved-by: Dave Richer
2024-03-05 20:31:53 +00:00
Allan Carr
d29ffc21e5 IO-2575 Special Coverage and Sorters & Filters
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-05 11:49:57 -08:00
Allan Carr
959f7780e8 IO-2660 Phonebook Drawer Title
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-03-04 12:13:41 -08:00
Dave Richer
e47731702a Merged in release/2024-03-01 (pull request #1315)
Release/2024 03 01
2024-03-01 23:12:11 +00:00
Patrick Fic
8c4f395ff9 Merged in release/2024-03-01 (pull request #1316)
Resolve refund payment logging.
2024-03-01 19:59:42 +00:00
Patrick Fic
85a3aeb335 Resolve refund payment logging. 2024-03-01 11:51:01 -08:00
Allan Carr
acbf75ff4d Merged in release/2024-03-01 (pull request #1313)
Release/2024 03 01

Approved-by: Dave Richer
2024-03-01 17:18:20 +00:00
Dave Richer
80b7ae0e54 Merge branch 'feautre/IO-2647-Reporting-V3-From-Master' into release/2024-03-01
# Conflicts:
#	_reference/reportFiltersAndSorters.md
2024-02-29 22:25:52 -05:00
Dave Richer
0529ac4478 - Reports V3 Targeted at Master
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-02-29 22:22:55 -05:00
Allan Carr
3cafbebbee Merged in feature/IO-1366-Audit-Logging (pull request #1311)
Feature/IO-1366 Audit Logging

Approved-by: Dave Richer
2024-03-01 02:44:05 +00:00
Allan Carr
6f248d864e Merged in feature/IO-2656-Job-Count-on-Scoreboard (pull request #1312)
IO-2656 Job Count on Scoreboard Jobs

Approved-by: Dave Richer
2024-03-01 02:43:23 +00:00
Allan Carr
a4a84572b7 IO-2656 Job Count on Scoreboard Jobs
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-02-29 15:36:58 -08:00
Allan Carr
0bc00d46cf Merged in feature/IO-2654-Courtesy-Car-List-Filter (pull request #1310)
IO-2654 Local Storage Filter State for Courtesy Car List

Approved-by: Dave Richer
2024-02-28 18:23:49 +00:00
Allan Carr
e80e40bb76 IO-2654 Local Storage Filter State for Courtesy Car List
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-02-27 16:37:07 -08:00
Allan Carr
a88c102b27 Prettier and Package Update
Azura Storage Blob and Trivago Prettier Sort Imports

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-02-27 16:04:41 -08:00
Allan Carr
36e3828e2d Merged in release/2024-02-23 (pull request #1309)
IO-2640 Adjust Filters and Sorters for Table
2024-02-23 21:26:20 +00:00
Allan Carr
281c9bbc2a Merged in release/2024-02-23 (pull request #1304)
Release/2024 02 23
2024-02-22 21:53:33 +00:00
Allan Carr
455e79a6f7 Merged in release/2024-02-16 (pull request #1295)
IO-2631 Correct Import Statement for moment
2024-02-16 20:34:07 +00:00
Allan Carr
bc73e75217 Merged in release/2024-02-16 (pull request #1293)
IO-2631 Correct for Business Days
2024-02-16 19:25:01 +00:00
Allan Carr
7b3f71701e Merged in release/2024-02-16 (pull request #1288)
IO-2637 Correct for Timezone offset
2024-02-16 17:24:44 +00:00
Dave Richer
383a383a9f Merged in release/2024-02-16 (pull request #1286)
- Fix issues with labels on sorters
2024-02-16 02:05:56 +00:00
Dave Richer
7ee853ae15 Merged in release/2024-02-16 (pull request #1285)
Release/2024 02 16
2024-02-15 17:45:53 +00:00
Allan Carr
3510d081cd Merged in release/2024-02-02 (pull request #1273)
IO-2630 Adjust Query to match Tags
2024-02-09 17:55:09 +00:00
Allan Carr
471105828c Merged in release/2024-02-02 (pull request #1271)
Release/2024 02 02
2024-02-09 16:20:01 +00:00
Allan Carr
1ec1f5d3a8 Merged in release/2024-02-02 (pull request #1268)
IO-2626 Adjust Image Prop on customer page
2024-02-08 17:58:46 +00:00
Patrick Fic
355de9efd0 Merge branch 'release/2024-02-02' into test 2024-02-06 10:05:44 -08:00
Patrick Fic
2c14731ebd Merge branch 'release/2024-02-02' into test 2024-02-06 09:30:29 -08:00
Allan Carr
065d2d5688 Merged in release/2024-02-02 (pull request #1264)
IO-2626 CICD Resource Size Change
2024-02-06 16:56:32 +00:00
Allan Carr
879dea9fa7 Merged in release/2024-02-02 (pull request #1260)
Release/2024 02 02
2024-02-06 16:18:33 +00:00
Allan Carr
3ceada30a3 Merged in release/2024-02-02 (pull request #1252)
IO-2624 federal_tax_exempt destructure
2024-02-02 17:47:36 +00:00
Patrick Fic
d26eb81339 Merge branch 'feature/Sentry-Improvements' into test 2024-01-30 16:58:35 -08:00
Patrick Fic
fcdcdbbf7f Merge branch 'feature/Sentry-Improvements' into test 2024-01-30 13:33:13 -08:00
Patrick Fic
d482447dc9 Merge branch 'feature/Sentry-Improvements' into test 2024-01-30 13:26:32 -08:00
Patrick Fic
647dfbfa06 Merge branch 'feature/Sentry-Improvements' into test 2024-01-30 13:14:43 -08:00
Patrick Fic
f17899285f Merge branch 'feature/Sentry-Improvements' into test 2024-01-30 12:53:20 -08:00
Patrick Fic
47f5641518 Merge branch 'test' of bitbucket.org:snaptsoft/bodyshop into test 2024-01-30 12:48:00 -08:00
Patrick Fic
6e41a282ca Merge branch 'feature/Sentry-Improvements' into test 2024-01-30 12:47:38 -08:00
Patrick Fic
abbd2263ee Merged in release/2024-01-29 (pull request #1229)
IO-1532 resolve update logic issue for status timings.
2024-01-29 17:06:19 +00:00
Dave Richer
4b70c89e54 Merged in release/2024-01-26 (pull request #1224)
- updates from lifecyle component.
2024-01-26 23:07:40 +00:00
Dave Richer
5e2c1aae4f Merged in release/2024-01-26 (pull request #1222)
- Fix use hook
2024-01-26 21:34:47 +00:00
Dave Richer
fda06bb298 Merged in release/2024-01-26 (pull request #1220)
Release/2024 01 26
2024-01-26 21:21:52 +00:00
Allan Carr
3dfd45ca0f Merged in release/2024-01-26 (pull request #1217)
Release/2024 01 26
2024-01-26 16:53:56 +00:00
Allan Carr
86e748502c Merged in release/2024-01-26 (pull request #1212)
Release/2024 01 26

Approved-by: Dave Richer
2024-01-25 20:27:32 +00:00
Patrick Fic
0f85250d40 Merged in release/2024-01-19 (pull request #1205)
Release/2024 01 19
2024-01-19 21:19:03 +00:00
Allan Carr
f04bc59966 Merged in release/2024-01-19 (pull request #1184)
Release/2024 01 19

Approved-by: Dave Richer
2024-01-19 16:59:22 +00:00
Dave Richer
e6e24dae4b Merged in feature/IO-1828-Beta-Updates-To-Test (pull request #1192)
- update handleBeta
2024-01-18 21:36:14 +00:00
Dave Richer
4ad32117c3 Merged in feature/IO-1828-Beta-Updates-To-Test (pull request #1191)
- update handleBeta
2024-01-18 21:23:38 +00:00
Dave Richer
befb8009e0 Merged in feature/IO-1828-Beta-Updates-To-Test (pull request #1190)
- update handleBeta
2024-01-18 21:16:22 +00:00
Dave Richer
41bec72390 Merged in feature/IO-1828-Beta-Updates-To-Test (pull request #1189)
- update handleBeta
2024-01-18 21:07:45 +00:00
142 changed files with 6411 additions and 1790 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:

16
.prettierrc.js Normal file
View File

@@ -0,0 +1,16 @@
exports.default = {
printWidth: 120,
useTabs: false,
tabWidth: 2,
trailingComma: "es5",
semi: true,
singleQuote: false,
bracketSpacing: true,
arrowParens: "always",
jsxSingleQuote: false,
bracketSameLine: false,
endOfLine: "lf",
importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
importOrderSeparation: true,
importOrderSortSpecifiers: true,
};

View File

@@ -3,7 +3,13 @@
This documentation details the schema required for `.filters` files on the report server. It is used to dynamically This documentation details the schema required for `.filters` files on the report server. It is used to dynamically
modify the graphQL query and provide the user more power over their reports. modify the graphQL query and provide the user more power over their reports.
# Special Notes For filters and sorters, valid types include (`type` key in the schema):
- string (default)
- number
- bool or boolean
- date
## Special Notes
- When passing the data to the template server, the property filters and sorters is added to the data object and will reflect the filters and sorters the user has selected - When passing the data to the template server, the property filters and sorters is added to the data object and will reflect the filters and sorters the user has selected
## High level Schema Overview ## High level Schema Overview
@@ -40,9 +46,10 @@ Filters effect the where clause of the graphQL query. They are used to filter th
A note on special notation used in the `name` field. A note on special notation used in the `name` field.
## Reflection ## Reflection
Filters can make use of reflection to pre-fill select boxes, the following is an example of that in the filters file. Filters can make use of reflection to pre-fill select boxes, the following is an example of that in the filters file.
``` ```json
{ {
"name": "jobs.status", "name": "jobs.status",
"translation": "jobs.fields.status", "translation": "jobs.fields.status",
@@ -52,7 +59,7 @@ Filters can make use of reflection to pre-fill select boxes, the following is an
"type": "internal", "type": "internal",
"name": "special.job_statuses" "name": "special.job_statuses"
} }
}, }
``` ```
in this example, a reflector with the type 'internal' (all types at the moment require this, and it is used for future functionality), with a name of `special.job_statuses` in this example, a reflector with the type 'internal' (all types at the moment require this, and it is used for future functionality), with a name of `special.job_statuses`
@@ -67,7 +74,13 @@ The following cases are available
- `special.employees` - This will reflect the employees `bodyshop.employees` - `special.employees` - This will reflect the employees `bodyshop.employees`
- `special.first_names` - This will reflect the first names `bodyshop.employees` - `special.first_names` - This will reflect the first names `bodyshop.employees`
- `special.last_names` - This will reflect the last names `bodyshop.employees` - `special.last_names` - This will reflect the last names `bodyshop.employees`
- - `special.referral_sources` - This will reflect the referral sources `bodyshop.md_referral_sources`
- `special.class`- This will reflect the class `bodyshop.md_classes`
- `special.lost_sale_reasons` - This will reflect the lost sale reasons `bodyshop.md_lost_sale_reasons`
- `special.alt_transports` - This will reflect the alternative transports `bodyshop.appt_alt_transport`
- `special.payment_types` - This will reflect the payment types `bodyshop.md_payment_types`
- `special.payment_payers` - This is a special case with a key value set of [Customer, Insurance]
### Path without brackets, multi level ### Path without brackets, multi level
`"name": "jobs.joblines.mod_lb_hrs",` `"name": "jobs.joblines.mod_lb_hrs",`
@@ -104,6 +117,7 @@ query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!
``` ```
### Path with brackets,top level ### Path with brackets,top level
`"name": "[jobs].joblines.mod_lb_hrs",` `"name": "[jobs].joblines.mod_lb_hrs",`
This will produce a where clause at the `jobs` level of the graphQL query. This will produce a where clause at the `jobs` level of the graphQL query.
@@ -138,18 +152,26 @@ query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!
``` ```
## Known Caveats ## Known Caveats
- Will only support two level of nesting in the graphQL query `jobs.joblines.mod_lb_hrs` vs `[jobs].joblines.mod_lb_hrs` is fine, but `jobs.[joblines.].some_table.mod_lb_hrs` is not.
- The `dates` object is not yet implemented and will be added in a future release. - Will only support two level of nesting in the graphQL query `jobs.joblines.mod_lb_hrs` vs `[jobs].joblines.mod_lb_hrs`
- The type object must be 'string' or 'number' and is case-sensitive. is fine, but `jobs.[joblines.].some_table.mod_lb_hrs` is not.
- The type object must be 'string' or 'number' or 'bool' or 'boolean' or 'date' and is case-sensitive.
- The `translation` key is used to look up the label in the GUI, if it is not found, the `label` key is used. - The `translation` key is used to look up the label in the GUI, if it is not found, the `label` key is used.
- Do not add the ability to filter things that are already filtered as part of the original query, this would be redundant and could cause issues. - Do not add the ability to filter things that are already filtered as part of the original query, this would be
redundant and could cause issues.
- Do not add the ability to filter on things like FK constraints, must like the above example. - Do not add the ability to filter on things like FK constraints, must like the above example.
- *INHERITANCE CAVEAT* If you have a filters file on a parent report that has a child that you do not want the filters inherited from, you must place a blank filters file (valid json so {}) on the child report level. This will than fetch the child filters, which are empty and move along, versus inheriting the parent filters.
## Sorters ## Sorters
- Sorters follow the same schema as filters, however, they do not do square bracket wrapping to indicate level hoisting, a filter added on `job.md_status` would be added at the top level, and a filter added on `jobs.joblines.mod_lb_hrs` would be added at the `joblines` level.
- Most of the reports currently do sorting on a template level, this will need to change to actually see the results using the sorters. - Sorters follow the same schema as filters, however, they do not do square bracket wrapping to indicate level hoisting,
a filter added on `job.md_status` would be added at the top level, and a filter added on `jobs.joblines.mod_lb_hrs`
would be added at the `joblines` level.
- Most of the reports currently do sorting on a template level, this will need to change to actually see the results
using the sorters.
### Default Sorters ### Default Sorters
- A sorter can be given a default object containing a `order` and `direction` key value. This will be used to sort the report if the user does not select any of the sorters themselves. - A sorter can be given a default object containing a `order` and `direction` key value. This will be used to sort the report if the user does not select any of the sorters themselves.
- The `order` key is the order in which the sorters are applied, and the `direction` key is the direction of the sort, either `asc` or `desc`. - The `order` key is the order in which the sorters are applied, and the `direction` key is the direction of the sort, either `asc` or `desc`.
@@ -164,4 +186,4 @@ query gendoc_hours_sold_detail_open($starttz: timestamptz!, $endtz: timestamptz!
"direction": "asc" "direction": "asc"
} }
} }
``` ```

File diff suppressed because it is too large Load Diff

View File

@@ -1,21 +1,21 @@
import { Input, Table, Checkbox, Card, Space } from "antd"; import { Card, Checkbox, Input, Space, Table } from "antd";
import queryString from "query-string";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import PayableExportButton from "../payable-export-button/payable-export-button.component";
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
import { DateFormatter } from "../../utils/DateFormatter";
import queryString from "query-string";
import { logImEXEvent } from "../../firebase/firebase.utils";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { pageLimit } from "../../utils/config";
import { alphaSort, dateSort } from "../../utils/sorters";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component"; import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
import PayableExportButton from "../payable-export-button/payable-export-button.component";
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component"; import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
import {pageLimit} from "../../utils/config"; import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -138,7 +138,6 @@ export function AccountingPayablesTableComponent({
title: t("exportlogs.labels.attempts"), title: t("exportlogs.labels.attempts"),
dataIndex: "attempts", dataIndex: "attempts",
key: "attempts", key: "attempts",
render: (text, record) => ( render: (text, record) => (
<ExportLogsCountDisplay logs={record.exportlogs} /> <ExportLogsCountDisplay logs={record.exportlogs} />
), ),
@@ -147,8 +146,6 @@ export function AccountingPayablesTableComponent({
title: t("general.labels.actions"), title: t("general.labels.actions"),
dataIndex: "actions", dataIndex: "actions",
key: "actions", key: "actions",
sorter: (a, b) => a.clm_total - b.clm_total,
render: (text, record) => ( render: (text, record) => (
<PayableExportButton <PayableExportButton
billId={record.id} billId={record.id}

View File

@@ -8,14 +8,16 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
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, DateTimeFormatter } from "../../utils/DateFormatter"; import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { pageLimit } from "../../utils/config";
import { alphaSort, dateSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component"; import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import PaymentExportButton from "../payment-export-button/payment-export-button.component"; import PaymentExportButton from "../payment-export-button/payment-export-button.component";
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component"; import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component"; import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component"; import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -75,7 +77,11 @@ export function AccountingPayablesTableComponent({
dataIndex: "owner", dataIndex: "owner",
key: "owner", key: "owner",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.job.ownr_ln, b.job.ownr_ln), sorter: (a, b) =>
alphaSort(
OwnerNameDisplayFunction(a.job),
OwnerNameDisplayFunction(b.job)
),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
@@ -94,6 +100,9 @@ export function AccountingPayablesTableComponent({
title: t("payments.fields.amount"), title: t("payments.fields.amount"),
dataIndex: "amount", dataIndex: "amount",
key: "amount", key: "amount",
sorter: (a, b) => a.amount - b.amount,
sortOrder:
state.sortedInfo.columnKey === "amount" && state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => (
<CurrencyFormatter>{record.amount}</CurrencyFormatter> <CurrencyFormatter>{record.amount}</CurrencyFormatter>
), ),
@@ -112,18 +121,21 @@ export function AccountingPayablesTableComponent({
title: t("payments.fields.created_at"), title: t("payments.fields.created_at"),
dataIndex: "created_at", dataIndex: "created_at",
key: "created_at", key: "created_at",
sorter: (a, b) => dateSort(a.created_at, b.created_at),
sortOrder:
state.sortedInfo.columnKey === "created_at" && state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => (
<DateTimeFormatter>{record.created_at}</DateTimeFormatter> <DateTimeFormatter>{record.created_at}</DateTimeFormatter>
), ),
}, },
{ // {
title: t("payments.fields.exportedat"), // title: t("payments.fields.exportedat"),
dataIndex: "exportedat", // dataIndex: "exportedat",
key: "exportedat", // key: "exportedat",
render: (text, record) => ( // render: (text, record) => (
<DateTimeFormatter>{record.exportedat}</DateTimeFormatter> // <DateTimeFormatter>{record.exportedat}</DateTimeFormatter>
), // ),
}, // },
{ {
title: t("exportlogs.labels.attempts"), title: t("exportlogs.labels.attempts"),
dataIndex: "attempts", dataIndex: "attempts",
@@ -137,8 +149,6 @@ export function AccountingPayablesTableComponent({
title: t("general.labels.actions"), title: t("general.labels.actions"),
dataIndex: "actions", dataIndex: "actions",
key: "actions", key: "actions",
sorter: (a, b) => a.clm_total - b.clm_total,
render: (text, record) => ( render: (text, record) => (
<PaymentExportButton <PaymentExportButton
paymentId={record.id} paymentId={record.id}

View File

@@ -4,17 +4,19 @@ import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, dateSort } from "../../utils/sorters"; import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component"; import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component"; import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component"; import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -63,7 +65,7 @@ export function AccountingReceivablesTableComponent({
title: t("jobs.fields.status"), title: t("jobs.fields.status"),
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
sorter: (a, b) => a.status - b.status, sorter: (a, b) => statusSort(a, b, bodyshop.md_ro_statuses.statuses),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order, state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
}, },
@@ -83,7 +85,8 @@ export function AccountingReceivablesTableComponent({
title: t("jobs.fields.owner"), title: t("jobs.fields.owner"),
dataIndex: "owner", dataIndex: "owner",
key: "owner", key: "owner",
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), sorter: (a, b) =>
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
@@ -103,6 +106,15 @@ export function AccountingReceivablesTableComponent({
dataIndex: "vehicle", dataIndex: "vehicle",
key: "vehicle", key: "vehicle",
ellipsis: true, ellipsis: true,
sorter: (a, b) =>
alphaSort(
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
a.v_model_desc || ""
}`,
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
),
sortOrder:
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
return record.vehicleid ? ( return record.vehicleid ? (
<Link to={"/manage/vehicles/" + record.vehicleid}> <Link to={"/manage/vehicles/" + record.vehicleid}>

View File

@@ -12,8 +12,8 @@ import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({}); const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect(mapStateToProps, mapDispatchToProps)(BillDeleteButton); export default connect(mapStateToProps, mapDispatchToProps)(BillDeleteButton);
@@ -51,6 +51,7 @@ export function BillDeleteButton({ bill, jobid, callback, insertAuditTrail }) {
insertAuditTrail({ insertAuditTrail({
jobid: jobid, jobid: jobid,
operation: AuditTrailMapping.billdeleted(bill.invoice_number), operation: AuditTrailMapping.billdeleted(bill.invoice_number),
type: "billdeleted",
}); });
if (callback && typeof callback === "function") callback(bill.id); if (callback && typeof callback === "function") callback(bill.id);

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";
@@ -33,8 +33,8 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) => setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })), dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect( export default connect(
@@ -150,6 +150,7 @@ export function BillDetailEditcontainer({
jobid: bill.jobid, jobid: bill.jobid,
billid: search.billid, billid: search.billid,
operation: AuditTrailMapping.billupdated(bill.invoice_number), operation: AuditTrailMapping.billupdated(bill.invoice_number),
type: "billupdated",
}); });
await refetch(); await refetch();
@@ -163,6 +164,7 @@ export function BillDetailEditcontainer({
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>; if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
const exported = data && data.bills_by_pk && data.bills_by_pk.exported; const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
const isinhouse = data && data.bills_by_pk && data.bills_by_pk.isinhouse;
return ( return (
<> <>
@@ -206,8 +208,8 @@ export function BillDetailEditcontainer({
initialValues={transformData(data)} initialValues={transformData(data)}
layout="vertical" layout="vertical"
> >
<BillFormContainer form={form} billEdit disabled={exported} /> <BillFormContainer form={form} billEdit disabled={exported} disableInHouse={isinhouse}/>
<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

@@ -16,8 +16,8 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) => setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })), dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect( export default connect(
@@ -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

@@ -37,8 +37,8 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")), toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
insertAuditTrail: ({ jobid, billid, operation }) => insertAuditTrail: ({ jobid, billid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, billid, operation })), dispatch(insertAuditTrail({ jobid, billid, operation, type })),
}); });
const Templates = TemplateList("job_special"); const Templates = TemplateList("job_special");
@@ -171,6 +171,7 @@ function BillEnterModalContainer({
mod_lbr_ty: key, mod_lbr_ty: key,
hours: adjustmentsToInsert[key].toFixed(1), hours: adjustmentsToInsert[key].toFixed(1),
}), }),
type: "jobmodifylbradj",
}); });
}); });
@@ -320,6 +321,7 @@ function BillEnterModalContainer({
operation: AuditTrailMapping.billposted( operation: AuditTrailMapping.billposted(
r1.data.insert_bills.returning[0].invoice_number r1.data.insert_bills.returning[0].invoice_number
), ),
type: "billposted",
}); });
if (enterAgain) { if (enterAgain) {

View File

@@ -47,6 +47,7 @@ export function BillFormComponent({
loadLines, loadLines,
billEdit, billEdit,
disableInvNumber, disableInvNumber,
disableInHouse,
job, job,
loadOutstandingReturns, loadOutstandingReturns,
loadInventory, loadInventory,
@@ -198,7 +199,7 @@ export function BillFormComponent({
]} ]}
> >
<VendorSearchSelect <VendorSearchSelect
disabled={disabled} disabled={disabled || disableInHouse}
options={vendorAutoCompleteOptions} options={vendorAutoCompleteOptions}
preferredMake={preferredMake} preferredMake={preferredMake}
onSelect={handleVendorSelect} onSelect={handleVendorSelect}
@@ -271,7 +272,7 @@ export function BillFormComponent({
}), }),
]} ]}
> >
<Input disabled={disabled || disableInvNumber} /> <Input disabled={disabled || disableInvNumber || disableInHouse} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bills.fields.date")} label={t("bills.fields.date")}
@@ -504,10 +505,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

@@ -22,6 +22,7 @@ export function BillFormContainer({
billEdit, billEdit,
disabled, disabled,
disableInvNumber, disableInvNumber,
disableInHouse
}) { }) {
const { Simple_Inventory } = useTreatments( const { Simple_Inventory } = useTreatments(
["Simple_Inventory"], ["Simple_Inventory"],
@@ -57,6 +58,7 @@ export function BillFormContainer({
job={lineData ? lineData.jobs_by_pk : null} job={lineData ? lineData.jobs_by_pk : null}
responsibilityCenters={bodyshop.md_responsibility_centers || null} responsibilityCenters={bodyshop.md_responsibility_centers || null}
disableInvNumber={disableInvNumber} disableInvNumber={disableInvNumber}
disableInHouse={disableInHouse}
loadOutstandingReturns={loadOutstandingReturns} loadOutstandingReturns={loadOutstandingReturns}
loadInventory={loadInventory} loadInventory={loadInventory}
preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null} preferredMake={lineData ? lineData.jobs_by_pk.v_make_desc : null}

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";
@@ -37,8 +35,8 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")), toggleModalVisible: () => dispatch(toggleModalVisible("cardPayment")),
}); });
@@ -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) => ({
@@ -102,55 +103,15 @@ const CardPaymentModalComponent = ({
insertAuditTrail({ insertAuditTrail({
jobid: payment.jobid, jobid: payment.jobid,
operation: AuditTrailMapping.failedpayment(), operation: AuditTrailMapping.failedpayment(),
type: "failedpayment",
}) })
); );
}); });
}; };
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();
@@ -163,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) {
@@ -191,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={{
@@ -272,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
@@ -343,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"
@@ -357,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

@@ -17,13 +17,18 @@ import { DateTimeFormatter } from "../../utils/DateFormatter";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
export default function CourtesyCarsList({ loading, courtesycars, refetch }) { export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
filteredInfo: { text: "" },
}); });
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const [filter, setFilter] = useLocalStorage(
"filter_courtesy_cars_list",
null
);
const { t } = useTranslation(); const { t } = useTranslation();
const columns = [ const columns = [
@@ -50,11 +55,16 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
sorter: (a, b) => alphaSort(a.status, b.status), sorter: (a, b) => alphaSort(a.status, b.status),
filteredValue: filter?.status || null,
filters: [ filters: [
{ {
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",
@@ -67,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) => {
@@ -112,6 +126,7 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
dataIndex: "readiness", dataIndex: "readiness",
key: "readiness", key: "readiness",
sorter: (a, b) => alphaSort(a.readiness, b.readiness), sorter: (a, b) => alphaSort(a.readiness, b.readiness),
filteredValue: filter?.readiness || null,
filters: [ filters: [
{ {
text: t("courtesycars.readiness.ready"), text: t("courtesycars.readiness.ready"),
@@ -171,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) => {
@@ -180,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:
@@ -227,7 +244,8 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
]; ];
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); setState({ ...state, sortedInfo: sorter });
setFilter(filters);
}; };
const tableData = searchText const tableData = searchText

View File

@@ -0,0 +1,169 @@
import {Card, Table, Tag} from "antd";
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
import {useTranslation} from "react-i18next";
import React, {useEffect, useState} from "react";
import moment from "moment";
import DashboardRefreshRequired from "../refresh-required.component";
import axios from "axios";
const fortyFiveDaysAgo = () => moment().subtract(45, 'days').toLocaleString();
export default function JobLifecycleDashboardComponent({data, bodyshop, ...cardProps}) {
const {t} = useTranslation();
const [loading, setLoading] = useState(false);
const [lifecycleData, setLifecycleData] = useState(null);
useEffect(() => {
async function getLifecycleData() {
if (data && data.job_lifecycle) {
setLoading(true);
const response = await axios.post("/job/lifecycle", {
jobids: data.job_lifecycle.map(x => x.id),
statuses: bodyshop.md_ro_statuses
});
setLifecycleData(response.data.durations);
setLoading(false);
}
}
getLifecycleData().catch(e => {
console.error(`Error in getLifecycleData: ${e}`);
})
}, [data, bodyshop]);
const columns = [
{
title: t('job_lifecycle.columns.status'),
dataIndex: 'status',
bgColor: 'red',
key: 'status',
render: (text, record) => {
return <Tag color={record.color}>{record.status}</Tag>
}
},
{
title: t('job_lifecycle.columns.human_readable'),
dataIndex: 'humanReadable',
key: 'humanReadable',
},
{
title: t('job_lifecycle.columns.status_count'),
key: 'statusCount',
render: (text, record) => {
return lifecycleData.statusCounts[record.status];
}
},
{
title: t('job_lifecycle.columns.percentage'),
dataIndex: 'percentage',
key: 'percentage',
render: (text, record) => {
return record.percentage.toFixed(2) + '%';
}
},
];
if (!data) return null;
if (!data.job_lifecycle || !lifecycleData) return <DashboardRefreshRequired {...cardProps} />;
const extra = `${t('job_lifecycle.content.calculated_based_on')} ${lifecycleData.jobs} ${t('job_lifecycle.content.jobs_in_since')} ${fortyFiveDaysAgo()}`
return (
<Card title={t("job_lifecycle.titles.dashboard")} {...cardProps}>
<LoadingSkeleton loading={loading}>
<div style={{overflow: 'scroll', height: "100%"}}>
<div id="bar-container" style={{
display: 'flex',
width: '100%',
height: '100px',
textAlign: 'center',
borderRadius: '5px',
borderWidth: '5px',
borderStyle: 'solid',
borderColor: '#f0f2f5',
margin: 0,
padding: 0
}}>
{lifecycleData.summations.map((key, index, array) => {
const isFirst = index === 0;
const isLast = index === array.length - 1;
return (
<div key={key.status} style={{
overflow: 'hidden',
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
margin: 0,
padding: 0,
borderTop: '1px solid #f0f2f5',
borderBottom: '1px solid #f0f2f5',
borderLeft: isFirst ? '1px solid #f0f2f5' : undefined,
borderRight: isLast ? '1px solid #f0f2f5' : undefined,
backgroundColor: key.color,
width: `${key.percentage}%`
}}
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
>
{key.percentage > 15 ?
<>
<div>{key.roundedPercentage}</div>
<div style={{
backgroundColor: '#f0f2f5',
borderRadius: '5px',
paddingRight: '2px',
paddingLeft: '2px',
fontSize: '0.8rem',
}}>
{key.status}
</div>
</>
: null}
</div>
);
})}
</div>
<Card extra={extra} type='inner' title={t('job_lifecycle.content.legend_title')}
style={{marginTop: '10px'}}>
<div>
{lifecycleData.summations.map((key) => (
<Tag color={key.color} style={{width: '13vh', padding: '4px', margin: '4px'}}>
<div
aria-label={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
title={`${key.status} | ${key.roundedPercentage} | ${key.humanReadable}`}
style={{
backgroundColor: '#f0f2f5',
color: '#000',
padding: '4px',
textAlign: 'center'
}}>
{key.status} [{lifecycleData.statusCounts[key.status]}] ({key.roundedPercentage})
</div>
</Tag>
))}
</div>
</Card>
<Card style={{marginTop: "5px"}} type='inner' title={t("job_lifecycle.titles.top_durations")}>
<Table size="small" pagination={false} columns={columns}
dataSource={lifecycleData.summations.sort((a, b) => b.value - a.value).slice(0, 3)}/>
</Card>
</div>
</LoadingSkeleton>
</Card>
);
}
export const JobLifecycleDashboardGQL = `
job_lifecycle: jobs(where: {
actual_in: {
_gte: "${moment().subtract(45, 'days').toISOString()}"
}
}) {
id
actual_in
} `;

View File

@@ -1,380 +1,391 @@
import Icon, { SyncOutlined } from "@ant-design/icons"; import Icon, {SyncOutlined} from "@ant-design/icons";
import { gql, useMutation, useQuery } from "@apollo/client"; import {gql, useMutation, useQuery} from "@apollo/client";
import { Button, Dropdown, Menu, PageHeader, Space, notification } from "antd"; import {Button, Dropdown, Menu, notification, PageHeader, Space} from "antd";
import i18next from "i18next"; import i18next from "i18next";
import _ from "lodash"; import _ from "lodash";
import moment from "moment"; import moment from "moment";
import React, { useState } from "react"; import React, {useState} from "react";
import { Responsive, WidthProvider } from "react-grid-layout"; import {Responsive, WidthProvider} from "react-grid-layout";
import { useTranslation } from "react-i18next"; import {useTranslation} from "react-i18next";
import { MdClose } from "react-icons/md"; import {MdClose} from "react-icons/md";
import { connect } from "react-redux"; import {connect} from "react-redux";
import { createStructuredSelector } from "reselect"; import {createStructuredSelector} from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import {logImEXEvent} from "../../firebase/firebase.utils";
import { UPDATE_DASHBOARD_LAYOUT } from "../../graphql/user.queries"; import {UPDATE_DASHBOARD_LAYOUT} from "../../graphql/user.queries";
import { import {selectBodyshop, selectCurrentUser,} from "../../redux/user/user.selectors";
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import DashboardMonthlyEmployeeEfficiency, { import DashboardMonthlyEmployeeEfficiency, {
DashboardMonthlyEmployeeEfficiencyGql, DashboardMonthlyEmployeeEfficiencyGql,
} from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component"; } from "../dashboard-components/monthly-employee-efficiency/monthly-employee-efficiency.component";
import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component"; import DashboardMonthlyJobCosting from "../dashboard-components/monthly-job-costing/monthly-job-costing.component";
import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component"; import DashboardMonthlyLaborSales from "../dashboard-components/monthly-labor-sales/monthly-labor-sales.component";
import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component"; import DashboardMonthlyPartsSales from "../dashboard-components/monthly-parts-sales/monthly-parts-sales.component";
import DashboardMonthlyRevenueGraph, { import DashboardMonthlyRevenueGraph, {
DashboardMonthlyRevenueGraphGql, DashboardMonthlyRevenueGraphGql,
} from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component"; } from "../dashboard-components/monthly-revenue-graph/monthly-revenue-graph.component";
import DashboardProjectedMonthlySales, { import DashboardProjectedMonthlySales, {
DashboardProjectedMonthlySalesGql, DashboardProjectedMonthlySalesGql,
} from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component"; } from "../dashboard-components/pojected-monthly-sales/projected-monthly-sales.component";
import DashboardTotalProductionDollars from "../dashboard-components/total-production-dollars/total-production-dollars.component"; import DashboardTotalProductionDollars
from "../dashboard-components/total-production-dollars/total-production-dollars.component";
import DashboardTotalProductionHours, { import DashboardTotalProductionHours, {
DashboardTotalProductionHoursGql, DashboardTotalProductionHoursGql,
} from "../dashboard-components/total-production-hours/total-production-hours.component"; } from "../dashboard-components/total-production-hours/total-production-hours.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
//Combination of the following: //Combination of the following:
// /node_modules/react-grid-layout/css/styles.css // /node_modules/react-grid-layout/css/styles.css
// /node_modules/react-resizable/css/styles.css // /node_modules/react-resizable/css/styles.css
import DashboardScheduledInToday, { import DashboardScheduledInToday, {
DashboardScheduledInTodayGql, DashboardScheduledInTodayGql,
} from "../dashboard-components/scheduled-in-today/scheduled-in-today.component"; } from "../dashboard-components/scheduled-in-today/scheduled-in-today.component";
import DashboardScheduledOutToday, { import DashboardScheduledOutToday, {
DashboardScheduledOutTodayGql, DashboardScheduledOutTodayGql,
} from "../dashboard-components/scheduled-out-today/scheduled-out-today.component"; } from "../dashboard-components/scheduled-out-today/scheduled-out-today.component";
import JobLifecycleDashboardComponent, {
JobLifecycleDashboardGQL
} from "../dashboard-components/job-lifecycle/job-lifecycle-dashboard.component";
import "./dashboard-grid.styles.scss"; import "./dashboard-grid.styles.scss";
import { GenerateDashboardData } from "./dashboard-grid.utils"; import {GenerateDashboardData} from "./dashboard-grid.utils";
const ResponsiveReactGridLayout = WidthProvider(Responsive); const ResponsiveReactGridLayout = WidthProvider(Responsive);
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export function DashboardGridComponent({ currentUser, bodyshop }) { export function DashboardGridComponent({currentUser, bodyshop}) {
const { t } = useTranslation(); const {t} = useTranslation();
const [state, setState] = useState({ const [state, setState] = useState({
...(bodyshop.associations[0].user.dashboardlayout ...(bodyshop.associations[0].user.dashboardlayout
? bodyshop.associations[0].user.dashboardlayout ? bodyshop.associations[0].user.dashboardlayout
: { items: [], layout: {}, layouts: [] }), : {items: [], layout: {}, layouts: []}),
});
const { loading, error, data, refetch } = useQuery(
createDashboardQuery(state),
{ fetchPolicy: "network-only", nextFetchPolicy: "network-only" }
);
const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT);
const handleLayoutChange = async (layout, layouts) => {
logImEXEvent("dashboard_change_layout");
setState({ ...state, layout, layouts });
const result = await updateLayout({
variables: {
email: currentUser.email,
layout: { ...state, layout, layouts },
},
}); });
if (!!result.errors) {
notification["error"]({
message: t("dashboard.errors.updatinglayout", {
message: JSON.stringify(result.errors),
}),
});
}
};
const handleRemoveComponent = (key) => {
logImEXEvent("dashboard_remove_component", { name: key });
const idxToRemove = state.items.findIndex((i) => i.i === key);
const items = _.cloneDeep(state.items); const {loading, error, data, refetch} = useQuery(
createDashboardQuery(state),
{fetchPolicy: "network-only", nextFetchPolicy: "network-only"}
);
items.splice(idxToRemove, 1); const [updateLayout] = useMutation(UPDATE_DASHBOARD_LAYOUT);
setState({ ...state, items });
};
const handleAddComponent = (e) => { const handleLayoutChange = async (layout, layouts) => {
logImEXEvent("dashboard_add_component", { name: e }); logImEXEvent("dashboard_change_layout");
setState({
...state,
items: [
...state.items,
{
i: e.key,
x: (state.items.length * 2) % (state.cols || 12),
y: 99, // puts it at the bottom
w: componentList[e.key].w || 2,
h: componentList[e.key].h || 2,
},
],
});
};
const dashboarddata = React.useMemo( setState({...state, layout, layouts});
() => GenerateDashboardData(data),
[data]
);
const existingLayoutKeys = state.items.map((i) => i.i);
const addComponentOverlay = (
<Menu onClick={handleAddComponent}>
{Object.keys(componentList).map((key) => (
<Menu.Item
key={key}
value={key}
disabled={existingLayoutKeys.includes(key)}
>
{componentList[key].label}
</Menu.Item>
))}
</Menu>
);
if (error) return <AlertComponent message={error.message} type="error" />; const result = await updateLayout({
variables: {
return ( email: currentUser.email,
<div> layout: {...state, layout, layouts},
<PageHeader },
extra={ });
<Space> if (!!result.errors) {
<Button onClick={() => refetch()}> notification["error"]({
<SyncOutlined /> message: t("dashboard.errors.updatinglayout", {
</Button> message: JSON.stringify(result.errors),
<Dropdown overlay={addComponentOverlay} trigger={["click"]}> }),
<Button>{t("dashboard.actions.addcomponent")}</Button> });
</Dropdown>
</Space>
} }
/> };
const handleRemoveComponent = (key) => {
logImEXEvent("dashboard_remove_component", {name: key});
const idxToRemove = state.items.findIndex((i) => i.i === key);
<ResponsiveReactGridLayout const items = _.cloneDeep(state.items);
className="layout"
breakpoints={{ lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0 }} items.splice(idxToRemove, 1);
cols={{ lg: 12, md: 10, sm: 6, xs: 4, xxs: 2 }} setState({...state, items});
width="100%" };
layouts={state.layouts}
onLayoutChange={handleLayoutChange} const handleAddComponent = (e) => {
// onBreakpointChange={onBreakpointChange} logImEXEvent("dashboard_add_component", {name: e});
> setState({
{state.items.map((item, index) => { ...state,
const TheComponent = componentList[item.i].component; items: [
return ( ...state.items,
<div {
key={item.i} i: e.key,
data-grid={{ x: (state.items.length * 2) % (state.cols || 12),
...item, y: 99, // puts it at the bottom
minH: componentList[item.i].minH || 1, w: componentList[e.key].w || 2,
minW: componentList[item.i].minW || 1, h: componentList[e.key].h || 2,
}} },
],
});
};
const dashboarddata = React.useMemo(
() => GenerateDashboardData(data),
[data]
);
const existingLayoutKeys = state.items.map((i) => i.i);
const addComponentOverlay = (
<Menu onClick={handleAddComponent}>
{Object.keys(componentList).map((key) => (
<Menu.Item
key={key}
value={key}
disabled={existingLayoutKeys.includes(key)}
>
{componentList[key].label}
</Menu.Item>
))}
</Menu>
);
if (error) return <AlertComponent message={error.message} type="error"/>;
return (
<div>
<PageHeader
extra={
<Space>
<Button onClick={() => refetch()}>
<SyncOutlined/>
</Button>
<Dropdown overlay={addComponentOverlay} trigger={["click"]}>
<Button>{t("dashboard.actions.addcomponent")}</Button>
</Dropdown>
</Space>
}
/>
<ResponsiveReactGridLayout
className="layout"
breakpoints={{lg: 1200, md: 996, sm: 768, xs: 480, xxs: 0}}
cols={{lg: 12, md: 10, sm: 6, xs: 4, xxs: 2}}
width="100%"
layouts={state.layouts}
onLayoutChange={handleLayoutChange}
// onBreakpointChange={onBreakpointChange}
> >
<LoadingSkeleton loading={loading}> {state.items.map((item, index) => {
<Icon const TheComponent = componentList[item.i].component;
component={MdClose} return (
key={item.i} <div
style={{ key={item.i}
position: "absolute", data-grid={{
zIndex: "2", ...item,
right: ".25rem", minH: componentList[item.i].minH || 1,
top: ".25rem", minW: componentList[item.i].minW || 1,
cursor: "pointer", }}
}} >
onClick={() => handleRemoveComponent(item.i)} <LoadingSkeleton loading={loading}>
/> <Icon
<TheComponent className="dashboard-card" data={dashboarddata} /> component={MdClose}
</LoadingSkeleton> key={item.i}
</div> style={{
); position: "absolute",
})} zIndex: "2",
</ResponsiveReactGridLayout> right: ".25rem",
</div> top: ".25rem",
); cursor: "pointer",
}}
onClick={() => handleRemoveComponent(item.i)}
/>
<TheComponent className="dashboard-card" bodyshop={bodyshop} data={dashboarddata}/>
</LoadingSkeleton>
</div>
);
})}
</ResponsiveReactGridLayout>
</div>
);
} }
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
)(DashboardGridComponent); )(DashboardGridComponent);
const componentList = { const componentList = {
ProductionDollars: { ProductionDollars: {
label: i18next.t("dashboard.titles.productiondollars"), label: i18next.t("dashboard.titles.productiondollars"),
component: DashboardTotalProductionDollars, component: DashboardTotalProductionDollars,
gqlFragment: null, gqlFragment: null,
w: 1, w: 1,
h: 1, h: 1,
minW: 2, minW: 2,
minH: 1, minH: 1,
}, },
ProductionHours: { ProductionHours: {
label: i18next.t("dashboard.titles.productionhours"), label: i18next.t("dashboard.titles.productionhours"),
component: DashboardTotalProductionHours, component: DashboardTotalProductionHours,
gqlFragment: DashboardTotalProductionHoursGql, gqlFragment: DashboardTotalProductionHoursGql,
w: 3, w: 3,
h: 1, h: 1,
minW: 3, minW: 3,
minH: 1, minH: 1,
}, },
ProjectedMonthlySales: { ProjectedMonthlySales: {
label: i18next.t("dashboard.titles.projectedmonthlysales"), label: i18next.t("dashboard.titles.projectedmonthlysales"),
component: DashboardProjectedMonthlySales, component: DashboardProjectedMonthlySales,
gqlFragment: DashboardProjectedMonthlySalesGql, gqlFragment: DashboardProjectedMonthlySalesGql,
w: 2, w: 2,
h: 1, h: 1,
minW: 2, minW: 2,
minH: 1, minH: 1,
}, },
MonthlyRevenueGraph: { MonthlyRevenueGraph: {
label: i18next.t("dashboard.titles.monthlyrevenuegraph"), label: i18next.t("dashboard.titles.monthlyrevenuegraph"),
component: DashboardMonthlyRevenueGraph, component: DashboardMonthlyRevenueGraph,
gqlFragment: DashboardMonthlyRevenueGraphGql, gqlFragment: DashboardMonthlyRevenueGraphGql,
w: 4, w: 4,
h: 2, h: 2,
minW: 4, minW: 4,
minH: 2, minH: 2,
}, },
MonthlyJobCosting: { MonthlyJobCosting: {
label: i18next.t("dashboard.titles.monthlyjobcosting"), label: i18next.t("dashboard.titles.monthlyjobcosting"),
component: DashboardMonthlyJobCosting, component: DashboardMonthlyJobCosting,
gqlFragment: null, gqlFragment: null,
minW: 6, minW: 6,
minH: 3, minH: 3,
w: 6, w: 6,
h: 3, h: 3,
}, },
MonthlyPartsSales: { MonthlyPartsSales: {
label: i18next.t("dashboard.titles.monthlypartssales"), label: i18next.t("dashboard.titles.monthlypartssales"),
component: DashboardMonthlyPartsSales, component: DashboardMonthlyPartsSales,
gqlFragment: null, gqlFragment: null,
minW: 2, minW: 2,
minH: 2, minH: 2,
w: 2, w: 2,
h: 2, h: 2,
}, },
MonthlyLaborSales: { MonthlyLaborSales: {
label: i18next.t("dashboard.titles.monthlylaborsales"), label: i18next.t("dashboard.titles.monthlylaborsales"),
component: DashboardMonthlyLaborSales, component: DashboardMonthlyLaborSales,
gqlFragment: null, gqlFragment: null,
minW: 2, minW: 2,
minH: 2, minH: 2,
w: 2, w: 2,
h: 2, h: 2,
}, },
MonthlyEmployeeEfficency: { // Typo in Efficency should be Efficiency, but changing it would reset users dashboard settings
label: i18next.t("dashboard.titles.monthlyemployeeefficiency"), MonthlyEmployeeEfficency: {
component: DashboardMonthlyEmployeeEfficiency, label: i18next.t("dashboard.titles.monthlyemployeeefficiency"),
gqlFragment: DashboardMonthlyEmployeeEfficiencyGql, component: DashboardMonthlyEmployeeEfficiency,
minW: 2, gqlFragment: DashboardMonthlyEmployeeEfficiencyGql,
minH: 2, minW: 2,
w: 2, minH: 2,
h: 2, w: 2,
}, h: 2,
ScheduleInToday: { },
label: i18next.t("dashboard.titles.scheduledintoday"), ScheduleInToday: {
component: DashboardScheduledInToday, label: i18next.t("dashboard.titles.scheduledintoday"),
gqlFragment: DashboardScheduledInTodayGql, component: DashboardScheduledInToday,
minW: 6, gqlFragment: DashboardScheduledInTodayGql,
minH: 2, minW: 6,
w: 10, minH: 2,
h: 3, w: 10,
}, h: 3,
ScheduleOutToday: { },
label: i18next.t("dashboard.titles.scheduledouttoday"), ScheduleOutToday: {
component: DashboardScheduledOutToday, label: i18next.t("dashboard.titles.scheduledouttoday"),
gqlFragment: DashboardScheduledOutTodayGql, component: DashboardScheduledOutToday,
minW: 6, gqlFragment: DashboardScheduledOutTodayGql,
minH: 2, minW: 6,
w: 10, minH: 2,
h: 3, w: 10,
}, h: 3,
},
JobLifecycle: {
label: i18next.t("dashboard.titles.joblifecycle"),
component: JobLifecycleDashboardComponent,
gqlFragment: JobLifecycleDashboardGQL,
minW: 6,
minH: 3,
w: 6,
h: 3,
},
}; };
const createDashboardQuery = (state) => { const createDashboardQuery = (state) => {
const componentBasedAdditions = const componentBasedAdditions =
state && state &&
Array.isArray(state.layout) && Array.isArray(state.layout) &&
state.layout state.layout
.map((item, index) => componentList[item.i].gqlFragment || "") .map((item, index) => componentList[item.i].gqlFragment || "")
.join(""); .join("");
return gql` return gql`
query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""} query QUERY_DASHBOARD_DETAILS { ${componentBasedAdditions || ""}
monthly_sales: jobs(where: {_and: [ monthly_sales: jobs(where: {_and: [
{ voided: {_eq: false}}, { voided: {_eq: false}},
{date_invoiced: {_gte: "${moment() {date_invoiced: {_gte: "${moment()
.startOf("month") .startOf("month")
.startOf("day") .startOf("day")
.toISOString()}"}}, {date_invoiced: {_lte: "${moment() .toISOString()}"}}, {date_invoiced: {_lte: "${moment()
.endOf("month") .endOf("month")
.endOf("day") .endOf("day")
.toISOString()}"}}]}) { .toISOString()}"}}]}) {
id id
ro_number ro_number
date_invoiced date_invoiced
job_totals job_totals
rate_la1 rate_la1
rate_la2 rate_la2
rate_la3 rate_la3
rate_la4 rate_la4
rate_laa rate_laa
rate_lab rate_lab
rate_lad rate_lad
rate_lae rate_lae
rate_laf rate_laf
rate_lag rate_lag
rate_lam rate_lam
rate_lar rate_lar
rate_las rate_las
rate_lau rate_lau
rate_ma2s rate_ma2s
rate_ma2t rate_ma2t
rate_ma3s rate_ma3s
rate_mabl rate_mabl
rate_macs rate_macs
rate_mahw rate_mahw
rate_mapa rate_mapa
rate_mash rate_mash
rate_matd rate_matd
joblines(where: { removed: { _eq: false } }) { joblines(where: { removed: { _eq: false } }) {
id id
mod_lbr_ty mod_lbr_ty
mod_lb_hrs mod_lb_hrs
act_price act_price
part_qty part_qty
part_type part_type
} }
} }
production_jobs: jobs(where: { inproduction: { _eq: true } }) { production_jobs: jobs(where: { inproduction: { _eq: true } }) {
id id
ro_number ro_number
ins_co_nm ins_co_nm
job_totals job_totals
joblines(where: { removed: { _eq: false } }) { joblines(where: { removed: { _eq: false } }) {
id id
mod_lbr_ty mod_lbr_ty
mod_lb_hrs mod_lb_hrs
act_price act_price
part_qty part_qty
part_type part_type
} }
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) { labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
aggregate { aggregate {
sum { sum {
mod_lb_hrs mod_lb_hrs
} }
} }
} }
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) { larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
aggregate { aggregate {
sum { sum {
mod_lb_hrs mod_lb_hrs
} }
} }
} }
} }
}`; }`;
}; };

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

@@ -53,6 +53,7 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
insertAuditTrail({ insertAuditTrail({
jobid: event.job.id, jobid: event.job.id,
operation: AuditTrailMapping.appointmentcancel(lost_sale_reason), operation: AuditTrailMapping.appointmentcancel(lost_sale_reason),
type: "appointmentcancel",
}) })
); );
} }

View File

@@ -28,8 +28,8 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function JobChecklistForm({ export function JobChecklistForm({
@@ -183,6 +183,7 @@ export function JobChecklistForm({
(type === "intake" && bodyshop.md_ro_statuses.default_arrived) || (type === "intake" && bodyshop.md_ro_statuses.default_arrived) ||
(type === "deliver" && bodyshop.md_ro_statuses.default_delivered) (type === "deliver" && bodyshop.md_ro_statuses.default_delivered)
), ),
type: "jobchecklist",
}); });
} else { } else {
notification["error"]({ notification["error"]({

View File

@@ -18,10 +18,8 @@ export default function JobDetailCardsTotalsComponent({ loading, data }) {
/> />
<Statistic <Statistic
className="imex-flex-row__margin-large" className="imex-flex-row__margin-large"
title={t("jobs.fields.ded_amt")} title={t("jobs.fields.customerowing")}
value={Dinero({ value={Dinero(data.job_totals.totals.custPayable.total).toFormat()}
amount: Math.round((data.ded_amt || 0) * 100),
}).toFormat()}
/> />
<Statistic <Statistic
className="imex-flex-row__margin-large" className="imex-flex-row__margin-large"

View File

@@ -1,14 +1,26 @@
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 { connect } from "react-redux";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries"; import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors.js";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import BillDetailEditcontainer from "../bill-detail-edit/bill-detail-edit.container.jsx";
export default function JobLinesExpander({ jobline, jobid }) { const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
export function JobLinesExpander({ jobline, jobid, technician }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, { const { loading, error, data } = useQuery(GET_JOB_LINE_ORDERS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
@@ -31,15 +43,31 @@ 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}`} {!technician ? (
> <Link
{line.parts_order.order_number} to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}
</Link> >
<DateFormatter>{line.parts_order.order_date}</DateFormatter> {line.parts_order.order_number}
{line.parts_order.vendor.name} </Link>
</Space> ) : (
`${line.parts_order.order_number}`
)}
</Col>
<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>
)) ))
) : ( ) : (
@@ -51,17 +79,22 @@ export default function JobLinesExpander({ jobline, jobid }) {
</Col> </Col>
<Col md={24} lg={12}> <Col md={24} lg={12}>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title> <Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<BillDetailEditcontainer />
<Timeline> <Timeline>
{data.billlines.length > 0 ? ( {data.billlines.length > 0 ? (
data.billlines.map((line) => ( data.billlines.map((line) => (
<Timeline.Item key={line.id}> <Timeline.Item key={line.id}>
<Row wrap> <Row wrap>
<Col span={4}> <Col span={4}>
<Link {!technician ? (
to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`} <Link
> to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}
{line.bill.invoice_number} >
</Link> {line.bill.invoice_number}
</Link>
) : (
`${line.bill.invoice_number}`
)}
</Col> </Col>
<Col span={4}> <Col span={4}>
<span> <span>
@@ -71,7 +104,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>
@@ -83,9 +116,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
</Timeline.Item> </Timeline.Item>
)) ))
) : ( ) : (
<Timeline.Item> <Timeline.Item>{t("bills.labels.nobilllines")}</Timeline.Item>
{t("parts_orders.labels.notyetordered")}
</Timeline.Item>
)} )}
</Timeline> </Timeline>
</Col> </Col>

View File

@@ -1,12 +1,12 @@
import { import {
DeleteFilled, DeleteFilled,
EditFilled,
FilterFilled, FilterFilled,
HomeOutlined,
MinusCircleTwoTone,
PlusCircleTwoTone,
SyncOutlined, SyncOutlined,
WarningFilled, WarningFilled,
EditFilled,
PlusCircleTwoTone,
MinusCircleTwoTone,
HomeOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { import {
@@ -20,6 +20,8 @@ import {
Tag, Tag,
} from "antd"; } from "antd";
import axios from "axios"; import axios from "axios";
import _ from "lodash";
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";
@@ -28,23 +30,19 @@ import { DELETE_JOB_LINE_BY_PK } from "../../graphql/jobs-lines.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 { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { onlyUnique } from "../../utils/arrayHelper"; import { onlyUnique } from "../../utils/arrayHelper";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component"; import JobLineLocationPopup from "../job-line-location-popup/job-line-location-popup.component";
import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component"; import JobLineNotePopup from "../job-line-note-popup/job-line-note-popup.component";
import JobLineStatusPopup from "../job-line-status-popup/job-line-status-popup.component"; import JobLineStatusPopup from "../job-line-status-popup/job-line-status-popup.component";
import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-reference.component"; import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-reference.component";
// import AllocationsAssignmentContainer from "../allocations-assignment/allocations-assignment.container"; import PartsOrderDrawer from "../parts-order-list-table/parts-order-list-table-drawer.component";
// import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
// import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container"; import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import _ from "lodash";
import JobCreateIOU from "../job-create-iou/job-create-iou.component";
import JobLinesExpander from "./job-lines-expander.component"; import JobLinesExpander from "./job-lines-expander.component";
import { selectBodyshop } from "../../redux/user/user.selectors";
import moment from "moment";
import JobLineConvertToLabor from "../job-line-convert-to-labor/job-line-convert-to-labor.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -57,6 +55,8 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setModalContext({ context: context, modal: "jobLineEdit" })), dispatch(setModalContext({ context: context, modal: "jobLineEdit" })),
setPartsOrderContext: (context) => setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })), dispatch(setModalContext({ context: context, modal: "partsOrder" })),
setPartsReceiveContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsReceive" })),
setBillEnterContext: (context) => setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })), dispatch(setModalContext({ context: context, modal: "billEnter" })),
}); });
@@ -66,6 +66,7 @@ export function JobLinesComponent({
jobRO, jobRO,
technician, technician,
setPartsOrderContext, setPartsOrderContext,
setPartsReceiveContext,
loading, loading,
refetch, refetch,
jobLines, jobLines,
@@ -74,6 +75,8 @@ export function JobLinesComponent({
setJobLineEditContext, setJobLineEditContext,
form, form,
setBillEnterContext, setBillEnterContext,
billsQuery,
handlePartsOrderOnRowClick,
}) { }) {
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK); const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
@@ -341,7 +344,7 @@ export function JobLinesComponent({
key: "actions", key: "actions",
render: (text, record) => ( render: (text, record) => (
<Space> <Space>
{(record.manual_line || jobIsPrivate) && ( {(record.manual_line || jobIsPrivate) && !technician && (
<> <>
<Button <Button
disabled={jobRO} disabled={jobRO}
@@ -424,6 +427,14 @@ export function JobLinesComponent({
return ( return (
<div> <div>
<PartsOrderModalContainer /> <PartsOrderModalContainer />
{!technician && (
<PartsOrderDrawer
job={job}
billsQuery={billsQuery}
handleOnRowClick={handlePartsOrderOnRowClick}
setPartsReceiveContext={setPartsReceiveContext}
/>
)}
<PageHeader <PageHeader
title={t("jobs.labels.estimatelines")} title={t("jobs.labels.estimatelines")}
extra={ extra={

View File

@@ -1,6 +1,15 @@
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import JobLinesComponent from "./job-lines.component"; import JobLinesComponent from "./job-lines.component";
function JobLinesContainer({ job, joblines, refetch, form, ...rest }) { function JobLinesContainer({
job,
joblines,
billsQuery,
handleBillOnRowClick,
handlePartsOrderOnRowClick,
refetch,
form,
...rest
}) {
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const jobLines = useMemo(() => { const jobLines = useMemo(() => {
@@ -37,6 +46,9 @@ function JobLinesContainer({ job, joblines, refetch, form, ...rest }) {
<JobLinesComponent <JobLinesComponent
refetch={refetch} refetch={refetch}
jobLines={jobLines} jobLines={jobLines}
billsQuery={billsQuery}
handleBillOnRowClick={handleBillOnRowClick}
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
setSearchText={setSearchText} setSearchText={setSearchText}
job={job} job={job}
form={form} form={form}

View File

@@ -23,7 +23,6 @@ export function JobEmployeeAssignments({
jobRO, jobRO,
body, body,
refinish, refinish,
prep, prep,
csr, csr,
handleAdd, handleAdd,
@@ -78,7 +77,7 @@ export function JobEmployeeAssignments({
setVisibility(false); setVisibility(false);
}} }}
> >
Assign {t("allocations.actions.assign")}
</Button> </Button>
<Button onClick={() => setVisibility(false)}>Close</Button> <Button onClick={() => setVisibility(false)}>Close</Button>
</Space> </Space>

View File

@@ -14,8 +14,8 @@ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
@@ -43,12 +43,13 @@ export function JobEmployeeAssignmentsContainer({
}); });
if (refetch) refetch(); if (refetch) refetch();
insertAuditTrail({ if (!!!result.errors) {
jobid: job.id, insertAuditTrail({
operation: AuditTrailMapping.jobassignmentchange(operation, name), jobid: job.id,
}); operation: AuditTrailMapping.jobassignmentchange(operation, name),
type: "jobassignmentchange",
if (!!result.errors) { });
} else {
notification["error"]({ notification["error"]({
message: t("jobs.errors.assigning", { message: t("jobs.errors.assigning", {
message: JSON.stringify(result.errors), message: JSON.stringify(result.errors),
@@ -66,17 +67,19 @@ export function JobEmployeeAssignmentsContainer({
variables: { jobId: job.id, job: { [empAssignment]: null } }, variables: { jobId: job.id, job: { [empAssignment]: null } },
}); });
if (!!result.errors) { if (!!!result.errors) {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobassignmentremoved(operation),
type: "jobassignmentremoved",
});
} else {
notification["error"]({ notification["error"]({
message: t("jobs.errors.assigning", { message: t("jobs.errors.assigning", {
message: JSON.stringify(result.errors), message: JSON.stringify(result.errors),
}), }),
}); });
} }
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobassignmentremoved(operation),
});
setLoading(false); setLoading(false);
}; };

View File

@@ -23,14 +23,18 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries"; import { UPDATE_JOB_LINE } from "../../graphql/jobs-lines.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
mapDispatchToProps mapDispatchToProps
@@ -41,6 +45,7 @@ export function JobLineConvertToLabor({
jobline, jobline,
job, job,
insertAuditTrail, insertAuditTrail,
technician,
...otherBtnProps ...otherBtnProps
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -113,6 +118,7 @@ export function JobLineConvertToLabor({
hours: calculateAdjustment({ mod_lbr_ty, job, jobline }).toFixed(1), hours: calculateAdjustment({ mod_lbr_ty, job, jobline }).toFixed(1),
mod_lbr_ty, mod_lbr_ty,
}), }),
type: "jobmodifylbradj",
}); });
setLoading(false); setLoading(false);
setVisibility(false); setVisibility(false);
@@ -221,7 +227,7 @@ export function JobLineConvertToLabor({
return ( return (
<> <>
{children} {children}
{jobline.act_price !== 0 && ( {jobline.act_price !== 0 && !technician && (
<Popover <Popover
disabled={jobline.convertedtolbr} disabled={jobline.convertedtolbr}
content={overlay} content={overlay}

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

@@ -14,8 +14,8 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminStatus); export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminStatus);
@@ -32,6 +32,7 @@ export function JobsAdminStatus({ insertAuditTrail, bodyshop, job }) {
insertAuditTrail({ insertAuditTrail({
jobid: job.id, jobid: job.id,
operation: AuditTrailMapping.admin_jobstatuschange(status), operation: AuditTrailMapping.admin_jobstatuschange(status),
type: "admin_jobstatuschange",
}); });
// refetch(); // refetch();
}) })

View File

@@ -20,8 +20,8 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect( export default connect(
@@ -57,6 +57,7 @@ export function JobsAdminDatesChange({ insertAuditTrail, job }) {
? DateTimeFormat(changedAuditFields[key]) ? DateTimeFormat(changedAuditFields[key])
: changedAuditFields[key] : changedAuditFields[key]
), ),
type: "admin_jobfieldchange",
}); });
}); });

View File

@@ -23,8 +23,8 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
@@ -59,6 +59,7 @@ export function JobAdminMarkReexport({
insertAuditTrail({ insertAuditTrail({
jobid: job.id, jobid: job.id,
operation: AuditTrailMapping.admin_jobmarkforreexport(), operation: AuditTrailMapping.admin_jobmarkforreexport(),
type: "admin_jobmarkforreexport",
}); });
} else { } else {
notification["error"]({ notification["error"]({
@@ -99,6 +100,7 @@ export function JobAdminMarkReexport({
insertAuditTrail({ insertAuditTrail({
jobid: job.id, jobid: job.id,
operation: AuditTrailMapping.admin_jobmarkexported(), operation: AuditTrailMapping.admin_jobmarkexported(),
type: "admin_jobmarkexported",
}); });
} else { } else {
notification["error"]({ notification["error"]({
@@ -124,6 +126,7 @@ export function JobAdminMarkReexport({
insertAuditTrail({ insertAuditTrail({
jobid: job.id, jobid: job.id,
operation: AuditTrailMapping.admin_jobuninvoice(), operation: AuditTrailMapping.admin_jobuninvoice(),
type: "admin_jobuninvoice",
}); });
} else { } else {
notification["error"]({ notification["error"]({

View File

@@ -10,8 +10,8 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({}); const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminRemoveAR); export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminRemoveAR);
@@ -34,6 +34,7 @@ export function JobsAdminRemoveAR({ insertAuditTrail, job }) {
insertAuditTrail({ insertAuditTrail({
jobid: job.id, jobid: job.id,
operation: AuditTrailMapping.admin_job_remove_from_ar(value), operation: AuditTrailMapping.admin_job_remove_from_ar(value),
type: "admin_job_remove_from_ar",
}); });
setSwitchValue(value); setSwitchValue(value);
} else { } else {

View File

@@ -17,8 +17,8 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminUnvoid); export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminUnvoid);
@@ -49,6 +49,7 @@ export function JobsAdminUnvoid({
insertAuditTrail({ insertAuditTrail({
jobid: job.id, jobid: job.id,
operation: AuditTrailMapping.admin_jobunvoid(), operation: AuditTrailMapping.admin_jobunvoid(),
type: "admin_jobunvoid",
}); });
} else { } else {
notification["error"]({ notification["error"]({

View File

@@ -47,8 +47,8 @@ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function JobsAvailableContainer({ export function JobsAvailableContainer({
bodyshop, bodyshop,
@@ -190,6 +190,7 @@ export function JobsAvailableContainer({
insertAuditTrail({ insertAuditTrail({
jobid: r.data.insert_jobs.returning[0].id, jobid: r.data.insert_jobs.returning[0].id,
operation: AuditTrailMapping.jobimported(), operation: AuditTrailMapping.jobimported(),
type: "jobimported",
}); });
deleteJob({ deleteJob({
@@ -350,6 +351,7 @@ export function JobsAvailableContainer({
insertAuditTrail({ insertAuditTrail({
jobid: selectedJob, jobid: selectedJob,
operation: AuditTrailMapping.jobsupplement(), operation: AuditTrailMapping.jobsupplement(),
type: "jobsupplement",
}); });
} }
}; };

View File

@@ -16,8 +16,8 @@ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) { export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
@@ -35,6 +35,7 @@ export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
insertAuditTrail({ insertAuditTrail({
jobid: job.id, jobid: job.id,
operation: AuditTrailMapping.jobstatuschange(status), operation: AuditTrailMapping.jobstatuschange(status),
type: "jobstatuschange",
}); });
// refetch(); // refetch();
}) })

View File

@@ -23,8 +23,8 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
function updateJobCache(items) { function updateJobCache(items) {
@@ -192,6 +192,7 @@ export function JobsCloseExportButton({
insertAuditTrail({ insertAuditTrail({
jobid: jobId, jobid: jobId,
operation: AuditTrailMapping.jobexported(), operation: AuditTrailMapping.jobexported(),
type: "jobexported",
}); });
updateJobCache( updateJobCache(
jobUpdateResponse.data.update_jobs.returning.map((job) => job.id) jobUpdateResponse.data.update_jobs.returning.map((job) => job.id)
@@ -217,6 +218,7 @@ export function JobsCloseExportButton({
insertAuditTrail({ insertAuditTrail({
jobid: jobId, jobid: jobId,
operation: AuditTrailMapping.jobexported(), operation: AuditTrailMapping.jobexported(),
type: "jobexported",
}); });
updateJobCache([ updateJobCache([
...new Set( ...new Set(

View File

@@ -25,8 +25,8 @@ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function JobsConvertButton({ export function JobsConvertButton({
@@ -78,6 +78,7 @@ export function JobsConvertButton({
operation: AuditTrailMapping.jobconverted( operation: AuditTrailMapping.jobconverted(
res.data.update_jobs.returning[0].ro_number res.data.update_jobs.returning[0].ro_number
), ),
type: "jobconverted",
}); });
setVisible(false); setVisible(false);

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

@@ -29,6 +29,7 @@ export default function AddToProduction(
insertAuditTrail({ insertAuditTrail({
jobid: jobId, jobid: jobId,
operation: AuditTrailMapping.jobinproductionchange(!remove), operation: AuditTrailMapping.jobinproductionchange(!remove),
type: "jobinproductionchange",
}) })
); );
if (completionCallback) completionCallback(); if (completionCallback) completionCallback();

View File

@@ -52,8 +52,8 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setModalContext({ context: context, modal: "timeTicket" })), dispatch(setModalContext({ context: context, modal: "timeTicket" })),
setCardPaymentContext: (context) => setCardPaymentContext: (context) =>
dispatch(setModalContext({ context: context, modal: "cardPayment" })), dispatch(setModalContext({ context: context, modal: "cardPayment" })),
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function JobsDetailHeaderActions({ export function JobsDetailHeaderActions({
@@ -115,6 +115,7 @@ export function JobsDetailHeaderActions({
? !job.production_vars.alert ? !job.production_vars.alert
: true : true
), ),
type: "alertToggle",
}); });
}; };
@@ -134,6 +135,7 @@ export function JobsDetailHeaderActions({
operation: AuditTrailMapping.jobsuspend( operation: AuditTrailMapping.jobsuspend(
!!job.suspended ? !job.suspended : true !!job.suspended ? !job.suspended : true
), ),
type: "jobsuspend",
}); });
}; };
@@ -190,6 +192,7 @@ export function JobsDetailHeaderActions({
jobid: job.id, jobid: job.id,
operation: operation:
AuditTrailMapping.appointmentcancel(lost_sale_reason), AuditTrailMapping.appointmentcancel(lost_sale_reason),
type: "appointmentcancel",
}); });
return; return;
} }
@@ -301,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 },
}); });
}} }}
@@ -549,6 +552,7 @@ export function JobsDetailHeaderActions({
insertAuditTrail({ insertAuditTrail({
jobid: job.id, jobid: job.id,
operation: AuditTrailMapping.jobvoid(), operation: AuditTrailMapping.jobvoid(),
type: "jobvoid",
}); });
//go back to jobs list. //go back to jobs list.
history.push(`/manage/`); history.push(`/manage/`);

View File

@@ -4,7 +4,8 @@ import {
PauseCircleOutlined, PauseCircleOutlined,
WarningFilled, WarningFilled,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Card, Col, 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";
@@ -62,6 +63,13 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
${job.v_make_desc || ""} ${job.v_make_desc || ""}
${job.v_model_desc || ""}`.trim(); ${job.v_model_desc || ""}`.trim();
const bodyHrs = job.joblines
.filter((j) => j.mod_lbr_ty !== "LAR")
.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
const refinishHrs = job.joblines
.filter((line) => line.mod_lbr_ty === "LAR")
.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
const ownerTitle = OwnerNameDisplayFunction(job).trim(); const ownerTitle = OwnerNameDisplayFunction(job).trim();
return ( return (
@@ -93,7 +101,13 @@ 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>
<DateTimeFormatter>{job.scheduled_in}</DateTimeFormatter> <Link
to={`/manage/schedule?date=${moment(
job.scheduled_in
).format("YYYY-MM-DD")}`}
>
<DateTimeFormatter>{job.scheduled_in}</DateTimeFormatter>
</Link>
</Tag> </Tag>
) : null} ) : null}
</Space> </Space>
@@ -211,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>
@@ -299,6 +319,11 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
> >
<div> <div>
<JobEmployeeAssignments job={job} /> <JobEmployeeAssignments job={job} />
<Divider style={{ margin: ".5rem" }} />
<DataLabel label={t("jobs.labels.labor_hrs")}>
{bodyHrs.toFixed(1)} / {refinishHrs.toFixed(1)} /{" "}
{(bodyHrs + refinishHrs).toFixed(1)}
</DataLabel>
</div> </div>
</Card> </Card>
</Col> </Col>

View File

@@ -1,44 +1,12 @@
import { useQuery } from "@apollo/client";
import queryString from "query-string";
import React from "react"; import React from "react";
import { useHistory, useLocation } from "react-router-dom";
import { QUERY_BILLS_BY_JOBID } from "../../graphql/bills.queries";
import JobsDetailPliComponent from "./jobs-detail-pli.component"; import JobsDetailPliComponent from "./jobs-detail-pli.component";
export default function JobsDetailPliContainer({ job }) { export default function JobsDetailPliContainer({
const billsQuery = useQuery(QUERY_BILLS_BY_JOBID, { job,
variables: { jobid: job.id }, billsQuery,
fetchPolicy: "network-only", handleBillOnRowClick,
nextFetchPolicy: "network-only", handlePartsOrderOnRowClick,
}); }) {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const handleBillOnRowClick = (record) => {
if (record) {
if (record.id) {
search.billid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.billid;
history.push({ search: queryString.stringify(search) });
}
};
const handlePartsOrderOnRowClick = (record) => {
if (record) {
if (record.id) {
search.partsorderid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.partsorderid;
history.push({ search: queryString.stringify(search) });
}
};
return ( return (
<JobsDetailPliComponent <JobsDetailPliComponent
job={job} job={job}

View File

@@ -23,8 +23,8 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
function updateJobCache(items) { function updateJobCache(items) {
@@ -189,6 +189,7 @@ export function JobsExportAllButton({
insertAuditTrail({ insertAuditTrail({
jobid: job.id, jobid: job.id,
operation: AuditTrailMapping.jobexported(), operation: AuditTrailMapping.jobexported(),
type: "jobexported",
}); });
}); });
updateJobCache( updateJobCache(
@@ -230,6 +231,7 @@ export function JobsExportAllButton({
insertAuditTrail({ insertAuditTrail({
jobid: successfulTransactionsSet[0], jobid: successfulTransactionsSet[0],
operation: AuditTrailMapping.jobexported(), operation: AuditTrailMapping.jobexported(),
type: "jobexported",
}); });
} }
updateJobCache(successfulTransactionsSet); updateJobCache(successfulTransactionsSet);

View File

@@ -11,6 +11,7 @@ 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 { pageLimit } from "../../utils/config"; import { pageLimit } from "../../utils/config";
import { alphaSort, statusSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage"; import useLocalStorage from "../../utils/useLocalStorage";
import StartChatButton from "../chat-open-button/chat-open-button.component"; import StartChatButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
@@ -36,7 +37,10 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number), sorter: search?.search
? (a, b) =>
parseInt((a.ro_number || "0").replace(/\D/g, "")) - parseInt((b.ro_number || "0").replace(/\D/g, ""))
: true,
sortOrder: sortcolumn === "ro_number" && sortorder, sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) => ( render: (text, record) => (
<Link to={"/manage/jobs/" + record.id}> <Link to={"/manage/jobs/" + record.id}>
@@ -50,7 +54,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
key: "ownr_ln", key: "ownr_ln",
ellipsis: true, ellipsis: true,
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), //sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
//sortOrder: sortcolumn === "ownr_ln" && sortorder, //sortOrder: sortcolumn === "ownr_ln" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.ownerid ? ( return record.ownerid ? (
@@ -68,7 +71,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ownr_ph1"), title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1", dataIndex: "ownr_ph1",
key: "ownr_ph1", key: "ownr_ph1",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => (
<StartChatButton phone={record.ownr_ph1} jobid={record.id} /> <StartChatButton phone={record.ownr_ph1} jobid={record.id} />
@@ -78,7 +80,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ownr_ph2"), title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2", dataIndex: "ownr_ph2",
key: "ownr_ph2", key: "ownr_ph2",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => (
<StartChatButton phone={record.ownr_ph2} jobid={record.id} /> <StartChatButton phone={record.ownr_ph2} jobid={record.id} />
@@ -88,9 +89,8 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.status"), title: t("jobs.fields.status"),
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
ellipsis: true, ellipsis: true,
sorter: true, // (a, b) => alphaSort(a.status, b.status), sorter: search?.search ? (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.active_statuses) : true,
sortOrder: sortcolumn === "status" && sortorder, sortOrder: sortcolumn === "status" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.status || t("general.labels.na"); return record.status || t("general.labels.na");
@@ -106,7 +106,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.vehicle"), title: t("jobs.fields.vehicle"),
dataIndex: "vehicle", dataIndex: "vehicle",
key: "vehicle", key: "vehicle",
ellipsis: true, ellipsis: true,
render: (text, record) => { render: (text, record) => {
return record.vehicleid ? ( return record.vehicleid ? (
@@ -127,7 +126,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
dataIndex: "plate_no", dataIndex: "plate_no",
key: "plate_no", key: "plate_no",
ellipsis: true, ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no), sorter: search?.search ? (a, b) => alphaSort(a.plate_no, b.plate_no) : true,
sortOrder: sortcolumn === "plate_no" && sortorder, sortOrder: sortcolumn === "plate_no" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.plate_no ? record.plate_no : ""; return record.plate_no ? record.plate_no : "";
@@ -138,7 +137,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
dataIndex: "clm_no", dataIndex: "clm_no",
key: "clm_no", key: "clm_no",
ellipsis: true, ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no), sorter: search?.search ? (a, b) => alphaSort(a.clm_no, b.clm_no) : true,
sortOrder: sortcolumn === "clm_no" && sortorder, sortOrder: sortcolumn === "clm_no" && sortorder,
render: (text, record) => render: (text, record) =>
`${record.clm_no || ""}${ `${record.clm_no || ""}${
@@ -156,7 +155,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
dataIndex: "clm_total", dataIndex: "clm_total",
key: "clm_total", key: "clm_total",
sorter: true, //(a, b) => a.clm_total - b.clm_total, sorter: search?.search ? (a, b) => a.clm_total - b.clm_total : true,
sortOrder: sortcolumn === "clm_total" && sortorder, sortOrder: sortcolumn === "clm_total" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.clm_total ? ( return record.clm_total ? (
@@ -170,7 +169,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.owner_owing"), title: t("jobs.fields.owner_owing"),
dataIndex: "owner_owing", dataIndex: "owner_owing",
key: "owner_owing", key: "owner_owing",
render: (text, record) => ( render: (text, record) => (
<CurrencyFormatter>{record.owner_owing}</CurrencyFormatter> <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
), ),

View File

@@ -21,6 +21,7 @@ import useLocalStorage from "../../utils/useLocalStorage";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -145,7 +146,8 @@ export function JobsList({ bodyshop }) {
key: "owner", key: "owner",
ellipsis: true, ellipsis: true,
responsive: ["md"], responsive: ["md"],
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), sorter: (a, b) =>
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
@@ -188,7 +190,8 @@ export function JobsList({ bodyshop }) {
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.status, b.status), sorter: (a, b) =>
statusSort(a.status, b.status, bodyshop.md_ro_statuses.active_statuses),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order, state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filteredValue: filter?.status || null, filteredValue: filter?.status || null,
@@ -219,6 +222,15 @@ export function JobsList({ bodyshop }) {
dataIndex: "vehicle", dataIndex: "vehicle",
key: "vehicle", key: "vehicle",
ellipsis: true, ellipsis: true,
sorter: (a, b) =>
alphaSort(
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
a.v_model_desc || ""
}`,
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
),
sortOrder:
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
return record.vehicleid ? ( return record.vehicleid ? (
<Link <Link
@@ -266,6 +278,9 @@ export function JobsList({ bodyshop }) {
dataIndex: "ins_co_nm", dataIndex: "ins_co_nm",
key: "ins_co_nm", key: "ins_co_nm",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
sortOrder:
state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
filteredValue: filter?.ins_co_nm || null, filteredValue: filter?.ins_co_nm || null,
filters: filters:
(jobs && (jobs &&
@@ -302,6 +317,13 @@ export function JobsList({ bodyshop }) {
key: "estimator", key: "estimator",
ellipsis: true, ellipsis: true,
responsive: ["xl"], responsive: ["xl"],
sorter: (a, b) =>
alphaSort(
`${a.est_ct_fn || ""} ${a.est_ct_ln || ""}`.trim(),
`${b.est_ct_fn || ""} ${b.est_ct_ln || ""}`.trim()
),
sortOrder:
state.sortedInfo.columnKey === "estimator" && state.sortedInfo.order,
filterSearch: true, filterSearch: true,
filteredValue: filter?.estimator || null, filteredValue: filter?.estimator || null,
filters: filters:

View File

@@ -19,8 +19,8 @@ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect(mapStateToProps, mapDispatchToProps)(JobNotesContainer); export default connect(mapStateToProps, mapDispatchToProps)(JobNotesContainer);
@@ -49,6 +49,7 @@ export function JobNotesContainer({ jobId, insertAuditTrail }) {
insertAuditTrail({ insertAuditTrail({
jobid: jobId, jobid: jobId,
operation: AuditTrailMapping.jobnotedeleted(), operation: AuditTrailMapping.jobnotedeleted(),
type: "jobnotedeleted",
}); });
}); });
setDeleteLoading(false); setDeleteLoading(false);

View File

@@ -20,8 +20,8 @@ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect( export default connect(
mapStateToProps, mapStateToProps,
@@ -76,6 +76,7 @@ export function LaborAllocationsAdjustmentEdit({
values.hours - values.hours -
((adjustments && adjustments[mod_lbr_ty]) || 0).toFixed(1), ((adjustments && adjustments[mod_lbr_ty]) || 0).toFixed(1),
}), }),
type: "jobmodifylbradj",
}); });
} }
setLoading(false); setLoading(false);

View File

@@ -19,8 +19,8 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("noteUpsert")), toggleModalVisible: () => dispatch(toggleModalVisible("noteUpsert")),
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function NoteUpsertModalContainer({ export function NoteUpsertModalContainer({
@@ -70,6 +70,7 @@ export function NoteUpsertModalContainer({
insertAuditTrail({ insertAuditTrail({
jobid: context.jobId, jobid: context.jobId,
operation: AuditTrailMapping.jobnoteupdated(), operation: AuditTrailMapping.jobnoteupdated(),
type: "jobnoteupdated",
}); });
}); });
if (refetch) refetch(); if (refetch) refetch();
@@ -102,6 +103,7 @@ export function NoteUpsertModalContainer({
insertAuditTrail({ insertAuditTrail({
jobid: newJobId, jobid: newJobId,
operation: AuditTrailMapping.jobnoteadded(), operation: AuditTrailMapping.jobnoteadded(),
type: "jobnoteadded",
}); });
}); });
} }
@@ -115,6 +117,7 @@ export function NoteUpsertModalContainer({
insertAuditTrail({ insertAuditTrail({
jobid: context.jobId, jobid: context.jobId,
operation: AuditTrailMapping.jobnoteadded(), operation: AuditTrailMapping.jobnoteadded(),
type: "jobnoteadded",
}); });
} }
}; };

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({
@@ -24,7 +25,7 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
}; };
const columns = [ const columns = [
{ {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
@@ -44,6 +45,15 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
title: t("jobs.fields.vehicle"), title: t("jobs.fields.vehicle"),
dataIndex: "vehicleid", dataIndex: "vehicleid",
key: "vehicleid", key: "vehicleid",
sorter: (a, b) =>
alphaSort(
`${a.v_model_yr || ""} ${a.v_make_desc || ""} ${
a.v_model_desc || ""
}`,
`${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
),
sortOrder:
state.sortedInfo.columnKey === "vehicleid" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>
record.vehicleid ? ( record.vehicleid ? (
<Link to={`/manage/vehicles/${record.vehicleid}`}> <Link to={`/manage/vehicles/${record.vehicleid}`}>
@@ -67,11 +77,28 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
title: t("jobs.fields.status"), title: t("jobs.fields.status"),
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses), sorter: (a, b) =>
statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order, state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filters: bodyshop.md_ro_statuses.statuses.map((status) => ({
text: status,
value: 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

@@ -0,0 +1,416 @@
import { DeleteFilled } from "@ant-design/icons";
import { useLazyQuery, useMutation } from "@apollo/client";
import {
Button,
Drawer,
Grid,
PageHeader,
Popconfirm,
Space,
Table,
} from "antd";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
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 { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters";
import DataLabel from "../data-label/data-label.component";
import FeatureWrapperComponent from "../feature-wrapper/feature-wrapper.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 PartsOrderDeleteLine from "../parts-order-delete-line/parts-order-delete-line.component";
import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
import PrintWrapper from "../print-wrapper/print-wrapper.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "billEnter",
})
),
setPartsReceiveContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "partsReceive",
})
),
});
export function PartsOrderListTableDrawerComponent({
setBillEnterContext,
bodyshop,
jobRO,
job,
billsQuery,
handleOnRowClick,
setPartsReceiveContext,
}) {
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const bpoints = {
xs: "100%",
sm: "100%",
md: "100%",
lg: "75%",
xl: "75%",
xxl: "65%",
};
const drawerPercentage = selectedBreakpoint
? bpoints[selectedBreakpoint[0]]
: "100%";
const responsibilityCenters = bodyshop.md_responsibility_centers;
const Templates = TemplateList("partsorder", { job });
const { t } = useTranslation();
const [state, setState] = useState({
sortedInfo: {},
});
const [billData, setBillData] = useState(null);
const search = queryString.parse(useLocation().search);
const selectedpartsorder = search.partsorderid;
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
const { refetch } = billsQuery;
const selectedPartsOrderRecord = parts_orders.find(
(r) => r.id === selectedpartsorder
);
useEffect(() => {
const fetchData = async () => {
if (selectedPartsOrderRecord?.returnfrombill) {
try {
const { data } = await billQuery({
variables: { billid: selectedPartsOrderRecord.returnfrombill },
});
setBillData(data);
} catch (error) {
console.error("Error fetching bill data:", error);
}
} else setBillData(null);
};
fetchData();
}, [selectedPartsOrderRecord, billQuery]);
const recordActions = (record) => (
<Space direction="horizontal" wrap>
<Button
disabled={
jobRO ||
record.return ||
record.vendor.id === bodyshop.inhousevendorid
}
onClick={() => {
logImEXEvent("parts_order_receive_bill");
setPartsReceiveContext({
actions: { refetch: refetch },
context: {
jobId: job.id,
job: job,
partsorderlines: record.parts_order_lines.map((pol) => ({
joblineid: pol.job_line_id,
id: pol.id,
line_desc: pol.line_desc,
quantity: pol.quantity,
act_price: pol.act_price,
oem_partno: pol.oem_partno,
})),
},
});
}}
>
{t("parts_orders.actions.receive")}
</Button>
<Popconfirm
title={t("parts_orders.labels.confirmdelete")}
disabled={jobRO}
onConfirm={async () => {
//Delete the parts return.!
await deletePartsOrder({
variables: { partsOrderId: record.id },
update(cache) {
cache.modify({
fields: {
parts_orders(existingPartsOrders, { readField }) {
return existingPartsOrders.filter(
(billref) => record.id !== readField("id", billref)
);
},
},
});
},
});
}}
>
<Button disabled={jobRO}>
<DeleteFilled />
</Button>
</Popconfirm>
<FeatureWrapperComponent featureName="bills" noauth={() => null}>
<Button
disabled={
(jobRO ? !record.return : jobRO) ||
record.vendor.id === bodyshop.inhousevendorid
}
onClick={() => {
logImEXEvent("parts_order_receive_bill");
setBillEnterContext({
actions: { refetch: refetch },
context: {
job: job,
bill: {
vendorid: record.vendor.id,
is_credit_memo: record.return,
billlines: record.parts_order_lines.map((pol) => ({
joblineid: pol.job_line_id || "noline",
line_desc: pol.line_desc,
quantity: pol.quantity,
actual_price: pol.act_price,
cost_center: pol.jobline?.part_type
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
? pol.jobline.part_type !== "PAE"
? pol.jobline.part_type
: null
: responsibilityCenters.defaults &&
(responsibilityCenters.defaults.costs[
pol.jobline.part_type
] ||
null)
: null,
})),
},
},
});
}}
>
{t("parts_orders.actions.receivebill")}
</Button>
</FeatureWrapperComponent>
<PrintWrapper
templateObject={{
name: record.return
? Templates.parts_return_slip.key
: Templates.parts_order.key,
variables: { id: record.id },
}}
messageObject={{
subject: record.return
? Templates.parts_return_slip.subject
: Templates.parts_order.subject,
to: record.vendor.email,
}}
id={job.id}
/>
</Space>
);
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const rowExpander = (record) => {
const columns = [
{
title: t("parts_orders.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.quantity"),
dataIndex: "quantity",
key: "quantity",
sorter: (a, b) => a.quantity - b.quantity,
sortOrder:
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.act_price"),
dataIndex: "act_price",
key: "act_price",
sorter: (a, b) => a.act_price - b.act_price,
sortOrder:
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
),
},
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
? [
{
title: t("parts_orders.fields.cost"),
dataIndex: "cost",
key: "cost",
sorter: (a, b) => a.cost - b.cost,
sortOrder:
state.sortedInfo.columnKey === "cost" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.cost}</CurrencyFormatter>
),
},
]
: []),
{
title: t("parts_orders.fields.part_type"),
dataIndex: "part_type",
key: "part_type",
render: (text, record) =>
record.part_type
? t(`joblines.fields.part_types.${record.part_type}`)
: null,
},
{
title: t("parts_orders.fields.oem_partno"),
dataIndex: "oem_partno",
key: "oem_partno",
sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno),
sortOrder:
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.line_remarks"),
dataIndex: "line_remarks",
key: "line_remarks",
},
{
title: t("parts_orders.fields.status"),
dataIndex: "status",
key: "status",
},
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
? [
{
title: t("parts_orders.fields.cm_received"),
dataIndex: "cm_received",
key: "cm_received",
render: (text, record) => (
<PartsOrderCmReceived
orderLineId={record.id}
checked={record.cm_received}
partsorderid={selectedPartsOrderRecord.id}
/>
),
},
]
: []),
{
title: t("parts_orders.fields.backordered_on"),
dataIndex: "backordered_on",
key: "backordered_on",
render: (text, record) => <DateFormatter>{text}</DateFormatter>,
},
{
title: t("parts_orders.fields.backordered_eta"),
dataIndex: "backordered_eta",
key: "backordered_eta",
render: (text, record) => (
<PartsOrderBackorderEta
backordered_eta={record.backordered_eta}
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
jobLineId={record.job_line_id}
/>
),
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Space wrap>
<PartsOrderDeleteLine
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
partsOrderId={selectedpartsorder}
jobLineId={record.job_line_id}
/>
<PartsOrderLineBackorderButton
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
jobLineId={record.job_line_id}
/>
</Space>
),
},
];
return (
<div>
<PageHeader
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)}
/>
<Table
scroll={{
x: true, //y: "50rem"
}}
columns={columns}
rowKey="id"
dataSource={record.parts_order_lines}
onChange={handleTableChange}
/>
<DataLabel label={t("parts_orders.fields.comments")}>
<div style={{ whiteSpace: "pre" }}>{record.comments}</div>
</DataLabel>
</div>
);
};
return (
<div>
<PartsReceiveModalContainer />
<Drawer
placement="right"
onClose={() => handleOnRowClick(null)}
open={selectedpartsorder}
closable
width={drawerPercentage}
>
{selectedPartsOrderRecord && rowExpander(selectedPartsOrderRecord)}
</Drawer>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(PartsOrderListTableDrawerComponent);

View File

@@ -1,39 +1,21 @@
import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons"; import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { import { Button, Card, Checkbox, Input, Popconfirm, Space, Table } from "antd";
Button,
Card,
Checkbox,
Drawer,
Grid,
Input,
PageHeader,
Popconfirm,
Space,
Table,
} from "antd";
import queryString from "query-string";
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";
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 { 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 { 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 DataLabel from "../data-label/data-label.component"; import { alphaSort } from "../../utils/sorters";
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 PartsOrderDeleteLine from "../parts-order-delete-line/parts-order-delete-line.component";
import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container"; import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
import PrintWrapper from "../print-wrapper/print-wrapper.component"; import PrintWrapper from "../print-wrapper/print-wrapper.component";
import PartsOrderDrawer from "./parts-order-list-table-drawer.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
@@ -56,21 +38,6 @@ export function PartsOrderListTableComponent({
handleOnRowClick, handleOnRowClick,
setPartsReceiveContext, setPartsReceiveContext,
}) { }) {
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const bpoints = {
xs: "100%",
sm: "100%",
md: "100%",
lg: "75%",
xl: "75%",
xxl: "65%",
};
const drawerPercentage = selectedBreakpoint
? bpoints[selectedBreakpoint[0]]
: "100%";
const responsibilityCenters = bodyshop.md_responsibility_centers; const responsibilityCenters = bodyshop.md_responsibility_centers;
const Templates = TemplateList("partsorder", { job }); const Templates = TemplateList("partsorder", { job });
@@ -78,10 +45,8 @@ export function PartsOrderListTableComponent({
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
}); });
const search = queryString.parse(useLocation().search);
const selectedpartsorder = search.partsorderid;
const [searchText, setSearchText] = useState("");
const [searchText, setSearchText] = useState("");
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 : [];
@@ -90,7 +55,11 @@ export function PartsOrderListTableComponent({
const recordActions = (record, showView = false) => ( const recordActions = (record, showView = false) => (
<Space wrap> <Space wrap>
{showView && ( {showView && (
<Button onClick={() => handleOnRowClick(record)}> <Button
onClick={() => {
handleOnRowClick(record);
}}
>
<EyeFilled /> <EyeFilled />
</Button> </Button>
)} )}
@@ -166,7 +135,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,
@@ -277,164 +246,6 @@ export function PartsOrderListTableComponent({
setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
}; };
const selectedPartsOrderRecord = parts_orders.find(
(r) => r.id === selectedpartsorder
);
const rowExpander = (record) => {
const columns = [
{
title: t("parts_orders.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.quantity"),
dataIndex: "quantity",
key: "quantity",
sorter: (a, b) => a.quantity - b.quantity,
sortOrder:
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.act_price"),
dataIndex: "act_price",
key: "act_price",
sorter: (a, b) => a.act_price - b.act_price,
sortOrder:
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
),
},
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
? [
{
title: t("parts_orders.fields.cost"),
dataIndex: "cost",
key: "cost",
sorter: (a, b) => a.cost - b.cost,
sortOrder:
state.sortedInfo.columnKey === "cost" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.cost}</CurrencyFormatter>
),
},
]
: []),
{
title: t("parts_orders.fields.part_type"),
dataIndex: "part_type",
key: "part_type",
render: (text, record) =>
record.part_type
? t(`joblines.fields.part_types.${record.part_type}`)
: null,
},
{
title: t("parts_orders.fields.oem_partno"),
dataIndex: "oem_partno",
key: "oem_partno",
sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno),
sortOrder:
state.sortedInfo.columnKey === "oem_partno" && state.sortedInfo.order,
},
{
title: t("parts_orders.fields.line_remarks"),
dataIndex: "line_remarks",
key: "line_remarks",
},
{
title: t("parts_orders.fields.status"),
dataIndex: "status",
key: "status",
},
...(selectedPartsOrderRecord && selectedPartsOrderRecord.return
? [
{
title: t("parts_orders.fields.cm_received"),
dataIndex: "cm_received",
key: "cm_received",
render: (text, record) => (
<PartsOrderCmReceived
orderLineId={record.id}
checked={record.cm_received}
partsorderid={selectedPartsOrderRecord.id}
/>
),
},
]
: []),
{
title: t("parts_orders.fields.backordered_on"),
dataIndex: "backordered_on",
key: "backordered_on",
render: (text, record) => <DateFormatter>{text}</DateFormatter>,
},
{
title: t("parts_orders.fields.backordered_eta"),
dataIndex: "backordered_eta",
key: "backordered_eta",
render: (text, record) => (
<PartsOrderBackorderEta
backordered_eta={record.backordered_eta}
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
jobLineId={record.job_line_id}
/>
),
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Space wrap>
<PartsOrderDeleteLine
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
partsOrderId={selectedpartsorder}
jobLineId={record.job_line_id}
/>
<PartsOrderLineBackorderButton
disabled={jobRO}
partsOrderStatus={record.status}
partsLineId={record.id}
jobLineId={record.job_line_id}
/>
</Space>
),
},
];
return (
<div>
<PageHeader
title={record && `${record.vendor.name} - ${record.order_number}`}
extra={recordActions(record)}
/>
<Table
scroll={{
x: true, //y: "50rem"
}}
columns={columns}
rowKey="id"
dataSource={record.parts_order_lines}
/>
<DataLabel label={t("parts_orders.fields.comments")}>
<div style={{ whiteSpace: "pre" }}>{record.comments}</div>
</DataLabel>
</div>
);
};
const filteredPartsOrders = parts_orders const filteredPartsOrders = parts_orders
? searchText === "" ? searchText === ""
? parts_orders ? parts_orders
@@ -470,15 +281,12 @@ export function PartsOrderListTableComponent({
} }
> >
<PartsReceiveModalContainer /> <PartsReceiveModalContainer />
<Drawer <PartsOrderDrawer
placement="right" job={job}
onClose={() => handleOnRowClick(null)} billsQuery={billsQuery}
visible={selectedpartsorder} handleOnRowClick={handleOnRowClick}
closable setPartsReceiveContext={setPartsReceiveContext}
width={drawerPercentage} />
>
{selectedPartsOrderRecord && rowExpander(selectedPartsOrderRecord)}
</Drawer>
<Table <Table
loading={billsQuery.loading} loading={billsQuery.loading}
scroll={{ scroll={{

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

@@ -1,12 +1,16 @@
import { useMutation, useQuery, useApolloClient } from "@apollo/client"; import { useApolloClient, useMutation, useQuery } from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Form, Modal, notification } from "antd"; import { Form, Modal, notification } from "antd";
import axios from "axios";
import _ from "lodash";
import moment from "moment"; import moment from "moment";
import React, { useEffect, 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 { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent, auth } from "../../firebase/firebase.utils"; import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries"; import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { import {
INSERT_NEW_PARTS_ORDERS, INSERT_NEW_PARTS_ORDERS,
QUERY_PARTS_ORDER_OEC, QUERY_PARTS_ORDER_OEC,
@@ -29,10 +33,6 @@ import { TemplateList } from "../../utils/TemplateConstants";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import PartsOrderModalComponent from "./parts-order-modal.component"; import PartsOrderModalComponent from "./parts-order-modal.component";
import axios from "axios";
import { useTreatments } from "@splitsoftware/splitio-react";
import _ from "lodash";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
@@ -45,8 +45,8 @@ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")), toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")),
setBillEnterContext: (context) => setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })), dispatch(setModalContext({ context: context, modal: "billEnter" })),
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function PartsOrderModalContainer({ export function PartsOrderModalContainer({
@@ -142,6 +142,7 @@ export function PartsOrderModalContainer({
: AuditTrailMapping.jobspartsorder( : AuditTrailMapping.jobspartsorder(
insertResult.data.insert_parts_orders.returning[0].order_number insertResult.data.insert_parts_orders.returning[0].order_number
), ),
type: isReturn ? "jobspartsreturn" : "jobspartsorder",
}); });
const jobLinesResult = await updateJobLines({ const jobLinesResult = await updateJobLines({

View File

@@ -95,15 +95,13 @@ export function PartsQueueListComponent({ bodyshop }) {
}; };
const handleOnRowClick = (record) => { const handleOnRowClick = (record) => {
if (record) { if (record?.id) {
if (record.id) { history.replace({
history.push({ search: queryString.stringify({
search: queryString.stringify({ ...searchParams,
...searchParams, selected: record.id,
selected: record.id, }),
}), });
});
}
} }
}; };
@@ -350,6 +348,13 @@ export function PartsQueueListComponent({ bodyshop }) {
selectedRowKeys: [selected], selectedRowKeys: [selected],
type: "radio", type: "radio",
}} }}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
},
};
}}
/> />
</Card> </Card>
); );

View File

@@ -59,8 +59,8 @@ const PaymentExpandedRowComponent = ({ record, bodyshop }) => {
await insertPayment({ await insertPayment({
variables: { variables: {
paymentInput: { paymentInput: {
amount: -refund_response.data.amount, amount: -refund_response?.data?.amount,
transactionid: payment_response.response.receiptelements.transid, transactionid: payment_response?.response?.receiptelements?.transid,
payer: record.payer, payer: record.payer,
type: "Refund", type: "Refund",
jobid: payment_response.jobid, jobid: payment_response.jobid,
@@ -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

@@ -1,6 +1,12 @@
import { Button, Form, Input, PageHeader, Space } from "antd"; import { Button, Form, Input, PageHeader, Space } 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 { createStructuredSelector } from "reselect";
import {
selectAuthLevel,
selectBodyshop,
} from "../../redux/user/user.selectors";
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 FormItemEmail from "../form-items-formatted/email-form-item.component"; import FormItemEmail from "../form-items-formatted/email-form-item.component";
import PhoneFormItem, { import PhoneFormItem, {
@@ -8,12 +14,6 @@ import PhoneFormItem, {
} from "../form-items-formatted/phone-form-item.component"; } from "../form-items-formatted/phone-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component"; import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectAuthLevel,
selectBodyshop,
} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
authLevel: selectAuthLevel, authLevel: selectAuthLevel,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -46,13 +46,19 @@ export function PhonebookFormComponent({
return ( return (
<div> <div>
<PageHeader <PageHeader
title={`${form.getFieldValue("firstname") || ""} ${ title={
form.getFieldValue("lastname") || "" <Form.Item shouldUpdate>
}${ {() =>
form.getFieldValue("company") `${form.getFieldValue("firstname") || ""} ${
? ` - ${form.getFieldValue("company")}` form.getFieldValue("lastname") || ""
: "" }${
}`} form.getFieldValue("company")
? ` - ${form.getFieldValue("company")}`
: ""
}`
}
</Form.Item>
}
extra={ extra={
<Space> <Space>
<Button <Button

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

@@ -29,8 +29,8 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function ProductionBoardKanbanComponent({ export function ProductionBoardKanbanComponent({
@@ -133,6 +133,7 @@ export function ProductionBoardKanbanComponent({
insertAuditTrail({ insertAuditTrail({
jobid: card.id, jobid: card.id,
operation: AuditTrailMapping.jobstatuschange(destination.toColumnId), operation: AuditTrailMapping.jobstatuschange(destination.toColumnId),
type: "jobstatuschange",
}); });
if (update.errors) { if (update.errors) {

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

@@ -13,8 +13,8 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({}); const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function ProductionListColumnAlert({ record, insertAuditTrail }) { export function ProductionListColumnAlert({ record, insertAuditTrail }) {
@@ -46,6 +46,7 @@ export function ProductionListColumnAlert({ record, insertAuditTrail }) {
? !record.production_vars.alert ? !record.production_vars.alert
: true : true
), ),
type: "alertToggle",
}).then(() => { }).then(() => {
if (record.refetch) record.refetch(); if (record.refetch) record.refetch();
}); });

View File

@@ -1,7 +1,6 @@
import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons"; import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons";
import { Space, Tooltip } from "antd"; import { Checkbox, Space, Tooltip } from "antd";
import i18n from "i18next"; import i18n from "i18next";
import moment from "moment";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { TimeFormatter } from "../../utils/DateFormatter"; import { TimeFormatter } from "../../utils/DateFormatter";
@@ -10,7 +9,7 @@ import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters"; import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import JobAltTransportChange from "../job-at-change/job-at-change.component"; import JobAltTransportChange from "../job-at-change/job-at-change.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component"; import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
import ProductionListColumnAlert from "./production-list-columns.alert.component"; import ProductionListColumnAlert from "./production-list-columns.alert.component";
import ProductionListColumnBodyPriority from "./production-list-columns.bodypriority.component"; import ProductionListColumnBodyPriority from "./production-list-columns.bodypriority.component";
@@ -33,11 +32,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
dataIndex: "viewdetail", dataIndex: "viewdetail",
key: "viewdetail", key: "viewdetail",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <Link to={{ search: `?selected=${record.id}` }}>{i18n.t("general.labels.view")}</Link>
<Link to={{ search: `?selected=${record.id}` }}>
{i18n.t("general.labels.view")}
</Link>
),
}, },
{ {
title: i18n.t("jobs.fields.ro_number"), title: i18n.t("jobs.fields.ro_number"),
@@ -45,23 +40,18 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "ro_number", key: "ro_number",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number), sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder: sortOrder: state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>
technician ? ( technician ? (
<Link to={`/tech/joblookup?selected=${record.id}`}> <Link to={`/tech/joblookup?selected=${record.id}`}>
{record.ro_number} {record.ro_number}
{record.suspended && ( {record.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
</Link> </Link>
) : ( ) : (
<Link to={`/manage/jobs/${record.id}`}> <Link to={`/manage/jobs/${record.id}`}>
<Space> <Space>
{record.ro_number} {record.ro_number}
{record.suspended && ( {record.suspended && <PauseCircleOutlined style={{ color: "orangered" }} />}
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{record.iouparent && ( {record.iouparent && (
<Tooltip title={i18n.t("jobs.labels.iou")}> <Tooltip title={i18n.t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} /> <BranchesOutlined style={{ color: "orangered" }} />
@@ -69,7 +59,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
)} )}
</Space> </Space>
</Link> </Link>
), )
}, },
{ {
title: i18n.t("jobs.fields.owner"), title: i18n.t("jobs.fields.owner"),
@@ -84,9 +74,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
<OwnerNameDisplay ownerObject={record} /> <OwnerNameDisplay ownerObject={record} />
</Link> </Link>
), ),
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), sorter: (a, b) => alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
sortOrder: sortOrder: state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order
state.sortedInfo.columnKey === "ownr" && state.sortedInfo.order,
}, },
{ {
title: i18n.t("jobs.fields.vehicle"), title: i18n.t("jobs.fields.vehicle"),
@@ -95,11 +84,10 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
ellipsis: true, ellipsis: true,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
a.v_make_desc + a.v_model_desc, `${a.v_model_yr || ""} ${a.v_make_desc || ""} ${a.v_model_desc || ""}`,
b.v_make_desc + b.v_model_desc `${b.v_model_yr || ""} ${b.v_make_desc || ""} ${b.v_model_desc || ""}`
), ),
sortOrder: sortOrder: state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
state.sortedInfo.columnKey === "vehicle" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>
technician ? ( technician ? (
<>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${ <>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
@@ -111,7 +99,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
} ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${ } ${record.v_make_desc || ""} ${record.v_model_desc || ""} ${
record.v_color || "" record.v_color || ""
} ${record.plate_no || ""}`}</Link> } ${record.plate_no || ""}`}</Link>
), )
}, },
{ {
title: i18n.t("jobs.fields.actual_in"), title: i18n.t("jobs.fields.actual_in"),
@@ -119,11 +107,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "actual_in", key: "actual_in",
ellipsis: true, ellipsis: true,
sorter: (a, b) => dateSort(a.actual_in, b.actual_in), sorter: (a, b) => dateSort(a.actual_in, b.actual_in),
sortOrder: sortOrder: state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order,
state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order, render: (text, record) => <ProductionListDate record={record} field="actual_in" time />
render: (text, record) => (
<ProductionListDate record={record} field="actual_in" time />
),
}, },
{ {
title: i18n.t("jobs.fields.actual_in") + " (HH:MM)", title: i18n.t("jobs.fields.actual_in") + " (HH:MM)",
@@ -131,28 +116,16 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "actual_in_time", key: "actual_in_time",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <TimeFormatter>{record.actual_in}</TimeFormatter>
<TimeFormatter>{record.actual_in}</TimeFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.scheduled_completion"), title: i18n.t("jobs.fields.scheduled_completion"),
dataIndex: "scheduled_completion", dataIndex: "scheduled_completion",
key: "scheduled_completion", key: "scheduled_completion",
ellipsis: true, ellipsis: true,
sorter: (a, b) => sorter: (a, b) => dateSort(a.scheduled_completion, b.scheduled_completion),
dateSort(a.scheduled_completion, b.scheduled_completion), sortOrder: state.sortedInfo.columnKey === "scheduled_completion" && state.sortedInfo.order,
sortOrder: render: (text, record) => <ProductionListDate record={record} field="scheduled_completion" pastIndicator time />
state.sortedInfo.columnKey === "scheduled_completion" &&
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate
record={record}
field="scheduled_completion"
pastIndicator
time
/>
),
}, },
{ {
title: i18n.t("jobs.fields.scheduled_completion") + " (HH:MM)", title: i18n.t("jobs.fields.scheduled_completion") + " (HH:MM)",
@@ -160,9 +133,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "scheduled_completion_time", key: "scheduled_completion_time",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <TimeFormatter>{record.scheduled_completion}</TimeFormatter>
<TimeFormatter>{record.scheduled_completion}</TimeFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.date_last_contacted"), title: i18n.t("jobs.fields.date_last_contacted"),
@@ -170,10 +141,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "date_last_contacted", key: "date_last_contacted",
ellipsis: true, ellipsis: true,
sorter: (a, b) => dateSort(a.date_last_contacted, b.date_last_contacted), sorter: (a, b) => dateSort(a.date_last_contacted, b.date_last_contacted),
sortOrder: sortOrder: state.sortedInfo.columnKey === "date_last_contacted" && state.sortedInfo.order,
state.sortedInfo.columnKey === "date_last_contacted" && render: (text, record) => <ProductionListLastContacted record={record} />
state.sortedInfo.order,
render: (text, record) => <ProductionListLastContacted record={record} />,
}, },
{ {
title: i18n.t("jobs.fields.date_next_contact"), title: i18n.t("jobs.fields.date_next_contact"),
@@ -181,22 +150,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "date_next_contact", key: "date_next_contact",
ellipsis: true, ellipsis: true,
sorter: (a, b) => dateSort(a.date_next_contact, b.date_next_contact), sorter: (a, b) => dateSort(a.date_next_contact, b.date_next_contact),
sortOrder: sortOrder: state.sortedInfo.columnKey === "date_next_contact" && state.sortedInfo.order,
state.sortedInfo.columnKey === "date_next_contact" && render: (text, record) => <ProductionListDate record={record} field="date_next_contact" pastIndicator time />
state.sortedInfo.order,
render: (text, record) => (
<span
style={{
color:
record.date_next_contact &&
moment(record.date_next_contact).isBefore(moment())
? "red"
: "",
}}
>
<ProductionListDate record={record} field="date_next_contact" time />
</span>
),
}, },
{ {
title: i18n.t("jobs.fields.scheduled_delivery"), title: i18n.t("jobs.fields.scheduled_delivery"),
@@ -204,17 +159,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "scheduled_delivery", key: "scheduled_delivery",
ellipsis: true, ellipsis: true,
sorter: (a, b) => dateSort(a.scheduled_delivery, b.scheduled_delivery), sorter: (a, b) => dateSort(a.scheduled_delivery, b.scheduled_delivery),
sortOrder: sortOrder: state.sortedInfo.columnKey === "scheduled_delivery" && state.sortedInfo.order,
state.sortedInfo.columnKey === "scheduled_delivery" && render: (text, record) => <ProductionListDate record={record} field="scheduled_delivery" pastIndicator time />
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate
record={record}
field="scheduled_delivery"
pastIndicator
time
/>
),
}, },
{ {
title: i18n.t("jobs.fields.scheduled_delivery") + " (HH:MM)", title: i18n.t("jobs.fields.scheduled_delivery") + " (HH:MM)",
@@ -222,9 +168,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "scheduled_delivery_time", key: "scheduled_delivery_time",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <TimeFormatter>{record.scheduled_delivery}</TimeFormatter>
<TimeFormatter>{record.scheduled_delivery}</TimeFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.ins_co_nm"), title: i18n.t("jobs.fields.ins_co_nm"),
@@ -232,8 +176,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "ins_co_nm", key: "ins_co_nm",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm), sorter: (a, b) => alphaSort(a.ins_co_nm, b.ins_co_nm),
sortOrder: sortOrder: state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order
state.sortedInfo.columnKey === "ins_co_nm" && state.sortedInfo.order,
}, },
{ {
title: i18n.t("jobs.fields.clm_no"), title: i18n.t("jobs.fields.clm_no"),
@@ -241,8 +184,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "clm_no", key: "clm_no",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no), sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder: sortOrder: state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
}, },
{ {
title: i18n.t("jobs.fields.clm_total"), title: i18n.t("jobs.fields.clm_total"),
@@ -250,11 +192,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "clm_total", key: "clm_total",
ellipsis: true, ellipsis: true,
sorter: (a, b) => a.clm_total - b.clm_total, sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder: sortOrder: state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order, render: (text, record) => <CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.owner_owing"), title: i18n.t("jobs.fields.owner_owing"),
@@ -262,35 +201,36 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "owner_owing", key: "owner_owing",
ellipsis: true, ellipsis: true,
sorter: (a, b) => a.owner_owing - b.owner_owing, sorter: (a, b) => a.owner_owing - b.owner_owing,
sortOrder: sortOrder: state.sortedInfo.columnKey === "owner_owing" && state.sortedInfo.order,
state.sortedInfo.columnKey === "owner_owing" && state.sortedInfo.order, render: (text, record) => <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
render: (text, record) => (
<CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.ownr_ph1"), title: i18n.t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1", dataIndex: "ownr_ph1",
key: "ownr_ph1", key: "ownr_ph1",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.ownr_ph2"), title: i18n.t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2", dataIndex: "ownr_ph2",
key: "ownr_ph2", key: "ownr_ph2",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
),
}, },
{ {
title: i18n.t("jobs.fields.specialcoveragepolicy"), title: i18n.t("jobs.fields.specialcoveragepolicy"),
dataIndex: "special_coverage_policy", dataIndex: "special_coverage_policy",
key: "special_coverage_policy", key: "special_coverage_policy",
ellipsis: true, ellipsis: true,
sorter: (a, b) => Number(a.special_coverage_policy) - Number(b.special_coverage_policy),
sortOrder: state.sortedInfo.columnKey === "special_coverage_policy" && state.sortedInfo.order,
filters: [
{ text: "True", value: true },
{ text: "False", value: false }
],
onFilter: (value, record) => value === record.special_coverage_policy,
render: (text, record) => <Checkbox checked={record.special_coverage_policy} />
}, },
{ {
@@ -299,15 +239,23 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "alt_transport", key: "alt_transport",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport), sorter: (a, b) => alphaSort(a.alt_transport, b.alt_transport),
sortOrder: sortOrder: state.sortedInfo.columnKey === "alt_transport" && state.sortedInfo.order,
state.sortedInfo.columnKey === "alt_transport" && filters:
state.sortedInfo.order, (bodyshop &&
bodyshop.appt_alt_transport.map((s) => {
return {
text: s,
value: [s]
};
})) ||
[],
onFilter: (value, record) => value.includes(record.alt_transport),
render: (text, record) => ( render: (text, record) => (
<div> <div>
{record.alt_transport} {record.alt_transport}
<JobAltTransportChange job={record} /> <JobAltTransportChange job={record} />
</div> </div>
), )
}, },
{ {
title: i18n.t("jobs.fields.status"), title: i18n.t("jobs.fields.status"),
@@ -315,9 +263,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "status", key: "status",
ellipsis: true, ellipsis: true,
sorter: (a, b) => statusSort(a.status, b.status, activeStatuses), sorter: (a, b) => statusSort(a.status, b.status, activeStatuses),
sortOrder: sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
state.sortedInfo.columnKey === "status" && state.sortedInfo.order, render: (text, record) => <ProductionListColumnStatus record={record} />
render: (text, record) => <ProductionListColumnStatus record={record} />,
}, },
{ {
title: i18n.t("jobs.fields.category"), title: i18n.t("jobs.fields.category"),
@@ -330,37 +277,30 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
bodyshop.md_categories.map((s) => { bodyshop.md_categories.map((s) => {
return { return {
text: s, text: s,
value: [s], value: [s]
}; };
})) || })) ||
[], [],
onFilter: (value, record) => value.includes(record.category), onFilter: (value, record) => value.includes(record.category),
sorter: (a, b) => alphaSort(a.category, b.category), sorter: (a, b) => alphaSort(a.category, b.category),
sortOrder: sortOrder: state.sortedInfo.columnKey === "category" && state.sortedInfo.order,
state.sortedInfo.columnKey === "category" && state.sortedInfo.order, render: (text, record) => <ProductionListColumnCategory record={record} />
render: (text, record) => (
<ProductionListColumnCategory record={record} />
),
}, },
{ {
title: i18n.t("production.labels.bodyhours"), title: i18n.t("production.labels.bodyhours"),
dataIndex: "labhrs", dataIndex: "labhrs",
key: "labhrs", key: "labhrs",
sorter: (a, b) => sorter: (a, b) => a.labhrs.aggregate.sum.mod_lb_hrs - b.labhrs.aggregate.sum.mod_lb_hrs,
a.labhrs.aggregate.sum.mod_lb_hrs - b.labhrs.aggregate.sum.mod_lb_hrs, sortOrder: state.sortedInfo.columnKey === "labhrs" && state.sortedInfo.order,
sortOrder: render: (text, record) => record.labhrs.aggregate.sum.mod_lb_hrs
state.sortedInfo.columnKey === "labhrs" && state.sortedInfo.order,
render: (text, record) => record.labhrs.aggregate.sum.mod_lb_hrs,
}, },
{ {
title: i18n.t("production.labels.refinishhours"), title: i18n.t("production.labels.refinishhours"),
dataIndex: "larhrs", dataIndex: "larhrs",
key: "larhrs", key: "larhrs",
sorter: (a, b) => sorter: (a, b) => a.larhrs.aggregate.sum.mod_lb_hrs - b.larhrs.aggregate.sum.mod_lb_hrs,
a.larhrs.aggregate.sum.mod_lb_hrs - b.larhrs.aggregate.sum.mod_lb_hrs, sortOrder: state.sortedInfo.columnKey === "larhrs" && state.sortedInfo.order,
sortOrder: render: (text, record) => record.larhrs.aggregate.sum.mod_lb_hrs
state.sortedInfo.columnKey === "larhrs" && state.sortedInfo.order,
render: (text, record) => record.larhrs.aggregate.sum.mod_lb_hrs,
}, },
{ {
title: i18n.t("production.labels.totalhours"), title: i18n.t("production.labels.totalhours"),
@@ -370,34 +310,36 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
a.labhrs.aggregate.sum.mod_lb_hrs + a.labhrs.aggregate.sum.mod_lb_hrs +
a.larhrs.aggregate.sum.mod_lb_hrs - a.larhrs.aggregate.sum.mod_lb_hrs -
(b.labhrs.aggregate.sum.mod_lb_hrs + b.larhrs.aggregate.sum.mod_lb_hrs), (b.labhrs.aggregate.sum.mod_lb_hrs + b.larhrs.aggregate.sum.mod_lb_hrs),
sortOrder: sortOrder: state.sortedInfo.columnKey === "totalhours" && state.sortedInfo.order,
state.sortedInfo.columnKey === "totalhours" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>
( (record.labhrs.aggregate.sum.mod_lb_hrs + record.larhrs.aggregate.sum.mod_lb_hrs).toFixed(1)
record.labhrs.aggregate.sum.mod_lb_hrs +
record.larhrs.aggregate.sum.mod_lb_hrs
).toFixed(1),
}, },
{ {
title: i18n.t("production.labels.alert"), title: i18n.t("production.labels.alert"),
dataIndex: "alert", dataIndex: "alert",
key: "alert", key: "alert",
sorter: (a, b) => Number(a.production_vars?.alert || false) - Number(b.production_vars?.alert || false),
render: (text, record) => <ProductionListColumnAlert record={record} />, sortOrder: state.sortedInfo.columnKey === "alert" && state.sortedInfo.order,
filters: [
{ text: "True", value: true },
{ text: "False", value: false }
],
onFilter: (value, record) => value === (record.production_vars?.alert || false),
render: (text, record) => <ProductionListColumnAlert record={record} />
}, },
{ {
title: i18n.t("production.labels.note"), title: i18n.t("production.labels.note"),
dataIndex: "note", dataIndex: "note",
key: "note", key: "note",
ellipsis: true, ellipsis: true,
render: (text, record) => <ProductionListColumnNote record={record} />, render: (text, record) => <ProductionListColumnNote record={record} />
}, },
{ {
title: i18n.t("production.labels.comment"), title: i18n.t("production.labels.comment"),
dataIndex: "comment", dataIndex: "comment",
key: "comment", key: "comment",
ellipsis: true, ellipsis: true,
render: (text, record) => <ProductionListColumnComment record={record} />, render: (text, record) => <ProductionListColumnComment record={record} />
}, },
{ {
title: i18n.t("production.labels.touchtime"), title: i18n.t("production.labels.touchtime"),
@@ -405,7 +347,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "tt", key: "tt",
render: (text, record) => { render: (text, record) => {
return <ProductionlistColumnTouchTime job={record} />; return <ProductionlistColumnTouchTime job={record} />;
}, }
}, },
{ {
title: i18n.t("production.labels.bodypriority"), title: i18n.t("production.labels.bodypriority"),
@@ -415,11 +357,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
sorter: (a, b) => sorter: (a, b) =>
((a.production_vars && a.production_vars.bodypriority) || 11) - ((a.production_vars && a.production_vars.bodypriority) || 11) -
((b.production_vars && b.production_vars.bodypriority) || 11), ((b.production_vars && b.production_vars.bodypriority) || 11),
sortOrder: sortOrder: state.sortedInfo.columnKey === "bodypriority" && state.sortedInfo.order,
state.sortedInfo.columnKey === "bodypriority" && state.sortedInfo.order, render: (text, record) => <ProductionListColumnBodyPriority record={record} />
render: (text, record) => (
<ProductionListColumnBodyPriority record={record} />
),
}, },
{ {
title: i18n.t("production.labels.paintpriority"), title: i18n.t("production.labels.paintpriority"),
@@ -429,12 +368,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
sorter: (a, b) => sorter: (a, b) =>
((a.production_vars && a.production_vars.paintpriority) || 11) - ((a.production_vars && a.production_vars.paintpriority) || 11) -
((b.production_vars && b.production_vars.paintpriority) || 11), ((b.production_vars && b.production_vars.paintpriority) || 11),
sortOrder: sortOrder: state.sortedInfo.columnKey === "paintpriority" && state.sortedInfo.order,
state.sortedInfo.columnKey === "paintpriority" && render: (text, record) => <ProductionListColumnPaintPriority record={record} />
state.sortedInfo.order,
render: (text, record) => (
<ProductionListColumnPaintPriority record={record} />
),
}, },
{ {
title: i18n.t("production.labels.detailpriority"), title: i18n.t("production.labels.detailpriority"),
@@ -444,110 +379,74 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
sorter: (a, b) => sorter: (a, b) =>
((a.production_vars && a.production_vars.detailpriority) || 11) - ((a.production_vars && a.production_vars.detailpriority) || 11) -
((b.production_vars && b.production_vars.detailpriority) || 11), ((b.production_vars && b.production_vars.detailpriority) || 11),
sortOrder: sortOrder: state.sortedInfo.columnKey === "detailpriority" && state.sortedInfo.order,
state.sortedInfo.columnKey === "detailpriority" && render: (text, record) => <ProductionListColumnDetailPriority record={record} />
state.sortedInfo.order,
render: (text, record) => (
<ProductionListColumnDetailPriority record={record} />
),
}, },
{ {
title: i18n.t("production.labels.sublets"), title: i18n.t("production.labels.sublets"),
dataIndex: "sublets", dataIndex: "sublets",
key: "sublets", key: "sublets",
render: (text, record) => ( render: (text, record) => <ProductionSubletsManageComponent subletJobLines={record.subletLines} />
<ProductionSubletsManageComponent subletJobLines={record.subletLines} />
),
}, },
{ {
title: i18n.t("jobs.fields.employee_body"), title: i18n.t("jobs.fields.employee_body"),
dataIndex: "employee_body", dataIndex: "employee_body",
key: "employee_body", key: "employee_body",
sortOrder: sortOrder: state.sortedInfo.columnKey === "employee_body" && state.sortedInfo.order,
state.sortedInfo.columnKey === "employee_body" &&
state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name, bodyshop.employees?.find((e) => e.id === a.employee_body)?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name bodyshop.employees?.find((e) => e.id === b.employee_body)?.first_name
), ),
render: (text, record) => ( render: (text, record) => <ProductionListEmployeeAssignment record={record} type="employee_body" />
<ProductionListEmployeeAssignment
record={record}
type="employee_body"
/>
),
}, },
{ {
title: i18n.t("jobs.fields.employee_prep"), title: i18n.t("jobs.fields.employee_prep"),
dataIndex: "employee_prep", dataIndex: "employee_prep",
key: "employee_prep", key: "employee_prep",
sortOrder: sortOrder: state.sortedInfo.columnKey === "employee_prep" && state.sortedInfo.order,
state.sortedInfo.columnKey === "employee_prep" &&
state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name, bodyshop.employees?.find((e) => e.id === a.employee_prep)?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name bodyshop.employees?.find((e) => e.id === b.employee_prep)?.first_name
), ),
render: (text, record) => ( render: (text, record) => <ProductionListEmployeeAssignment record={record} type="employee_prep" />
<ProductionListEmployeeAssignment
record={record}
type="employee_prep"
/>
),
}, },
{ {
title: i18n.t("jobs.fields.employee_csr"), title: i18n.t("jobs.fields.employee_csr"),
dataIndex: "employee_csr", dataIndex: "employee_csr",
key: "employee_csr", key: "employee_csr",
sortOrder: sortOrder: state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name, bodyshop.employees?.find((e) => e.id === a.employee_csr)?.first_name,
bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name bodyshop.employees?.find((e) => e.id === b.employee_csr)?.first_name
), ),
render: (text, record) => ( render: (text, record) => <ProductionListEmployeeAssignment record={record} type="employee_csr" />
<ProductionListEmployeeAssignment record={record} type="employee_csr" />
),
}, },
{ {
title: i18n.t("jobs.fields.employee_refinish"), title: i18n.t("jobs.fields.employee_refinish"),
dataIndex: "employee_refinish", dataIndex: "employee_refinish",
key: "employee_refinish", key: "employee_refinish",
sortOrder: sortOrder: state.sortedInfo.columnKey === "employee_refinish" && state.sortedInfo.order,
state.sortedInfo.columnKey === "employee_refinish" &&
state.sortedInfo.order,
sorter: (a, b) => sorter: (a, b) =>
alphaSort( alphaSort(
bodyshop.employees?.find((e) => e.id === a.employee_refinish) bodyshop.employees?.find((e) => e.id === a.employee_refinish)?.first_name,
?.first_name, bodyshop.employees?.find((e) => e.id === b.employee_refinish)?.first_name
bodyshop.employees?.find((e) => e.id === b.employee_refinish)
?.first_name
), ),
render: (text, record) => ( render: (text, record) => <ProductionListEmployeeAssignment record={record} type="employee_refinish" />
<ProductionListEmployeeAssignment
record={record}
type="employee_refinish"
/>
),
}, },
{ {
title: i18n.t("jobs.labels.parts_received"), title: i18n.t("jobs.labels.parts_received"),
dataIndex: "parts_received", dataIndex: "parts_received",
key: "parts_received", key: "parts_received",
render: (text, record) => ( render: (text, record) => <ProductionListColumnPartsReceived record={record} />
<ProductionListColumnPartsReceived record={record} />
),
}, },
{ {
title: i18n.t("jobs.fields.partsstatus"), title: i18n.t("jobs.fields.partsstatus"),
dataIndex: "partsstatus", dataIndex: "partsstatus",
key: "partsstatus", key: "partsstatus",
render: (text, record) => ( render: (text, record) => <JobPartsQueueCount parts={record.joblines_status} record={record} />
<JobPartsQueueCount parts={record.joblines_status} record={record} />
),
}, },
{ {
title: i18n.t("jobs.labels.estimator"), title: i18n.t("jobs.labels.estimator"),
@@ -558,8 +457,7 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
`${a.est_ct_fn || ""} ${a.est_ct_ln || ""}`.trim(), `${a.est_ct_fn || ""} ${a.est_ct_ln || ""}`.trim(),
`${b.est_ct_fn || ""} ${b.est_ct_ln || ""}`.trim() `${b.est_ct_fn || ""} ${b.est_ct_ln || ""}`.trim()
), ),
sortOrder: sortOrder: state.sortedInfo.columnKey === "estimator" && state.sortedInfo.order,
state.sortedInfo.columnKey === "estimator" && state.sortedInfo.order,
filters: filters:
(data && (data &&
data data
@@ -568,16 +466,12 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
.map((s) => { .map((s) => {
return { return {
text: s || "N/A", text: s || "N/A",
value: [s], value: [s]
}; };
})) || })) ||
[], [],
onFilter: (value, record) => onFilter: (value, record) => value.includes(`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()),
value.includes( render: (text, record) => `${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
),
render: (text, record) =>
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
}, },
//Added as a place holder for St Claude. Not implemented as it requires another join for a field used by only 1 client. //Added as a place holder for St Claude. Not implemented as it requires another join for a field used by only 1 client.
@@ -607,12 +501,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "date_repairstarted", key: "date_repairstarted",
ellipsis: true, ellipsis: true,
sorter: (a, b) => dateSort(a.date_repairstarted, b.date_repairstarted), sorter: (a, b) => dateSort(a.date_repairstarted, b.date_repairstarted),
sortOrder: sortOrder: state.sortedInfo.columnKey === "date_repairstarted" && state.sortedInfo.order,
state.sortedInfo.columnKey === "date_repairstarted" && render: (text, record) => <ProductionListDate record={record} field="date_repairstarted" time />
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="date_repairstarted" time />
),
}, },
{ {
title: i18n.t("jobs.fields.date_repairstarted") + " (HH:MM)", title: i18n.t("jobs.fields.date_repairstarted") + " (HH:MM)",
@@ -620,10 +510,8 @@ const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
key: "date_repairstarted_time", key: "date_repairstarted_time",
ellipsis: true, ellipsis: true,
render: (text, record) => ( render: (text, record) => <TimeFormatter>{record.date_repairstarted}</TimeFormatter>
<TimeFormatter>{record.date_repairstarted}</TimeFormatter> }
),
},
]; ];
}; };
export default r; export default r;

View File

@@ -3,12 +3,12 @@ import { useMutation } from "@apollo/client";
import { import {
Button, Button,
Col, Col,
notification,
Popover, Popover,
Row, Row,
Select, Select,
Space, Space,
Spin, Spin,
notification,
} from "antd"; } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -25,8 +25,8 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function ProductionListEmpAssignment({ export function ProductionListEmpAssignment({
@@ -55,6 +55,7 @@ export function ProductionListEmpAssignment({
insertAuditTrail({ insertAuditTrail({
jobid: record.id, jobid: record.id,
operation: AuditTrailMapping.jobassignmentchange(empAssignment, name), operation: AuditTrailMapping.jobassignmentchange(empAssignment, name),
type: "jobassignmentchange",
}); });
if (!!result.errors) { if (!!result.errors) {
@@ -80,6 +81,7 @@ export function ProductionListEmpAssignment({
insertAuditTrail({ insertAuditTrail({
jobid: record.id, jobid: record.id,
operation: AuditTrailMapping.jobassignmentremoved(empAssignment), operation: AuditTrailMapping.jobassignmentremoved(empAssignment),
type: "jobassignmentremoved",
}); });
if (!!result.errors) { if (!!result.errors) {

View File

@@ -12,8 +12,8 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function ProductionListColumnCategory({ record, bodyshop }) { export function ProductionListColumnCategory({ record, bodyshop }) {
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);

View File

@@ -5,16 +5,16 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function ProductionListColumnStatus({ export function ProductionListColumnStatus({
record, record,
@@ -40,6 +40,7 @@ export function ProductionListColumnStatus({
insertAuditTrail({ insertAuditTrail({
jobid: record.id, jobid: record.id,
operation: AuditTrailMapping.jobstatuschange(key), operation: AuditTrailMapping.jobstatuschange(key),
type: "jobstatuschange",
}); });
setLoading(false); setLoading(false);

View File

@@ -6,7 +6,7 @@ import {useTranslation} from "react-i18next";
import {getOrderOperatorsByType, getWhereOperatorsByType} from "../../utils/graphQLmodifier"; import {getOrderOperatorsByType, getWhereOperatorsByType} from "../../utils/graphQLmodifier";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import {generateInternalReflections} from "./report-center-modal-utils"; import {generateInternalReflections} from "./report-center-modal-utils";
import {FormDatePicker} from "../form-date-picker/form-date-picker.component.jsx";
export default function ReportCenterModalFiltersSortersComponent({form, bodyshop}) { export default function ReportCenterModalFiltersSortersComponent({form, bodyshop}) {
return ( return (
@@ -33,7 +33,7 @@ function FiltersSection({filters, form, bodyshop}) {
return ( return (
<Card type='inner' title={t('reportcenter.labels.advanced_filters_filters')} style={{marginTop: '10px'}}> <Card type='inner' title={t('reportcenter.labels.advanced_filters_filters')} style={{marginTop: '10px'}}>
<Form.List name={["filters"]}> <Form.List name={["filters"]}>
{(fields, {add, remove, move}) => { {(fields, {add, remove}) => {
return ( return (
<div> <div>
{fields.map((field, index) => ( {fields.map((field, index) => (
@@ -70,7 +70,9 @@ function FiltersSection({filters, form, bodyshop}) {
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Form.Item dependencies={[['filters', field.name, "field"]]}> <Form.Item
dependencies={[['filters', field.name, "field"],['filters', field.name, "value"]]}
>
{ {
() => { () => {
const name = form.getFieldValue(['filters', field.name, "field"]); const name = form.getFieldValue(['filters', field.name, "field"]);
@@ -80,7 +82,6 @@ function FiltersSection({filters, form, bodyshop}) {
key={`${index}operator`} key={`${index}operator`}
label={t('reportcenter.labels.advanced_filters_filter_operator')} label={t('reportcenter.labels.advanced_filters_filter_operator')}
name={[field.name, "operator"]} name={[field.name, "operator"]}
dependencies={[]}
rules={[ rules={[
{ {
required: true, required: true,
@@ -90,19 +91,32 @@ function FiltersSection({filters, form, bodyshop}) {
> >
<Select <Select
getPopupContainer={trigger => trigger.parentNode} getPopupContainer={trigger => trigger.parentNode}
options={getWhereOperatorsByType(type)}/> options={ getWhereOperatorsByType(type)}
onChange={() => {
// Clear related Fields
form.setFieldValue(['filters', field.name, 'value'], undefined);
}}
/>
</Form.Item> </Form.Item>
} }
} }
</Form.Item> </Form.Item>
</Col> </Col>
<Col span={6}> <Col span={6}>
<Form.Item dependencies={[['filters', field.name, "field"]]}> <Form.Item dependencies={[
['filters', field.name, "field"],
['filters', field.name, "operator"]
]}
>
{ {
() => { () => {
// Because it looks cleaner than inlining.
const name = form.getFieldValue(['filters', field.name, "field"]); const name = form.getFieldValue(['filters', field.name, "field"]);
const type = filters.find(f => f.name === name)?.type; const type = filters.find(f => f.name === name)?.type;
const reflector = filters.find(f => f.name === name)?.reflector; const reflector = filters.find(f => f.name === name)?.reflector;
const operator = form.getFieldValue(['filters', field.name, "operator"]);
const operatorType = operator ? getWhereOperatorsByType(type).find((o) => o.value === operator)?.type : null;
return <Form.Item return <Form.Item
key={`${index}value`} key={`${index}value`}
@@ -128,14 +142,29 @@ function FiltersSection({filters, form, bodyshop}) {
return generateInternalReflections({ return generateInternalReflections({
bodyshop, bodyshop,
upperPath, upperPath,
finalPath finalPath,
t
}); });
}; };
const reflections = reflector ? generateReflections(reflector) : []; const reflections = reflector ? generateReflections(reflector) : [];
const fieldPath = [[field.name, "value"]]; const fieldPath = [[field.name, "value"]];
// We have reflections so we will use a select box
if (reflections.length > 0) { if (reflections.length > 0) {
// We have reflections and the operator type is array, so we will use a select box with multiple options
if (operatorType === "array") {
return (
<Select
disabled={!operator}
mode="multiple"
options={reflections}
getPopupContainer={trigger => trigger.parentNode}
onChange={(value) => {
form.setFieldValue(fieldPath, value);
}}
/>
);
}
return ( return (
<Select <Select
options={reflections} options={reflections}
@@ -147,16 +176,50 @@ function FiltersSection({filters, form, bodyshop}) {
); );
} }
// We have a type of number, so we will use a number input
if (type === "number") { if (type === "number") {
return ( return (
<InputNumber <InputNumber
disabled={!operator}
onChange={(value) => form.setFieldValue(fieldPath, value)}/> onChange={(value) => form.setFieldValue(fieldPath, value)}/>
); );
} }
// We have a type of date, so we will use a date picker
if (type === "date") {
return (
<FormDatePicker
disabled={!operator}
onChange={(date) => form.setFieldValue(fieldPath, date)}
/>
);
}
// we have a type of boolean, so we will use a select box with a true or false option.
if (type === "boolean" || type === "bool") {
return (
<Select
disabled={!operator}
getPopupContainer={trigger => trigger.parentNode}
options={[
{
label: t('reportcenter.labels.advanced_filters_true'),
value: true
},
{
label: t('reportcenter.labels.advanced_filters_false'),
value: false
}
]}
onChange={(value) => form.setFieldValue(fieldPath, value)}
/>
);
}
return ( return (
<Input <Input
onChange={(e) => form.setFieldValue(fieldPath, e.target.value)}/> disabled={!operator}
onChange={(e) => form.setFieldValue(fieldPath, e.target.value)}
/>
); );
})() })()
} }
@@ -203,12 +266,12 @@ function FiltersSection({filters, form, bodyshop}) {
* @returns {JSX.Element} * @returns {JSX.Element}
* @constructor * @constructor
*/ */
function SortersSection({sorters, form}) { function SortersSection({sorters}) {
const {t} = useTranslation(); const {t} = useTranslation();
return ( return (
<Card type='inner' title={t('reportcenter.labels.advanced_filters_sorters')} style={{marginTop: '10px'}}> <Card type='inner' title={t('reportcenter.labels.advanced_filters_sorters')} style={{marginTop: '10px'}}>
<Form.List name={["sorters"]}> <Form.List name={["sorters"]}>
{(fields, {add, remove, move}) => { {(fields, {add, remove}) => {
return ( return (
<div> <div>
Sorters Sorters

View File

@@ -8,6 +8,20 @@ import {uniqBy} from "lodash";
*/ */
const getValueFromPath = (obj, path) => path.split('.').reduce((prev, curr) => prev?.[curr], obj); const getValueFromPath = (obj, path) => path.split('.').reduce((prev, curr) => prev?.[curr], obj);
/**
* Generate options from array
* @param bodyshop
* @param path
* @returns {unknown[]}
*/
const generateOptionsFromArray = (bodyshop, path) => {
const options = getValueFromPath(bodyshop, path);
return uniqBy(options.map((value) => ({
label: value,
value: value,
})), 'value');
}
/** /**
* Valid internal reflections * Valid internal reflections
* Note: This is intended for future functionality * Note: This is intended for future functionality
@@ -42,19 +56,44 @@ const generateOptionsFromObject = (bodyshop, path, labelPath, valuePath) => {
* Generate special reflections * Generate special reflections
* @param bodyshop * @param bodyshop
* @param finalPath * @param finalPath
* @param t - i18n
* @returns {{label: *, value: *}[]|{label: *, value: *}[]|{label: string, value: *}[]|*[]} * @returns {{label: *, value: *}[]|{label: *, value: *}[]|{label: string, value: *}[]|*[]}
*/ */
const generateSpecialReflections = (bodyshop, finalPath) => { const generateSpecialReflections = (bodyshop, finalPath, t) => {
switch (finalPath) { switch (finalPath) {
case 'payment_payers':
return [
{
label: t("payments.labels.customer"),
value: t("payments.labels.customer"),
},
{
label: t("payments.labels.insurance"),
value: t("payments.labels.insurance"),
},
// This is a weird one supposedly only used by one shop and could potentially be
// placed behind a SplitSDK
{
label: t("payments.labels.external"),
value: t("payments.labels.external"),
}
];
case 'payment_types':
return generateOptionsFromArray(bodyshop, 'md_payment_types');
case 'alt_transports':
return generateOptionsFromArray(bodyshop, 'appt_alt_transport');
case 'lost_sale_reasons':
return generateOptionsFromArray(bodyshop, 'md_lost_sale_reasons');
// Special case because Referral Sources is an Array, not an Object.
case 'referral_source':
return generateOptionsFromArray(bodyshop, 'md_referral_sources');
case 'class':
return generateOptionsFromArray(bodyshop, 'md_classes');
case 'cost_centers': case 'cost_centers':
return generateOptionsFromObject(bodyshop, 'md_responsibility_centers.costs', 'name', 'name'); return generateOptionsFromObject(bodyshop, 'md_responsibility_centers.costs', 'name', 'name');
// Special case because Categories is an Array, not an Object. // Special case because Categories is an Array, not an Object.
case 'categories': case 'categories':
const catOptions = getValueFromPath(bodyshop, 'md_categories'); return generateOptionsFromArray(bodyshop, 'md_categories');
return uniqBy(catOptions.map((value) => ({
label: value,
value: value,
})), 'value');
case 'insurance_companies': case 'insurance_companies':
return generateOptionsFromObject(bodyshop, 'md_ins_cos', 'name', 'name'); return generateOptionsFromObject(bodyshop, 'md_ins_cos', 'name', 'name');
case 'employee_teams': case 'employee_teams':
@@ -105,12 +144,13 @@ const generateBodyshopReflections = (bodyshop, finalPath) => {
* @param bodyshop * @param bodyshop
* @param upperPath * @param upperPath
* @param finalPath * @param finalPath
* @param t - i18n
* @returns {{label: *, value: *}[]|[]|{label: *, value: *}[]|{label: string, value: *}[]|{label: *, value: *}[]|*[]} * @returns {{label: *, value: *}[]|[]|{label: *, value: *}[]|{label: string, value: *}[]|{label: *, value: *}[]|*[]}
*/ */
const generateInternalReflections = ({bodyshop, upperPath, finalPath}) => { const generateInternalReflections = ({bodyshop, upperPath, finalPath, t}) => {
switch (upperPath) { switch (upperPath) {
case 'special': case 'special':
return generateSpecialReflections(bodyshop, finalPath); return generateSpecialReflections(bodyshop, finalPath, t);
case 'bodyshop': case 'bodyshop':
return generateBodyshopReflections(bodyshop, finalPath); return generateBodyshopReflections(bodyshop, finalPath);
default: default:
@@ -118,4 +158,4 @@ const generateInternalReflections = ({bodyshop, upperPath, finalPath}) => {
} }
}; };
export {generateInternalReflections,} export {generateInternalReflections}

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

@@ -34,8 +34,8 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("schedule")), toggleModalVisible: () => dispatch(toggleModalVisible("schedule")),
setEmailOptions: (e) => dispatch(setEmailOptions(e)), setEmailOptions: (e) => dispatch(setEmailOptions(e)),
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function ScheduleJobModalContainer({ export function ScheduleJobModalContainer({
@@ -146,6 +146,7 @@ export function ScheduleJobModalContainer({
operation: AuditTrailMapping.appointmentinsert( operation: AuditTrailMapping.appointmentinsert(
DateTimeFormat(values.start) DateTimeFormat(values.start)
), ),
type: "appointmentinsert",
}); });
} }

View File

@@ -25,6 +25,8 @@ export function ScoreboardDayStats({ bodyshop, date, entries }) {
return acc + value.bodyhrs; return acc + value.bodyhrs;
}, 0); }, 0);
const numJobs = entries.length;
return ( return (
<Card <Card
title={moment(date).format("D - ddd")} title={moment(date).format("D - ddd")}
@@ -33,17 +35,18 @@ export function ScoreboardDayStats({ bodyshop, date, entries }) {
> >
<Statistic <Statistic
valueStyle={{ color: dailyBodyTarget > bodyHrs ? "red" : "green" }} valueStyle={{ color: dailyBodyTarget > bodyHrs ? "red" : "green" }}
label="B" label="Body"
value={bodyHrs.toFixed(1)} value={bodyHrs.toFixed(1)}
/> />
<Statistic <Statistic
valueStyle={{ color: dailyPaintTarget > paintHrs ? "red" : "green" }} valueStyle={{ color: dailyPaintTarget > paintHrs ? "red" : "green" }}
label="P" label="Refinish"
value={paintHrs.toFixed(1)} value={paintHrs.toFixed(1)}
/> />
<Divider style={{ margin: 0 }} /> <Divider style={{ margin: 0 }} />
<Statistic label="Total" value={(bodyHrs + paintHrs).toFixed(1)} />
<Statistic value={(bodyHrs + paintHrs).toFixed(1)} /> <Divider style={{ margin: 0 }} />
<Statistic label="Jobs" value={numJobs} />
</Card> </Card>
); );
} }

View File

@@ -29,10 +29,13 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
let ret = { let ret = {
todayBody: 0, todayBody: 0,
todayPaint: 0, todayPaint: 0,
todayJobs: 0,
weeklyPaint: 0, weeklyPaint: 0,
weeklyJobs: 0,
weeklyBody: 0, weeklyBody: 0,
toDateBody: 0, toDateBody: 0,
toDatePaint: 0, toDatePaint: 0,
toDateJobs: 0,
}; };
const today = moment(); const today = moment();
@@ -40,6 +43,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
dateHash[today.format("YYYY-MM-DD")].forEach((d) => { dateHash[today.format("YYYY-MM-DD")].forEach((d) => {
ret.todayBody = ret.todayBody + d.bodyhrs; ret.todayBody = ret.todayBody + d.bodyhrs;
ret.todayPaint = ret.todayPaint + d.painthrs; ret.todayPaint = ret.todayPaint + d.painthrs;
ret.todayJobs++;
}); });
} }
@@ -49,6 +53,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
dateHash[StartOfWeek.format("YYYY-MM-DD")].forEach((d) => { dateHash[StartOfWeek.format("YYYY-MM-DD")].forEach((d) => {
ret.weeklyBody = ret.weeklyBody + d.bodyhrs; ret.weeklyBody = ret.weeklyBody + d.bodyhrs;
ret.weeklyPaint = ret.weeklyPaint + d.painthrs; ret.weeklyPaint = ret.weeklyPaint + d.painthrs;
ret.weeklyJobs++;
}); });
} }
StartOfWeek = StartOfWeek.add(1, "day"); StartOfWeek = StartOfWeek.add(1, "day");
@@ -60,6 +65,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
dateHash[startOfMonth.format("YYYY-MM-DD")].forEach((d) => { dateHash[startOfMonth.format("YYYY-MM-DD")].forEach((d) => {
ret.toDateBody = ret.toDateBody + d.bodyhrs; ret.toDateBody = ret.toDateBody + d.bodyhrs;
ret.toDatePaint = ret.toDatePaint + d.painthrs; ret.toDatePaint = ret.toDatePaint + d.painthrs;
ret.toDateJobs++;
}); });
} }
startOfMonth = startOfMonth.add(1, "day"); startOfMonth = startOfMonth.add(1, "day");
@@ -87,7 +93,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
<Statistic <Statistic
title={t("scoreboard.labels.dailytarget")} title={t("scoreboard.labels.dailytarget")}
value={bodyshop.scoreboard_target.dailyBodyTarget} value={bodyshop.scoreboard_target.dailyBodyTarget}
prefix="B" prefix={t("scoreboard.labels.bodyabbrev")}
/> />
</Col> </Col>
<Col {...statSpans}> <Col {...statSpans}>
@@ -140,7 +146,7 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
<Col {...statSpans}> <Col {...statSpans}>
<Statistic <Statistic
value={bodyshop.scoreboard_target.dailyPaintTarget} value={bodyshop.scoreboard_target.dailyPaintTarget}
prefix="P" prefix={t("scoreboard.labels.refinishabbrev")}
/> />
</Col> </Col>
<Col {...statSpans}> <Col {...statSpans}>
@@ -181,7 +187,12 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
<Divider style={{ margin: 5 }} /> <Divider style={{ margin: 5 }} />
</Row> </Row>
<Row> <Row>
<Col {...statSpans}></Col> <Col {...statSpans}>
<Statistic
value={"\u00A0"}
prefix={t("scoreboard.labels.total")}
/>
</Col>
<Col {...statSpans}> <Col {...statSpans}>
<Statistic <Statistic
value={(values.todayPaint + values.todayBody).toFixed(1)} value={(values.todayPaint + values.todayBody).toFixed(1)}
@@ -240,6 +251,29 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
/> />
</Col> </Col>
</Row> </Row>
<Row>
<Divider style={{ margin: 5 }} />
</Row>
<Row>
<Col {...statSpans}>
<Statistic
value={"\u00A0"}
prefix={t("scoreboard.labels.jobs")}
/>
</Col>
<Col {...statSpans}>
<Statistic value={values.todayJobs} />
</Col>
<Col {...statSpans} />
<Col {...statSpans}>
<Statistic value={values.weeklyJobs} />
</Col>
<Col {...statSpans} />
<Col {...statSpans} />
<Col {...statSpans}>
<Statistic value={values.toDateJobs} />
</Col>
</Row>
</Col> </Col>
</Row> </Row>
</Card> </Card>

View File

@@ -5,6 +5,7 @@ import {
Input, Input,
InputNumber, InputNumber,
Select, Select,
Space,
Switch, Switch,
Typography, Typography,
} from "antd"; } from "antd";
@@ -17,6 +18,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useTreatments } from "@splitsoftware/splitio-react";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
const SelectorDiv = styled.div` const SelectorDiv = styled.div`
.ant-form-item .ant-select { .ant-form-item .ant-select {
@@ -191,7 +193,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.dms.cdk.payers")}> <LayoutFormRow header={t("bodyshop.labels.dms.cdk.payers")}>
<Form.List name={["cdk_configuration", "payers"]}> <Form.List name={["cdk_configuration", "payers"]}>
{(fields, { add, remove }) => { {(fields, { add, remove, move }) => {
return ( return (
<div> <div>
{fields.map((field, index) => ( {fields.map((field, index) => (
@@ -249,11 +251,18 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
</Select> </Select>
</Form.Item> </Form.Item>
<DeleteFilled <Space align="center">
onClick={() => { <DeleteFilled
remove(field.name); onClick={() => {
}} remove(field.name);
/> }}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</LayoutFormRow> </LayoutFormRow>
</Form.Item> </Form.Item>
))} ))}
@@ -345,7 +354,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
id="costs" id="costs"
> >
<Form.List name={["md_responsibility_centers", "costs"]}> <Form.List name={["md_responsibility_centers", "costs"]}>
{(fields, { add, remove }) => { {(fields, { add, remove, move }) => {
return ( return (
<div> <div>
{fields.map((field, index) => ( {fields.map((field, index) => (
@@ -462,12 +471,18 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Input onBlur={handleBlur} /> <Input onBlur={handleBlur} />
</Form.Item> </Form.Item>
)} )}
<Space align="center">
<DeleteFilled <DeleteFilled
onClick={() => { onClick={() => {
remove(field.name); remove(field.name);
}} }}
/> />
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</LayoutFormRow> </LayoutFormRow>
</Form.Item> </Form.Item>
))} ))}
@@ -493,7 +508,7 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
id="profits" id="profits"
> >
<Form.List name={["md_responsibility_centers", "profits"]}> <Form.List name={["md_responsibility_centers", "profits"]}>
{(fields, { add, remove }) => { {(fields, { add, remove, move }) => {
return ( return (
<div> <div>
{fields.map((field, index) => ( {fields.map((field, index) => (
@@ -595,11 +610,18 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<Input onBlur={handleBlur} /> <Input onBlur={handleBlur} />
</Form.Item> </Form.Item>
)} )}
<DeleteFilled <Space align="center">
onClick={() => { <DeleteFilled
remove(field.name); onClick={() => {
}} remove(field.name);
/> }}
/>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</Space>
</LayoutFormRow> </LayoutFormRow>
</Form.Item> </Form.Item>
))} ))}

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,8 +6,11 @@ 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 OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component"; import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -45,6 +48,10 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
title: t("jobs.fields.owner"), title: t("jobs.fields.owner"),
dataIndex: "owner", dataIndex: "owner",
key: "owner", key: "owner",
sorter: (a, b) =>
alphaSort(OwnerNameDisplayFunction(a), OwnerNameDisplayFunction(b)),
sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => (
<Link to={`/manage/owners/${record.owner.id}`}> <Link to={`/manage/owners/${record.owner.id}`}>
<OwnerNameDisplay ownerObject={record} /> <OwnerNameDisplay ownerObject={record} />
@@ -63,11 +70,28 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
title: t("jobs.fields.status"), title: t("jobs.fields.status"),
dataIndex: "status", dataIndex: "status",
key: "status", key: "status",
sorter: (a, b) => statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses), sorter: (a, b) =>
statusSort(a.status, b.status, bodyshop.md_ro_statuses.statuses),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order, state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filters: bodyshop.md_ro_statuses.statuses.map((status) => ({
text: status,
value: 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

@@ -4,8 +4,9 @@ import queryString from "query-string";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
import { pageLimit } from "../../utils/config";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
import {pageLimit} from "../../utils/config"; import { alphaSort } from './../../utils/sorters';
export default function VehiclesListComponent({ export default function VehiclesListComponent({
loading, loading,
vehicles, vehicles,
@@ -31,6 +32,8 @@ export default function VehiclesListComponent({
title: t("vehicles.fields.v_vin"), title: t("vehicles.fields.v_vin"),
dataIndex: "v_vin", dataIndex: "v_vin",
key: "v_vin", key: "v_vin",
sorter: (a, b) => alphaSort(a.v_vin, b.v_vin),
sortOrder: state.sortedInfo.columnKey === "v_vin" && state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => (
<Link to={"/manage/vehicles/" + record.id}> <Link to={"/manage/vehicles/" + record.id}>
<VehicleVinDisplay>{record.v_vin || "N/A"}</VehicleVinDisplay> <VehicleVinDisplay>{record.v_vin || "N/A"}</VehicleVinDisplay>
@@ -51,8 +54,10 @@ export default function VehiclesListComponent({
}, },
{ {
title: t("vehicles.fields.plate_no"), title: t("vehicles.fields.plate_no"),
dataIndex: "plate", dataIndex: "plate_no",
key: "plate", key: "plate_no",
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder: state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
return ( return (
<span>{`${record.plate_st || ""} | ${record.plate_no || ""}`}</span> <span>{`${record.plate_st || ""} | ${record.plate_no || ""}`}</span>

View File

@@ -4,6 +4,8 @@ import { getAuth, updatePassword, updateProfile } from "firebase/auth";
import { getFirestore } from "firebase/firestore"; import { getFirestore } from "firebase/firestore";
import { getMessaging, getToken, onMessage } from "firebase/messaging"; import { getMessaging, getToken, onMessage } from "firebase/messaging";
import { store } from "../redux/store"; import { store } from "../redux/store";
import axios from "axios";
import { checkBeta } from "../utils/handleBeta";
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG); const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
initializeApp(config); initializeApp(config);
@@ -86,6 +88,18 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
null, null,
...additionalParams, ...additionalParams,
}; };
axios.post("/ioevent", {
useremail:
(state.user && state.user.currentUser && state.user.currentUser.email) ||
null,
bodyshopid:
(state.user && state.user.bodyshop && state.user.bodyshop.id) || null,
operationName: eventName,
variables: additionalParams,
dbevent: false,
env: checkBeta() ? "beta" : "master",
});
// console.log( // console.log(
// "%c[Analytics]", // "%c[Analytics]",
// "background-color: green ;font-weight:bold;", // "background-color: green ;font-weight:bold;",

View File

@@ -40,6 +40,7 @@ export const INSERT_AUDIT_TRAIL = gql`
bodyshopid bodyshopid
created created
operation operation
type
useremail useremail
} }
} }

View File

@@ -58,7 +58,7 @@ export const QUERY_ALL_BILLS_PAGINATED = gql`
} }
`; `;
export const QUERY_BILLS_BY_JOBID = gql` export const QUERY_PARTS_BILLS_BY_JOBID = gql`
query QUERY_PARTS_BILLS_BY_JOBID($jobid: uuid!) { query QUERY_PARTS_BILLS_BY_JOBID($jobid: uuid!) {
parts_orders( parts_orders(
where: { jobid: { _eq: $jobid } } where: { jobid: { _eq: $jobid } }
@@ -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

@@ -1,7 +1,7 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql` export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED( query QUERY_ALL_ACTIVE_JOBS_PAGINATED(
$offset: Int $offset: Int
$limit: Int $limit: Int
$order: [jobs_order_by!] $order: [jobs_order_by!]
@@ -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
@@ -1862,6 +1863,7 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
ownr_co_nm ownr_co_nm
ownr_ph1 ownr_ph1
ownr_ph2 ownr_ph2
ownerid
plate_no plate_no
plate_st plate_st
v_vin v_vin
@@ -2203,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,7 +30,9 @@ 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_fn ownr_fn
ownr_ln ownr_ln
owner { owner {

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

@@ -40,8 +40,8 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)), setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer); export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
@@ -127,6 +127,7 @@ export function DmsContainer({
insertAuditTrail({ insertAuditTrail({
jobid: payload, jobid: payload,
operation: AuditTrailMapping.jobexported(), operation: AuditTrailMapping.jobexported(),
type: "jobexported",
}); });
history.push("/manage/accounting/receivables"); history.push("/manage/accounting/receivables");
}); });

View File

@@ -12,7 +12,8 @@ import AlertComponent from "../../components/alert/alert.component";
import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries"; import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import {pageLimit} from "../../utils/config"; import { pageLimit } from "../../utils/config";
import { alphaSort, dateSort } from "./../../utils/sorters";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -34,11 +35,43 @@ export function ExportLogsPageComponent({ bodyshop }) {
limit: pageLimit, limit: pageLimit,
order: [ order: [
{ {
[sortcolumn || "created_at"]: sortorder ...(sortcolumn === "ro_number"
? sortorder === "descend" ? {
? "desc" job: {
: "asc" [sortcolumn || "created_at"]: sortorder
: "desc", ? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
}
: sortcolumn === "invoice_number"
? {
bill: {
[sortcolumn || "created_at"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
}
: sortcolumn === "paymentnum"
? {
payment: {
[sortcolumn || "created_at"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
},
}
: {
[sortcolumn || "created_at"]: sortorder
? sortorder === "descend"
? "desc"
: "asc"
: "desc",
}),
}, },
], ],
}, },
@@ -68,6 +101,8 @@ export function ExportLogsPageComponent({ bodyshop }) {
title: t("general.labels.created_at"), title: t("general.labels.created_at"),
dataIndex: "created_at", dataIndex: "created_at",
key: "created_at", key: "created_at",
sorter: (a, b) => dateSort(a.created_at, b.created_at),
sortOrder: sortcolumn === "created_at" && sortorder,
render: (text, record) => ( render: (text, record) => (
<DateTimeFormatter>{record.created_at}</DateTimeFormatter> <DateTimeFormatter>{record.created_at}</DateTimeFormatter>
), ),
@@ -81,7 +116,8 @@ export function ExportLogsPageComponent({ bodyshop }) {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) => render: (text, record) =>
record.job && ( record.job && (
<Link to={`/manage/jobs/${record.job.id}`}> <Link to={`/manage/jobs/${record.job.id}`}>
@@ -93,6 +129,8 @@ export function ExportLogsPageComponent({ bodyshop }) {
title: t("bills.fields.invoice_number"), title: t("bills.fields.invoice_number"),
dataIndex: "invoice_number", dataIndex: "invoice_number",
key: "invoice_number", key: "invoice_number",
sorter: (a, b) => alphaSort(a.invoice_number, b.invoice_number),
sortOrder: sortcolumn === "invoice_number" && sortorder,
render: (text, record) => render: (text, record) =>
record.bill && ( record.bill && (
<Link to={"/manage/bills?billid=" + (record.bill && record.bill.id)}> <Link to={"/manage/bills?billid=" + (record.bill && record.bill.id)}>
@@ -104,6 +142,8 @@ export function ExportLogsPageComponent({ bodyshop }) {
title: t("payments.fields.paymentnum"), title: t("payments.fields.paymentnum"),
dataIndex: "paymentnum", dataIndex: "paymentnum",
key: "paymentnum", key: "paymentnum",
sorter: (a, b) => alphaSort(a.paymentnum, b.paymentnum),
sortOrder: sortcolumn === "paymentnum" && sortorder,
render: (text, record) => render: (text, record) =>
record.payment && ( record.payment && (
<Link <Link
@@ -120,9 +160,14 @@ export function ExportLogsPageComponent({ bodyshop }) {
title: t("general.labels.successful"), title: t("general.labels.successful"),
dataIndex: "successful", dataIndex: "successful",
key: "successful", key: "successful",
render: (text, record) => ( sorter: (a, b) => Number(a.successful) - Number(b.successful),
<Checkbox disabled checked={record.successful} /> sortOrder: sortcolumn === "successful" && sortorder,
), filters: [
{ text: "True", value: true },
{ text: "False", value: false },
],
onFilter: (value, record) => record.successful === value,
render: (text, record) => <Checkbox checked={record.successful} />,
}, },
{ {
title: t("general.labels.message"), title: t("general.labels.message"),

View File

@@ -47,11 +47,11 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, }) { export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const client = useApolloClient(); const client = useApolloClient();
@@ -121,6 +121,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, })
insertAuditTrail({ insertAuditTrail({
jobid: job.id, jobid: job.id,
operation: AuditTrailMapping.jobinvoiced(), operation: AuditTrailMapping.jobinvoiced(),
type: "jobinvoiced",
}); });
// history.push(`/manage/jobs/${job.id}`); // history.push(`/manage/jobs/${job.id}`);
} else { } else {

View File

@@ -8,6 +8,7 @@ import Icon, {
SyncOutlined, SyncOutlined,
ToolFilled, ToolFilled,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { import {
Button, Button,
Divider, Divider,
@@ -29,6 +30,7 @@ import { createStructuredSelector } from "reselect";
import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component"; import FormFieldsChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component"; import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component";
import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container"; import JobsLinesContainer from "../../components/job-detail-lines/job-lines.container";
import JobLifecycleComponent from "../../components/job-lifecycle/job-lifecycle.component";
import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container"; import JobLineUpsertModalContainer from "../../components/job-lines-upsert-modal/job-lines-upsert-modal.container";
import JobReconciliationModal from "../../components/job-reconciliation-modal/job-reconciliation.modal.container"; import JobReconciliationModal from "../../components/job-reconciliation-modal/job-reconciliation.modal.container";
import JobSyncButton from "../../components/job-sync-button/job-sync-button.component"; import JobSyncButton from "../../components/job-sync-button/job-sync-button.component";
@@ -47,6 +49,7 @@ import JobsDocumentsLocalGallery from "../../components/jobs-documents-local-gal
import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container"; import JobNotesContainer from "../../components/jobs-notes/jobs-notes.container";
import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container"; import NoteUpsertModalComponent from "../../components/note-upsert-modal/note-upsert-modal.container";
import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container"; import ScheduleJobModalContainer from "../../components/schedule-job-modal/schedule-job-modal.container";
import { QUERY_PARTS_BILLS_BY_JOBID } from "../../graphql/bills.queries.js";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
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";
@@ -54,7 +57,6 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import UndefinedToNull from "../../utils/undefinedtonull"; import UndefinedToNull from "../../utils/undefinedtonull";
import { DateTimeFormat } from "./../../utils/DateFormatter"; import { DateTimeFormat } from "./../../utils/DateFormatter";
import JobLifecycleComponent from "../../components/job-lifecycle/job-lifecycle.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -63,8 +65,8 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) => setPrintCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "printCenter" })), dispatch(setModalContext({ context: context, modal: "printCenter" })),
insertAuditTrail: ({ jobid, operation }) => insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, operation })), dispatch(insertAuditTrail({ jobid, operation, type })),
}); });
export function JobsDetailPage({ export function JobsDetailPage({
bodyshop, bodyshop,
@@ -78,19 +80,49 @@ export function JobsDetailPage({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const history = useHistory();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const history = useHistory();
const formItemLayout = { const formItemLayout = {
layout: "vertical", layout: "vertical",
}; };
const billsQuery = useQuery(QUERY_PARTS_BILLS_BY_JOBID, {
variables: { jobid: job.id },
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
useEffect(() => { useEffect(() => {
//form.setFieldsValue(transormJobToForm(job)); //form.setFieldsValue(transormJobToForm(job));
form.resetFields(); form.resetFields();
}, [form, job]); }, [form, job]);
const handleBillOnRowClick = (record) => {
if (record) {
if (record.id) {
search.billid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.billid;
history.push({ search: queryString.stringify(search) });
}
};
const handlePartsOrderOnRowClick = (record) => {
if (record) {
if (record.id) {
search.partsorderid = record.id;
history.push({ search: queryString.stringify(search) });
}
} else {
delete search.partsorderid;
history.push({ search: queryString.stringify(search) });
}
};
//useKeyboardSaveShortcut(form.submit); //useKeyboardSaveShortcut(form.submit);
const handleFinish = async (values) => { const handleFinish = async (values) => {
@@ -177,6 +209,7 @@ export function JobsDetailPage({
? DateTimeFormat(changedAuditFields[key]) ? DateTimeFormat(changedAuditFields[key])
: changedAuditFields[key] : changedAuditFields[key]
), ),
type: "jobfieldchange",
}); });
}); });
@@ -285,6 +318,9 @@ export function JobsDetailPage({
<JobsLinesContainer <JobsLinesContainer
job={job} job={job}
joblines={job.joblines} joblines={job.joblines}
billsQuery={billsQuery}
handleBillOnRowClick={handleBillOnRowClick}
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
refetch={refetch} refetch={refetch}
form={form} form={form}
/> />
@@ -321,7 +357,12 @@ export function JobsDetailPage({
} }
key="partssublet" key="partssublet"
> >
<JobsDetailPliContainer job={job} /> <JobsDetailPliContainer
job={job}
billsQuery={billsQuery}
handleBillOnRowClick={handleBillOnRowClick}
handlePartsOrderOnRowClick={handlePartsOrderOnRowClick}
/>
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane <Tabs.TabPane
tab={ tab={
@@ -334,15 +375,23 @@ export function JobsDetailPage({
> >
<JobsDetailLaborContainer job={job} jobId={job.id} /> <JobsDetailLaborContainer job={job} jobId={job.id} />
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane <Tabs.TabPane
forceRender forceRender
tab={<span><BarsOutlined />{t('menus.jobsdetail.lifecycle')}</span>} tab={
key="lifecycle" <span>
> <BarsOutlined />
<JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses}/> {t("menus.jobsdetail.lifecycle")}
</Tabs.TabPane> </span>
}
key="lifecycle"
>
<JobLifecycleComponent
job={job}
statuses={bodyshop.md_ro_statuses}
/>
</Tabs.TabPane>
<Tabs.TabPane <Tabs.TabPane
forceRender forceRender
tab={ tab={
<span> <span>

View File

@@ -54,9 +54,9 @@ export const setOnline = (isOnline) => ({
payload: isOnline, payload: isOnline,
}); });
export const insertAuditTrail = ({ jobid, billid, operation }) => ({ export const insertAuditTrail = ({ jobid, billid, operation, type }) => ({
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL, type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
payload: { jobid, billid, operation }, payload: { jobid, billid, operation, type },
}); });
export const setProblemJobs = (problemJobs) => ({ export const setProblemJobs = (problemJobs) => ({
type: ApplicationActionTypes.SET_PROBLEM_JOBS, type: ApplicationActionTypes.SET_PROBLEM_JOBS,

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,
}; };
} }
}); });
@@ -266,7 +299,7 @@ export function* onInsertAuditTrail() {
} }
export function* insertAuditTrailSaga({ export function* insertAuditTrailSaga({
payload: { jobid, billid, operation }, payload: { jobid, billid, operation, type },
}) { }) {
const state = yield select(); const state = yield select();
const bodyshop = state.user.bodyshop; const bodyshop = state.user.bodyshop;
@@ -278,6 +311,7 @@ export function* insertAuditTrailSaga({
jobid, jobid,
billid, billid,
operation, operation,
type,
useremail: currentUser.email, useremail: currentUser.email,
}, },
}; };

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