Compare commits

..

188 Commits

Author SHA1 Message Date
Patrick Fic
5db43dd065 IO-70 Add basic functions of OEC. 2021-11-29 15:04:34 -08:00
Patrick Fic
53be0bbc1a IO-70 OEC WIP. 2021-11-29 08:57:44 -08:00
Patrick Fic
bfcc03850b IO-70 Add OEC Through Partner Call. 2021-11-23 00:30:11 -08:00
Patrick Fic
65402c1420 Clean up console log statements. 2021-11-22 23:20:13 -08:00
Patrick Fic
b462b2fa03 IO-1485 Auto CC on parts order. 2021-11-22 15:39:12 -08:00
Patrick Fic
0c26b90591 IO-1533 Resolve board rerender. 2021-11-21 21:59:33 -08:00
Patrick Fic
f8f2384c54 Merged in release/2021-11-19 (pull request #272)
release/2021-11-19

Approved-by: Patrick Fic
2021-11-19 20:08:53 +00:00
Patrick Fic
ef18cf0718 IO-233 CDK Updates. 2021-11-19 10:50:38 -08:00
Patrick Fic
9d35fc85ad Merged in release/2021-11-19 (pull request #271)
release/2021-11-19

Approved-by: Patrick Fic
2021-11-18 18:39:14 +00:00
Patrick Fic
e98f9763fd Resolve QB Payments Refund Issue. 2021-11-18 10:38:04 -08:00
Patrick Fic
95e5385cd1 Merged in release/2021-11-19 (pull request #270)
release/2021-11-19

Approved-by: Patrick Fic
2021-11-18 17:19:57 +00:00
Patrick Fic
46abf01366 IO-1533 Resolve defaults check issue for production board. 2021-11-18 09:19:34 -08:00
Patrick Fic
87f06425e1 Merged in release/2021-11-19 (pull request #269)
release/2021-11-19

Approved-by: Patrick Fic
2021-11-18 06:19:53 +00:00
Patrick Fic
45ab7543d5 Resolve CI Issues. 2021-11-17 22:18:09 -08:00
Patrick Fic
120e4fc94c Merged in release/2021-11-19 (pull request #268)
release/2021-11-19

Approved-by: Patrick Fic
2021-11-18 06:17:09 +00:00
Patrick Fic
fed16efd10 IO-1533 Added check for null settings on kanban. 2021-11-17 22:16:43 -08:00
Patrick Fic
ea6277c586 Merged in release/2021-11-19 (pull request #267)
release/2021-11-19

Approved-by: Patrick Fic
2021-11-18 06:08:21 +00:00
Patrick Fic
49bf461c36 IO-1533 Kanban Customizations 2021-11-17 22:07:50 -08:00
Patrick Fic
696781c857 IO-1533 Kanban Customizations 2021-11-17 21:47:05 -08:00
Patrick Fic
5540872f62 IO-1531 CC Contract add to production. 2021-11-17 20:18:21 -08:00
Patrick Fic
dac53a56c3 Merged in release/2021-11-19 (pull request #266)
release/2021-11-19

Approved-by: Patrick Fic
2021-11-18 01:30:23 +00:00
Patrick Fic
69690f0184 IO-1533 Kanban Customizations 2021-11-17 15:29:04 -08:00
Patrick Fic
f11a4c93ac IO-233 CDK Updates 2021-11-17 14:44:07 -08:00
Patrick Fic
d74870812e Merged in release/2021-11-19 (pull request #265)
release/2021-11-19

Approved-by: Patrick Fic
2021-11-16 01:17:31 +00:00
Patrick Fic
9eed33b5f2 Resolve CI issues with missing imports. 2021-11-15 17:15:51 -08:00
Patrick Fic
55b7715e1c IO-1529 Added gateway templates. 2021-11-15 17:14:12 -08:00
Patrick Fic
c129b5ba8c IO-1533 Kanban Customization 2021-11-15 13:53:54 -08:00
Patrick Fic
efc2844d99 IO-1530 Email individual note. 2021-11-15 11:06:10 -08:00
Patrick Fic
ab38f85d38 Package updates. 2021-11-15 10:36:41 -08:00
Patrick Fic
81eccba393 Merged in release/2021-11-12 (pull request #263)
release/2021-11-12

Approved-by: Patrick Fic
2021-11-12 21:46:33 +00:00
Patrick Fic
b3b49fd4ca Email test fix & Shop Config validation for resp centers. 2021-11-12 13:46:01 -08:00
Patrick Fic
b7a08db4e7 Merged in release/2021-11-12 (pull request #262)
IO-117 PBS WIP

Approved-by: Patrick Fic
2021-11-11 02:13:28 +00:00
Patrick Fic
2d63c3d576 IO-1528 Autohouse Updates. 2021-11-10 18:06:24 -08:00
Patrick Fic
5f4a2392af Costmetic updates & limit of conversations in subscription. 2021-11-10 17:09:09 -08:00
Patrick Fic
f6fe8be7c4 IO-1491 Simplify production query to use ignore employee relationships and match locally. 2021-11-10 16:54:02 -08:00
Patrick Fic
89af6d23e8 IO-1513 Added next contact date. 2021-11-10 16:22:52 -08:00
Patrick Fic
7e1431d65e IO-117 WIP PBS 2021-11-10 15:30:07 -08:00
Patrick Fic
d50e845ba0 IO-117 PBS WIP 2021-11-10 08:18:29 -08:00
Patrick Fic
6796c35e5b Merged in release/2021-11-12 (pull request #260)
release/2021-11-12

Approved-by: Patrick Fic
2021-11-10 01:06:57 +00:00
Patrick Fic
4a68a10005 IO-1486 Add dropdown for file handler. 2021-11-09 17:05:20 -08:00
Patrick Fic
17088b3025 IO-1188 Add AB proof of loss. 2021-11-09 16:33:20 -08:00
Patrick Fic
4e3c659b6d IO-1497 Ability to delete parts orders. 2021-11-09 16:31:57 -08:00
Patrick Fic
6afcf82cc4 IO-1505 Resolve PST exempt exporting to QBO. 2021-11-09 16:04:16 -08:00
Patrick Fic
f4ddb40bde IO-117 PBS WIP 2021-11-09 13:56:52 -08:00
Patrick Fic
6017e2172e IO-117 PBS WIP 2021-11-09 10:38:07 -08:00
Patrick Fic
bef9e64bf8 Resolve associations permissions to allow adjustments of RBAC. 2021-11-08 11:23:37 -08:00
Patrick Fic
e8d95bdb68 IO-1508 Add # of jobs in production. 2021-11-08 11:12:56 -08:00
Patrick Fic
10fb7d9d96 IO-1499 Reconciliation to consider quantity. 2021-11-08 10:50:21 -08:00
Patrick Fic
2ec196e664 IO-1510 Missing report templates 2021-11-08 10:29:16 -08:00
Patrick Fic
1f38b98bfd Package Updates 2021-11-08 10:27:38 -08:00
Patrick Fic
a7c76386bc Merged in release/2021-11-01 (pull request #258)
release/2021-11-01

Approved-by: Patrick Fic
2021-11-01 16:00:52 +00:00
Patrick Fic
042b67c531 IO-1438 Resolve issue with texting 2021-11-01 08:57:30 -07:00
Patrick Fic
2e72ed3698 IO-1438 Resolve Texting Issue 2021-11-01 08:40:53 -07:00
Patrick Fic
bc01f46388 Merge branch 'release/2021-10-29' into test 2021-10-29 13:48:27 -07:00
Patrick Fic
74f791541f IO-256 Resolve no invoice on CM QBO export. 2021-10-29 13:48:17 -07:00
Patrick Fic
84c7fdba5a Merge branch 'release/2021-10-29' into test 2021-10-29 11:30:53 -07:00
Patrick Fic
630fa23f6c Resolve uneeded import CI error. 2021-10-29 11:30:43 -07:00
Patrick Fic
8f58f09c8c Merge branch 'release/2021-10-29' into test 2021-10-29 11:19:36 -07:00
Patrick Fic
a0922f2944 IO-1476 Add Hrs to schedule modal. 2021-10-29 11:18:17 -07:00
Patrick Fic
b8f001625b IO-1502 PST on Adjustments for QBO 2021-10-29 10:55:35 -07:00
Patrick Fic
53394efebf IO-1503 QBO Credit Memos 2021-10-28 09:56:59 -07:00
Patrick Fic
d8296e1d01 Merge branch 'release/2021-10-29' into test 2021-10-27 11:37:04 -07:00
Patrick Fic
2c00e5ee79 IO-256 Resolve QBO Aposstroph Issues 2021-10-27 11:36:40 -07:00
Patrick Fic
34e220dcad IO-233 Update Regex for CDK. 2021-10-27 10:46:06 -07:00
Patrick Fic
fa1fffd8b9 Merge branch 'release/2021-10-29' into test 2021-10-27 08:39:20 -07:00
Patrick Fic
67eb430ff9 Add missing query field to CDK. 2021-10-27 08:39:05 -07:00
Patrick Fic
ff53355b4a Merge branch 'release/2021-10-29' into test 2021-10-27 08:30:03 -07:00
Patrick Fic
331b2c517b Update CDK Country Inclusion. 2021-10-27 08:29:43 -07:00
Patrick Fic
05d9f20a66 Mark Other RO field as Read Only 2021-10-26 15:38:05 -07:00
Patrick Fic
9e3a2e920d Merge branch 'release/2021-10-29' into test 2021-10-26 13:10:37 -07:00
Patrick Fic
1511b87959 IO-1487 MPI Totals Adjustments 2021-10-26 13:10:07 -07:00
Patrick Fic
9c3e4b7b83 IO-1489 Resolve payables memo for QBO. 2021-10-25 17:25:29 -07:00
Patrick Fic
b718f49071 IO-256 Resolve QBO Payable Vendor Insert. 2021-10-25 17:05:21 -07:00
Patrick Fic
e7157119ae Merged in release/2021-10-29 (pull request #256)
Release/2021 10 29
2021-10-25 21:58:53 +00:00
Patrick Fic
09187bdb7f Remove manual scheduling for CI. 2021-10-25 14:57:27 -07:00
Patrick Fic
2aed392fbe IO-658 IO-652 IO-657 add MPI Templates. 2021-10-25 14:46:24 -07:00
Patrick Fic
1a0054a911 IO-1438 Resolve inbound conversation creation. 2021-10-25 11:00:31 -07:00
Patrick Fic
6e6f3d3d3e Resolve user email naming if null. 2021-10-25 10:46:30 -07:00
Patrick Fic
29df140680 IO-1402 Respect blocked day today for scheduling. 2021-10-25 10:41:22 -07:00
Patrick Fic
f37c67a122 Merge branch 'master' into release/2021-10-29 2021-10-25 10:31:49 -07:00
Patrick Fic
e53c9aab72 IO-1379 CSI invite Key update. 2021-10-25 10:24:18 -07:00
Patrick Fic
385ad06adc IO-1375 Special characters in CDK 2021-10-25 10:12:43 -07:00
Patrick Fic
d1e2d943a9 Package updates. 2021-10-25 09:41:54 -07:00
Patrick Fic
90aa3557b7 Merged in release/2021-10-22 (pull request #255)
Change Nod Version.

Approved-by: Patrick Fic
2021-10-22 21:38:53 +00:00
Patrick Fic
8891167183 Merged in release/2021-10-22 (pull request #254)
release/2021-10-22

Approved-by: Patrick Fic
2021-10-22 21:38:35 +00:00
Patrick Fic
6631e645df Change Nod Version. 2021-10-22 14:38:05 -07:00
Patrick Fic
b7f202969b Merged in release/2021-10-22 (pull request #253)
release/2021-10-22

Approved-by: Patrick Fic
2021-10-22 21:29:49 +00:00
Patrick Fic
1dc3353ecc Merge branch 'release/2021-10-22' into test 2021-10-22 13:01:41 -07:00
Patrick Fic
b1a3f1a7b8 Finish revert stripe removal. 2021-10-22 13:01:26 -07:00
Patrick Fic
89f3a26635 Revert "Disable stripe promise."
This reverts commit 3ece5e0ba2.
2021-10-22 12:59:45 -07:00
Patrick Fic
506fe9b1af Merge branch 'release/2021-10-22' into test 2021-10-22 10:47:12 -07:00
Patrick Fic
37c898d3ce IO-256 Resolve QBO issues. 2021-10-22 10:46:03 -07:00
Patrick Fic
e4eac5714a Merge branch 'release/2021-10-22' into test 2021-10-22 10:00:42 -07:00
Patrick Fic
9d4a59ca16 IO-256 Remove credentials with Axios calls for QBO. 2021-10-22 10:00:23 -07:00
Patrick Fic
8ec524061a Merge branch 'release/2021-10-22' into test 2021-10-21 14:32:06 -07:00
Patrick Fic
434ed46b5a IO-1469 Add keep schedule details to job detail reschedule. 2021-10-21 14:30:50 -07:00
Patrick Fic
3ece5e0ba2 Disable stripe promise. 2021-10-21 14:16:18 -07:00
Patrick Fic
52a383ffb7 IO-1454 Disable production colors & add feature flags. 2021-10-21 14:03:48 -07:00
Patrick Fic
8ad1d5929a Reduce Sentry Logging. 2021-10-21 12:02:02 -07:00
Patrick Fic
445c01499b Merge branch 'release/2021-10-22' into test 2021-10-21 11:42:33 -07:00
Patrick Fic
0b05be841d Merge branch 'release/2021-10-22' of bitbucket.org:snaptsoft/bodyshop into release/2021-10-22 2021-10-21 11:42:10 -07:00
Patrick Fic
6bcb5f2af5 IO-1446 Smart Schedule Blocked Day Fixes 2021-10-21 11:42:05 -07:00
Patrick Fic
7482751c5b IO-1478 Make CC Contract RO lines editable. 2021-10-21 09:43:14 -07:00
Patrick Fic
7a025fff42 Merge branch 'release/2021-10-22' into test 2021-10-21 08:59:49 -07:00
Patrick Fic
602fe36638 QBO Cleanup. 2021-10-21 08:58:24 -07:00
Patrick Fic
da08fc74f1 IO-1475 Totals update for Mitchell Cloud discounts. 2021-10-21 08:53:15 -07:00
Patrick Fic
14af45baf0 IO-256 QBO SS Realm ID 2021-10-20 14:33:31 -07:00
Patrick Fic
420a88c505 Merged in release/2021-10-22 (pull request #252)
release/2021-10-22

Approved-by: Patrick Fic
2021-10-19 05:12:44 +00:00
Patrick Fic
f3c44f8dd1 Merge branch 'feature/pbs' into release/2021-10-22 2021-10-18 22:12:16 -07:00
Patrick Fic
c72111e18b Merged in release/2021-10-22 (pull request #251)
release/2021-10-22

Approved-by: Patrick Fic
2021-10-19 05:04:35 +00:00
Patrick Fic
1ca2870912 IO-1437 Add deductible Note 2021-10-18 22:02:45 -07:00
Patrick Fic
db9744e1e5 IO-1454 Production List Visual Indicators. 2021-10-18 21:54:24 -07:00
Patrick Fic
e700095551 IO-1492 Add estimator Presets 2021-10-18 20:26:57 -07:00
Patrick Fic
99196a77ed IO-1433 Add Loss of Use Field 2021-10-18 19:56:12 -07:00
Patrick Fic
289a8222a0 IO-223 ARM development 2021-10-18 19:37:27 -07:00
Patrick Fic
dc10f8d35b IO-1469 Retain event detals when rescheduling. 2021-10-18 13:56:48 -07:00
Patrick Fic
f448232fe7 IO-1435 Missed additional query. 2021-10-18 13:52:30 -07:00
Patrick Fic
4baf4b4afa IO-1435 Add scheduled time to job header if scheduled. 2021-10-18 13:48:23 -07:00
Patrick Fic
1785093023 IO-1449 Adjust display of time in schedulign setup. 2021-10-18 13:43:15 -07:00
Patrick Fic
0d65f8d894 IO-1407 Resolve bill not clearing on enter again 2021-10-18 13:40:31 -07:00
Patrick Fic
3d8c390291 IO-1408 Resolve bill form index issue. 2021-10-18 13:29:35 -07:00
Patrick Fic
f0a13856bc IO-1439 Trim QB name fields on export. 2021-10-18 12:16:12 -07:00
Patrick Fic
ad6394783d IO-1443 Expand Priority selection to 15. 2021-10-18 11:22:01 -07:00
Patrick Fic
16e9843298 Package Updates. 2021-10-18 11:21:51 -07:00
Patrick Fic
a4c949c376 Merged in release/2021-10-22 (pull request #250)
Release/2021 10 22
2021-10-18 17:01:18 +00:00
Patrick Fic
0e0d5316b7 Undo Quickbooks CORS to resolve prod error. 2021-10-18 07:55:36 -07:00
Patrick Fic
60cb6ee8fb IO-223 Begin ARMS Integration 2021-10-15 17:55:55 -07:00
Patrick Fic
7d3279d21a Merged in release/2021-10-15 (pull request #249)
Release/2021 10 15
2021-10-15 21:33:24 +00:00
Patrick Fic
402d13ad99 Merge branch 'test' into release/2021-10-15 2021-10-15 10:53:50 -07:00
Patrick Fic
e803f5a2d4 IO-1471 Total Hrs in produciton. 2021-10-15 10:53:24 -07:00
Patrick Fic
3989d0f1e2 Merge branch 'test' into feature/pbs 2021-10-14 17:20:15 -07:00
Patrick Fic
161d476ab3 IO-256 Export payments with no matching invoice & export cust data. 2021-10-14 15:07:31 -07:00
Patrick Fic
ce84a89cf3 Merge branch 'release/2021-10-15' into test 2021-10-14 11:22:14 -07:00
Patrick Fic
1d210a9e52 IO-1468 QB and CDK Updates for MCE Markup 2021-10-14 11:21:06 -07:00
Patrick Fic
660f463aea IO-1468 Resolve line markup for MCE. 2021-10-14 10:52:08 -07:00
Patrick Fic
ad9f01111c Merge branch 'release/2021-10-15' into test 2021-10-13 21:22:55 -07:00
Patrick Fic
1b885e4114 IO-256 Add description to receivables export. 2021-10-13 21:22:38 -07:00
Patrick Fic
b56742bcb2 IO-256 QBO Improvements. 2021-10-13 21:12:02 -07:00
Patrick Fic
40d4d69a9a Merge branch 'release/2021-10-15' into test 2021-10-13 17:03:00 -07:00
Patrick Fic
b54d5beb76 IO-256 Resolve Bill Center reference 2021-10-13 17:02:39 -07:00
Patrick Fic
5cbcd440f5 Merge branch 'feature/qbo' into test 2021-10-13 15:49:02 -07:00
Patrick Fic
1cdc34249a IO-256 Fix null handling for missing metadata. 2021-10-13 15:48:43 -07:00
Patrick Fic
8bbb218777 Merge branch 'feature/qbo' into test
# Conflicts:
#	server/accounting/qbo/qbo-payables.js
#	server/accounting/qbo/qbo-payments.js
#	server/accounting/qbo/qbo-receivables.js
2021-10-13 15:25:02 -07:00
Patrick Fic
755ac7f657 IO-256 Improved Logging 2021-10-13 15:24:14 -07:00
Patrick Fic
d7b884ff86 Revert "IO-256 Genericize email."
This reverts commit fda3620ed0.
2021-10-13 15:14:26 -07:00
Patrick Fic
fda3620ed0 IO-256 Genericize email. 2021-10-13 15:10:36 -07:00
Patrick Fic
61ad9f0d58 IO-256 Add CORS back for QBO. 2021-10-13 15:01:59 -07:00
Patrick Fic
804c8ad40a IO-256 Add cookie location 2021-10-13 14:49:47 -07:00
Patrick Fic
0ddf009f8f Reverse CORS & Fix Cookie Setting. 2021-10-13 14:35:39 -07:00
Patrick Fic
14309b5c96 QBO CORS Updates 2021-10-13 14:06:25 -07:00
Patrick Fic
57d9de469a Add CORS to Server. 2021-10-13 14:01:59 -07:00
Patrick Fic
404ade396c QB URL Updates. 2021-10-13 13:39:04 -07:00
Patrick Fic
bdad6da6d9 QB URL Fix. 2021-10-13 13:35:42 -07:00
Patrick Fic
e7ef3b94c1 QB Env update. 2021-10-13 13:34:56 -07:00
Patrick Fic
c19c92ab7e QBO port fix. 2021-10-13 13:25:15 -07:00
Patrick Fic
944229bae3 Update process env. 2021-10-13 13:22:56 -07:00
Patrick Fic
661b05d9e3 Merge branch 'feature/qbo' into test 2021-10-13 13:15:48 -07:00
Patrick Fic
4d1d471a66 Merge branch 'release/2021-10-15' into test 2021-10-13 13:15:35 -07:00
Patrick Fic
3e84fbbaf4 IO-1458 IO-1465 Sorting Fixes. 2021-10-13 11:52:11 -07:00
Patrick Fic
c4e59c1a5e IO-117 Begin PBS Structure. 2021-10-13 11:45:24 -07:00
Patrick Fic
9ee8e9007a IO-256 Add Payables Posting. 2021-10-12 20:18:06 -07:00
Patrick Fic
4d52a5c44a IO-256 Exporting of payments 2021-10-12 19:07:43 -07:00
Patrick Fic
fff9073f9d IO-256 Add vendor credits. 2021-10-12 16:54:28 -07:00
Patrick Fic
71f0b8a005 Merge branch 'release/2021-10-15' into test 2021-10-12 16:41:01 -07:00
Patrick Fic
d2b965f79e Resolve CI Issue. 2021-10-12 16:40:27 -07:00
Patrick Fic
a02aa71a95 Merged in release/2021-10-15 (pull request #248)
release/2021-10-15

Approved-by: Patrick Fic
2021-10-12 23:33:17 +00:00
Patrick Fic
7562bf5c95 IO-1418 Allow owner re-search on import. 2021-10-12 16:17:31 -07:00
Patrick Fic
f9521483e2 IO-1448 Disable negative deductible amounts. 2021-10-12 14:39:20 -07:00
Patrick Fic
ca85858885 IO-1453 Allow null scheduled completion dates on cehcklist view. 2021-10-12 14:27:22 -07:00
Patrick Fic
c7b3a94533 IO-1441 Conversion safety check. 2021-10-12 14:18:09 -07:00
Patrick Fic
b010c9ecb0 IO-1458 Production Board Sort ORder 2021-10-12 13:50:25 -07:00
Patrick Fic
0b98d04bac IO-1458 Production Board Sort Order 2021-10-12 13:49:32 -07:00
Patrick Fic
e25e388e59 Merged in release/2021-10-15 (pull request #247)
IO-233 Replace unknown CDK function.

Approved-by: Patrick Fic
2021-10-12 19:06:35 +00:00
Patrick Fic
10654b7916 IO-233 Replace unknown CDK function. 2021-10-12 12:04:50 -07:00
Patrick Fic
ff049ad3e8 Merged in release/2021-10-08 (pull request #246)
release/2021-10-08
2021-10-09 15:29:49 +00:00
Patrick Fic
3572fff2c1 Merged in release/2021-10-08 (pull request #245)
release/2021-10-08

Approved-by: Patrick Fic
2021-10-07 23:38:03 +00:00
Patrick Fic
851f1c265f Merged in release/2021-10-08 (pull request #244)
release/2021-10-08

Approved-by: Patrick Fic
2021-10-07 16:51:36 +00:00
Patrick Fic
c104ee4fd9 Merged in release/2021-10-08 (pull request #243)
release/2021-10-08

Approved-by: Patrick Fic
2021-10-06 20:37:34 +00:00
Patrick Fic
e550baf59d Merged in release/2021-10-08 (pull request #242)
release/2021-10-08

Approved-by: Patrick Fic
2021-10-05 23:20:08 +00:00
Patrick Fic
ca2ded047b Merged in release/2021-10-08 (pull request #241)
release/2021-10-08

Approved-by: Patrick Fic
2021-10-05 18:48:13 +00:00
Patrick Fic
fd43d7d56d Merged in release/2021-10-08 (pull request #237)
JSR CHange

Approved-by: Patrick Fic
2021-10-04 14:57:42 +00:00
Patrick Fic
e899e4545f Merged in release/2021-10-08 (pull request #236)
Release/2021 10 08
2021-10-02 06:19:45 +00:00
Patrick Fic
2e28a4a790 Merged in release/2021-10-01 (pull request #231)
Release/2021 10 01
2021-09-30 01:50:34 +00:00
Patrick Fic
891aa649e8 Merged in release/2021-09-24 (pull request #226)
release/2021-09-24
2021-09-24 17:44:53 +00:00
Patrick Fic
a8a0167123 Merged in release/2021-09-24 (pull request #224)
Release/2021 09 24
2021-09-24 17:20:52 +00:00
Patrick Fic
4b55719f86 Merged in hotfix-2021-09-14 (pull request #214)
hotfix-2021-09-14

Approved-by: Patrick Fic
2021-09-14 16:52:50 +00:00
Patrick Fic
5387ff207c Merged in release/2021-09-10 (pull request #212)
Release/2021 09 10
2021-09-13 19:02:26 +00:00
Patrick Fic
e3804b103b Merged in release/2021-09-10 (pull request #208)
Release/2021 09 10
2021-09-10 22:08:15 +00:00
198 changed files with 42209 additions and 2785 deletions

1
.gitignore vendored
View File

@@ -112,3 +112,4 @@ firebase/.env
.elasticbeanstalk/*
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml
logs/oAuthClient-log.log

File diff suppressed because it is too large Load Diff

View File

@@ -2,74 +2,75 @@
"name": "bodyshop",
"version": "0.1.1",
"private": true,
"proxy": "http://localhost:5000",
"proxy": "http://localhost:4000",
"dependencies": {
"@apollo/client": "^3.4.16",
"@craco/craco": "^6.3.0",
"@apollo/client": "^3.4.17",
"@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^6.4.0",
"@fingerprintjs/fingerprintjs": "^3.3.0",
"@lourenci/react-kanban": "^2.1.0",
"@openreplay/tracker": "^3.4.4",
"@openreplay/tracker-assist": "^3.4.3",
"@openreplay/tracker": "^3.4.7",
"@openreplay/tracker-assist": "^3.4.4",
"@openreplay/tracker-graphql": "^3.0.0",
"@openreplay/tracker-redux": "^3.0.0",
"@sentry/react": "^6.13.2",
"@sentry/tracing": "^6.13.2",
"@stripe/react-stripe-js": "^1.5.0",
"@stripe/stripe-js": "^1.19.1",
"@tanem/react-nprogress": "^3.0.79",
"@sentry/react": "^6.14.3",
"@sentry/tracing": "^6.14.3",
"@splitsoftware/splitio-react": "^1.3.0",
"@stripe/react-stripe-js": "^1.6.0",
"@stripe/stripe-js": "^1.21.1",
"@tanem/react-nprogress": "^3.0.82",
"antd": "^4.16.13",
"apollo-link-logger": "^2.0.0",
"axios": "^0.22.0",
"axios": "^0.24.0",
"craco-less": "^1.20.0",
"dinero.js": "^1.9.0",
"dinero.js": "^1.9.1",
"dotenv": "^10.0.0",
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"firebase": "^9.1.1",
"graphql": "^15.6.0",
"i18next": "^21.2.4",
"firebase": "^9.4.1",
"graphql": "^16.0.1",
"i18next": "^21.4.2",
"i18next-browser-languagedetector": "^6.1.2",
"jsoneditor": "^9.5.6",
"jsoneditor": "^9.5.7",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.9.36",
"logrocket": "^2.0.0",
"markerjs2": "^2.13.0",
"libphonenumber-js": "^1.9.42",
"logrocket": "^2.1.1",
"markerjs2": "^2.17.0",
"moment-business-days": "^1.2.0",
"phone": "^3.1.8",
"phone": "^3.1.9",
"preval.macro": "^5.0.0",
"prop-types": "^15.7.2",
"query-string": "^7.0.1",
"rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6",
"react": "^17.0.1",
"react-big-calendar": "^0.36.1",
"react-big-calendar": "^0.38.1",
"react-color": "^2.19.3",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.1",
"react-drag-listview": "^0.1.8",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.3.0",
"react-i18next": "^11.12.0",
"react-i18next": "^11.14.2",
"react-icons": "^4.3.1",
"react-number-format": "^4.7.3",
"react-redux": "^7.2.5",
"react-number-format": "^4.8.0",
"react-redux": "^7.2.6",
"react-resizable": "^3.0.4",
"react-router-dom": "^5.3.0",
"react-scripts": "^4.0.3",
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3",
"recharts": "^2.1.4",
"redux": "^4.1.1",
"recharts": "^2.1.6",
"redux": "^4.1.2",
"redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.2",
"reselect": "^4.0.0",
"sass": "^1.42.1",
"socket.io-client": "^4.2.0",
"styled-components": "^5.3.1",
"subscriptions-transport-ws": "^0.9.18",
"web-vitals": "^2.1.0",
"reselect": "^4.1.2",
"sass": "^1.43.4",
"socket.io-client": "^4.3.2",
"styled-components": "^5.3.3",
"subscriptions-transport-ws": "^0.11.0",
"web-vitals": "^2.1.2",
"workbox-background-sync": "^6.3.0",
"workbox-broadcast-update": "^6.3.0",
"workbox-cacheable-response": "^6.3.0",
@@ -114,7 +115,7 @@
]
},
"devDependencies": {
"@sentry/webpack-plugin": "^1.17.2",
"@sentry/webpack-plugin": "^1.18.3",
"patch-package": "^6.4.7",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.2"

View File

@@ -1,4 +1,8 @@
import { ApolloProvider } from "@apollo/client";
//import trackerRedux from "@openreplay/tracker-redux";
import Tracker from "@openreplay/tracker";
import trackerGraphQL from "@openreplay/tracker-graphql";
import { SplitFactory, SplitSdk } from "@splitsoftware/splitio-react";
import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US";
import LogRocket from "logrocket";
@@ -6,13 +10,11 @@ import moment from "moment";
import React from "react";
import { useTranslation } from "react-i18next";
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import client from "../utils/GraphQLClient";
import App from "./App";
import trackerGraphQL from "@openreplay/tracker-graphql";
//import trackerRedux from "@openreplay/tracker-redux";
import Tracker from "@openreplay/tracker";
//import trackerAssist from "@openreplay/tracker-assist";
import { getCurrentUser } from "../firebase/firebase.utils";
import client from "../utils/GraphQLClient";
import App from "./App";
moment.locale("en-US");
export const tracker = new Tracker({
@@ -25,7 +27,6 @@ export const tracker = new Tracker({
onStart: async ({ sessionID }) => {
const user = await getCurrentUser();
if (user) tracker.setUserID(user.email);
console.log("ORS SESSION ", sessionID, user && user.email);
},
});
@@ -36,6 +37,13 @@ export const recordGraphQL = tracker.use(trackerGraphQL());
tracker.start();
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
export const factory = SplitSdk({
core: {
authorizationKey: process.env.REACT_APP_SPLIT_API,
key: "anon",
},
});
export default function AppContainer() {
const { t } = useTranslation();
@@ -53,7 +61,9 @@ export default function AppContainer() {
}}
>
<GlobalLoadingBar />
<App />
<SplitFactory factory={factory}>
<App />
</SplitFactory>
</ConfigProvider>
</ApolloProvider>
);

View File

@@ -53,7 +53,6 @@ export function App({ checkUserSession, currentUser, online, setOnline }) {
const { t } = useTranslation();
window.addEventListener("offline", function (e) {
console.log("Internet connection lost.");
setOnline(false);
});

View File

@@ -128,3 +128,9 @@
.react-kanban-column {
background-color: #ddd !important;
}
.production-list-table {
td.ant-table-column-sort {
background: unset;
}
}

View File

@@ -8,8 +8,26 @@ import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
export default function AccountingPayablesTableComponent({
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(AccountingPayablesTableComponent);
export function AccountingPayablesTableComponent({
bodyshop,
loading,
payments,
}) {
@@ -163,6 +181,9 @@ export default function AccountingPayablesTableComponent({
loadingCallback={setTransInProgress}
completedCallback={setSelectedPayments}
/>
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
<QboAuthorizeComponent />
)}
<Input
value={state.search}
onChange={handleSearch}

View File

@@ -199,7 +199,7 @@ export function AccountingReceivablesTableComponent({
<Card
extra={
<Space wrap>
{!bodyshop.cdk_dealerid && (
{!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && (
<JobsExportAllButton
jobIds={selectedJobs}
disabled={transInProgress || selectedJobs.length === 0}

View File

@@ -216,7 +216,7 @@ function BillEnterModalContainer({
if (enterAgain) {
form.resetFields();
form.setFieldsValue(formValues);
form.setFieldsValue({ ...form.getFieldsValue(), billlines: [] });
} else {
toggleModalVisible();
}

View File

@@ -156,18 +156,7 @@ export function BillEnterModalLinesComponent({
setFieldsValue({
billlines: getFieldsValue("billlines").billlines.map(
(item, idx) => {
console.log("Checking", index, idx);
if (idx === index) {
console.log(
"Found and setting.",
!!item.actual_cost
? item.actual_cost
: Math.round(
(parseFloat(e.target.value) * (1 - discount) +
Number.EPSILON) *
100
) / 100
);
return {
...item,
actual_cost: !!item.actual_cost
@@ -502,9 +491,9 @@ const EditableCell = ({
labelCol={{ span: 0 }}
{...(formItemProps && formItemProps(record))}
>
{(formInput && formInput(record, record.key)) || children}
{(formInput && formInput(record, record.name)) || children}
</Form.Item>
{additional && additional(record, record.key)}
{additional && additional(record, record.name)}
</Space>
</td>
);
@@ -516,7 +505,7 @@ const EditableCell = ({
name={dataIndex}
{...(formItemProps && formItemProps(record))}
>
{(formInput && formInput(record, record.key)) || children}
{(formInput && formInput(record, record.name)) || children}
</Form.Item>
</td>
);

View File

@@ -14,7 +14,6 @@ const BillLineSearchSelect = ({ options, disabled, ...restProps }, ref) => {
showSearch
// optionFilterProp="line_desc"
filterOption={(inputValue, option) => {
console.log(inputValue);
return (
(option.line_desc &&
option.line_desc

View File

@@ -20,7 +20,6 @@ export function ChatNewConversation({ openChatByPhone }) {
const { t } = useTranslation();
const [form] = Form.useForm();
const handleFinish = (values) => {
console.log("values :>> ", values);
openChatByPhone({ phone_num: values.phoneNumber });
form.resetFields();
};

View File

@@ -1,5 +1,5 @@
import { ShrinkOutlined } from "@ant-design/icons";
import { Col, Row, Typography } from "antd";
import { ShrinkOutlined, InfoCircleOutlined } from "@ant-design/icons";
import { Col, Row, Tooltip, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -31,6 +31,9 @@ export function ChatPopupComponent({
{t("messaging.labels.messaging")}
</Typography.Title>
<ChatNewConversation />
<Tooltip title={t("messaging.labels.recentonly")}>
<InfoCircleOutlined />
</Tooltip>
</div>
<ShrinkOutlined
onClick={() => toggleChatVisible()}

View File

@@ -53,6 +53,7 @@ export function ContractConvertToRo({
const billingLines = [];
if (contractLength > 0)
billingLines.push({
manual_line:true,
unq_seq: 1,
line_no: 1,
line_ref: 1,
@@ -70,6 +71,7 @@ export function ContractConvertToRo({
contract.kmend - contract.kmstart - contract.dailyfreekm * contractLength;
if (mileageDiff > 0) {
billingLines.push({
manual_line:true,
unq_seq: 2,
line_no: 2,
line_ref: 2,
@@ -86,6 +88,7 @@ export function ContractConvertToRo({
if (values.refuelqty > 0) {
billingLines.push({
manual_line:true,
unq_seq: 3,
line_no: 3,
line_ref: 3,
@@ -101,6 +104,7 @@ export function ContractConvertToRo({
}
if (values.applyCleanupCharge) {
billingLines.push({
manual_line:true,
unq_seq: 4,
line_no: 4,
line_ref: 4,
@@ -117,6 +121,7 @@ export function ContractConvertToRo({
if (contract.damagewaiver) {
//Add for cleanup fee.
billingLines.push({
manual_line:true,
unq_seq: 5,
line_no: 5,
line_ref: 5,

View File

@@ -42,7 +42,6 @@ export default function ContractFormComponent({
<ContractStatusSelector />
</Form.Item>
)}
<Form.Item
label={t("contracts.fields.start")}
name="start"

View File

@@ -86,10 +86,7 @@ export function DashboardGridComponent({ currentUser, bodyshop }) {
const handleRemoveComponent = (key) => {
logImEXEvent("dashboard_remove_component", { name: key });
const idxToRemove = state.items.findIndex((i) => i.i === key);
console.log(
"🚀 ~ file: dashboard-grid.component.jsx ~ line 81 ~ idxToRemove",
idxToRemove
);
const items = _.cloneDeep(state.items);
items.splice(idxToRemove, 1);

View File

@@ -1,10 +1,10 @@
import { Button } from "antd";
import axios from "axios";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
@@ -19,11 +19,11 @@ export function DmsCdkMakesRefetch({ bodyshop, form, socket }) {
const { t } = useTranslation();
const handleRefetch = async () => {
setLoading(true);
const response = await axios.post("/cdk/getvehicles", {
await axios.post("/cdk/getvehicles", {
cdk_dealerid: bodyshop.cdk_dealerid,
bodyshopid: bodyshop.id,
});
console.log(response);
setLoading(false);
};
return (

View File

@@ -1,4 +1,4 @@
import { Button, Table, Col , Checkbox} from "antd";
import { Button, Table, Col, Checkbox } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -23,15 +23,26 @@ export function DmsCustomerSelector({ bodyshop }) {
const [customerList, setcustomerList] = useState([]);
const [visible, setVisible] = useState(false);
const [selectedCustomer, setSelectedCustomer] = useState(null);
const [dmsType, setDmsType] = useState("cdk");
socket.on("cdk-select-customer", (customerList, callback) => {
setVisible(true);
setDmsType("cdk");
setcustomerList(customerList);
});
socket.on("pbs-select-customer", (customerList, callback) => {
setVisible(true);
setDmsType("pbs");
setcustomerList(customerList);
console.log(
"🚀 ~ file: dms-customer-selector.component.jsx ~ line 37 ~ socket.on ~ customerList",
customerList
);
});
const onUseSelected = () => {
setVisible(false);
socket.emit("cdk-selected-customer", selectedCustomer);
socket.emit(`${dmsType}-selected-customer`, selectedCustomer);
setSelectedCustomer(null);
};
@@ -50,7 +61,7 @@ export function DmsCustomerSelector({ bodyshop }) {
setSelectedCustomer(null);
};
const columns = [
const cdkColumns = [
{
title: t("jobs.fields.dms.id"),
dataIndex: ["id", "value"],
@@ -60,13 +71,14 @@ export function DmsCustomerSelector({ bodyshop }) {
title: t("jobs.fields.dms.vinowner"),
dataIndex: "vinOwner",
key: "vinOwner",
render: (text, record) => <Checkbox disabled checked={record.vinOwner}/>
render: (text, record) => <Checkbox disabled checked={record.vinOwner} />,
},
{
title: t("jobs.fields.dms.name1"),
dataIndex: ["name1", "fullName"],
key: "name1",
sorter: (a, b) => alphaSort(a.name1?.fullName, b.name1?.fullName),
sorter: (a, b) =>
alphaSort(a.name1 && a.name1.fullName, b.name1 && b.name1.fullName),
},
{
@@ -74,11 +86,43 @@ export function DmsCustomerSelector({ bodyshop }) {
//dataIndex: ["name2", "fullName"],
key: "address",
render: (record, value) =>
`${record?.address?.addressLine[0]}, ${record.address?.city} ${record.address?.stateOrProvince} ${record.address?.postalCode}`,
`${record.address && record.address.addressLine[0]}, ${
record.address && record.address.city
} ${record.address && record.address.stateOrProvince} ${
record.address && record.address.postalCode
}`,
},
];
if (!visible) return <></>;
const pbsColumns = [
{
title: t("jobs.fields.dms.id"),
dataIndex: "ContactId",
key: "ContactId",
},
{
title: t("jobs.fields.dms.vinowner"),
dataIndex: "vinOwner",
key: "vinOwner",
render: (text, record) => <Checkbox disabled checked={record.vinOwner} />,
},
{
title: t("jobs.fields.dms.name1"),
key: "name1",
sorter: (a, b) => alphaSort(a.LastName, b.LastName),
render: (text, record) =>
`${record.FirstName || ""} ${record.LastName || ""}`,
},
{
title: t("jobs.fields.dms.address"),
key: "address",
render: (record, value) =>
`${record.Address}, ${record.City} ${record.State} ${record.ZipCode}`,
},
];
if (!visible) return null;
return (
<Col span={24}>
<Table
@@ -104,13 +148,17 @@ export function DmsCustomerSelector({ bodyshop }) {
</div>
)}
pagination={{ position: "top" }}
columns={columns}
rowKey={(record) => record.id.value}
columns={dmsType === "cdk" ? cdkColumns : pbsColumns}
rowKey={(record) =>
dmsType === "cdk" ? record.id.value : record.ContactId
}
dataSource={customerList}
//onChange={handleTableChange}
rowSelection={{
onSelect: (props) => {
setSelectedCustomer(props.id.value);
onSelect: (record) => {
setSelectedCustomer(
dmsType === "cdk" ? record.id.value : record.ContactId
);
},
type: "radio",
selectedRowKeys: [selectedCustomer],

View File

@@ -27,7 +27,7 @@ export function DmsLogEvents({ socket, logs, bodyshop }) {
<Timeline.Item key={idx} color={LogLevelHierarchy(log.level)}>
<Space wrap align="start" style={{}}>
<Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag>
<span>{moment(log.timestamp).format("MM/DD/YYYY HH:MM:ss")}</span>
<span>{moment(log.timestamp).format("MM/DD/YYYY HH:mm:ss")}</span>
<Divider type="vertical" />
<span>{log.message}</span>
</Space>

View File

@@ -119,33 +119,35 @@ export function DmsPostForm({ bodyshop, socket, job }) {
</Form.Item>
</LayoutFormRow>
<LayoutFormRow style={{ justifyContent: "center" }} grow>
<Form.Item
name="dms_make"
label={t("jobs.fields.dms.dms_make")}
rules={[
{
required: true,
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item
name="dms_model"
label={t("jobs.fields.dms.dms_model")}
rules={[
{
required: true,
},
]}
>
<Input disabled />
</Form.Item>
{bodyshop.cdk_dealerid && (
<LayoutFormRow style={{ justifyContent: "center" }} grow>
<Form.Item
name="dms_make"
label={t("jobs.fields.dms.dms_make")}
rules={[
{
required: true,
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item
name="dms_model"
label={t("jobs.fields.dms.dms_model")}
rules={[
{
required: true,
},
]}
>
<Input disabled />
</Form.Item>
<DmsCdkMakes form={form} socket={socket} job={job} />
<DmsCdkMakesRefetch />
</LayoutFormRow>
<DmsCdkMakes form={form} socket={socket} job={job} />
<DmsCdkMakesRefetch />
</LayoutFormRow>
)}
<Form.Item
name="story"
label={t("jobs.fields.dms.story")}
@@ -157,6 +159,7 @@ export function DmsPostForm({ bodyshop, socket, job }) {
>
<Input.TextArea maxLength={240} />
</Form.Item>
<Divider />
<Form.List name={["payers"]}>
{(fields, { add, remove }) => {
@@ -259,7 +262,6 @@ export function DmsPostForm({ bodyshop, socket, job }) {
))}
<Form.Item>
<Button
type="dashed"
disabled={!(fields.length < 3)}
onClick={() => {
if (fields.length < 3) add();

View File

@@ -58,11 +58,9 @@ export function DocumentEditorComponent({ currentUser, bodyshop, document }) {
if (imgRef.current !== null) {
// create a marker.js MarkerArea
markerArea.current = new markerjs2.MarkerArea(imgRef.current);
console.log(`markerArea.current`, markerArea.current);
// attach an event handler to assign annotated image back to our image element
markerArea.current.addCloseEventListener((closeEvent) => {
console.log("Close Event", closeEvent);
});
markerArea.current.addCloseEventListener((closeEvent) => {});
markerArea.current.addRenderEventListener((dataUrl) => {
imgRef.current.src = dataUrl;

View File

@@ -14,8 +14,6 @@ var cleanAxios = axios.create();
cleanAxios.interceptors.request.eject(axiosAuthInterceptorId);
export const handleUpload = (ev, context) => {
console.log("Handling Upload", ev);
logImEXEvent("document_upload", { filetype: ev.file.type });
const { onError, onSuccess, onProgress } = ev;
@@ -61,7 +59,7 @@ export const uploadToCloudinary = async (
// let eager = process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS;
//Get the signed url.
console.log("fileType", fileType);
const upload_preset = fileType.startsWith("video")
? "incoming_upload_video"
: "incoming_upload";
@@ -74,7 +72,6 @@ export const uploadToCloudinary = async (
});
if (signedURLResponse.status !== 200) {
console.log("Error Getting Signed URL", signedURLResponse.statusText);
if (!!onError) onError(signedURLResponse.statusText);
notification["error"]({
message: i18n.t("documents.errors.getpresignurl", {
@@ -113,13 +110,8 @@ export const uploadToCloudinary = async (
...options,
}
);
console.log("Upload Response", cloudinaryUploadResponse.data);
if (cloudinaryUploadResponse.status !== 200) {
console.log(
"Error uploading to cloudinary.",
cloudinaryUploadResponse.statusText
);
if (!!onError) onError(cloudinaryUploadResponse.statusText);
notification["error"]({
message: i18n.t("documents.errors.insert", {

View File

@@ -35,10 +35,6 @@ export function EmailDocumentsComponent({
},
skip: !emailConfig.jobid,
});
console.log(
"🚀 ~ file: email-documents.component.jsx ~ line 38 ~ emailConfig",
emailConfig
);
return (
<div>

View File

@@ -1,15 +1,71 @@
import { UploadOutlined } from "@ant-design/icons";
import { Divider, Form, Input, Select, Tabs, Upload } from "antd";
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
import {
Divider,
Form,
Input,
Select,
Tabs,
Upload,
Space,
Menu,
Dropdown,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import EmailDocumentsComponent from "../email-documents/email-documents.component";
import _ from "lodash";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(EmailOverlayComponent);
export default function EmailOverlayComponent({ form, selectedMediaState }) {
export function EmailOverlayComponent({ form, selectedMediaState, bodyshop }) {
const { t } = useTranslation();
const handleClick = ({ item, key, keyPath }) => {
const email = item.props.value;
form.setFieldsValue({ to: _.uniq([...form.getFieldValue("to"), email]) });
};
const menu = (
<div>
<Menu onClick={handleClick}>
{bodyshop.employees
.filter((e) => e.user_email)
.map((e, idx) => (
<Menu.Item value={e.user_email} key={idx}>
{`${e.first_name} ${e.last_name}`}
</Menu.Item>
))}
</Menu>
</div>
);
return (
<div>
<Form.Item
label={t("emails.fields.to")}
label={
<Space>
{t("emails.fields.to")}
<Dropdown overlay={menu}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<UserAddOutlined />
</a>
</Dropdown>
</Space>
}
name="to"
rules={[
{

View File

@@ -51,7 +51,9 @@ export function EmailOverlayContainer({
const defaultEmailFrom = {
from: {
name: `${currentUser.displayName} @ ${bodyshop.shopname}`,
name: currentUser.displayName
? `${currentUser.displayName} @ ${bodyshop.shopname}`
: bodyshop.shopname,
address: EmailSettings.fromAddress,
},
ReplyTo: {
@@ -109,7 +111,6 @@ export function EmailOverlayContainer({
notification["success"]({ message: t("emails.successes.sent") });
toggleEmailOverlayVisible();
} catch (error) {
console.log(JSON.stringify(error));
notification["error"]({
message: t("emails.errors.notsent", { message: error.message }),
});
@@ -147,6 +148,18 @@ export function EmailOverlayContainer({
html: response.data,
fileList: [],
});
if (
bodyshop.md_email_cc[emailConfig.template.name] &&
bodyshop.md_email_cc[emailConfig.template.name].length > 0
) {
form.setFieldsValue({
cc: [
...(form.getFieldValue("cc") || []),
...bodyshop.md_email_cc[emailConfig.template.name],
],
});
}
setLoading(false);
};

View File

@@ -7,10 +7,12 @@ import { selectCurrentUser } from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import moment from "moment";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
});
@@ -19,14 +21,24 @@ export function EmailTestComponent({ currentUser, setEmailOptions }) {
const [form] = Form.useForm();
const handleFinish = (values) => {
console.log("values", values);
GenerateDocument(
{
name: values.key,
variables: {
...(values.start
? {
start: moment(values.start).startOf("day").format("YYYY-MM-DD"),
}
: {}),
...(values.end
? { end: moment(values.end).endOf("day").format("YYYY-MM-DD") }
: {}),
...(values.start
? { starttz: moment(values.start).startOf("day") }
: {}),
...(values.end ? { endtz: moment(values.end).endOf("day") } : {}),
...(values.id ? { id: values.id } : {}),
...(values.start ? { start: values.start } : {}),
...(values.end ? { end: values.end } : {}),
},
},
{

View File

@@ -21,7 +21,7 @@ export const PhoneItemFormatterValidation = (getFieldValue, name) => ({
return Promise.resolve();
} else {
const p = parsePhoneNumber(value, "CA");
if (p.isValid()) {
if (p && p.isValid()) {
return Promise.resolve();
} else {
return Promise.reject(i18n.t("general.validation.invalidphone"));

View File

@@ -46,12 +46,16 @@ import {
} from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { signOutStart } from "../../redux/user/user.actions";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
recentItems: selectRecentItems,
selectedHeader: selectSelectedHeader,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
@@ -69,6 +73,7 @@ const mapDispatchToProps = (dispatch) => ({
function Header({
handleMenuClick,
currentUser,
bodyshop,
selectedHeader,
signOutStart,
setBillEnterContext,
@@ -237,16 +242,26 @@ function Header({
{t("menus.header.accounting-receivables")}
</Link>
</Menu.Item>
<Menu.Item key="payables">
<Link to="/manage/accounting/payables">
{t("menus.header.accounting-payables")}
</Link>
</Menu.Item>
<Menu.Item key="payments">
<Link to="/manage/accounting/payments">
{t("menus.header.accounting-payments")}
</Link>
</Menu.Item>
{!(
(bodyshop && bodyshop.cdk_dealerid) ||
(bodyshop && bodyshop.pbs_serialnumber)
) && (
<Menu.Item key="payables">
<Link to="/manage/accounting/payables">
{t("menus.header.accounting-payables")}
</Link>
</Menu.Item>
)}
{!(
(bodyshop && bodyshop.cdk_dealerid) ||
(bodyshop && bodyshop.pbs_serialnumber)
) && (
<Menu.Item key="payments">
<Link to="/manage/accounting/payments">
{t("menus.header.accounting-payments")}
</Link>
</Menu.Item>
)}
<Menu.Item key="export-logs">
<Link to="/manage/accounting/exportlogs">
{t("menus.header.export-logs")}

View File

@@ -10,15 +10,11 @@ export default function HelpRescue() {
var bodyFormData = new FormData();
bodyFormData.append("Code", code);
bodyFormData.append("hostederrorhandling", 1);
const res1 = await fetch(
"https://secure.logmeinrescue.com/Customer/Code.aspx",
{
mode: "no-cors",
method: "POST",
body: bodyFormData,
}
);
console.log("handleClick -> res1", await res1.text());
await fetch("https://secure.logmeinrescue.com/Customer/Code.aspx", {
mode: "no-cors",
method: "POST",
body: bodyFormData,
});
};
return (
@@ -40,7 +36,6 @@ export default function HelpRescue() {
method="post"
id="logmeinsupport"
onSubmit={(...props) => {
console.log(`props`, props);
alert();
}}
>

View File

@@ -44,7 +44,7 @@ export function Jobd3RdPartyModal({ bodyshop, jobId }) {
};
const handleFinish = (values) => {
const { sendtype, ...restVals } = values;
console.log(restVals);
GenerateDocument(
{
name: TemplateList("job_special").thirdpartypayer.key,

View File

@@ -56,7 +56,7 @@ export function ScheduleEventColor({ bodyshop, event }) {
<Menu.Item key={"null"}>{t("general.actions.clear")}</Menu.Item>
</Menu>
);
console.log(`event`, event);
return (
<Dropdown overlay={menu}>
<a href=" #" onClick={(e) => e.preventDefault()}>

View File

@@ -176,7 +176,7 @@ export function ScheduleEventComponent({
t("appointments.labels.reminder", {
shopname: bodyshop.shopname,
date: moment(event.start).format("MM/DD/YYYY"),
time: moment(event.start).format("HH:MM a"),
time: moment(event.start).format("HH:mm a"),
})
);
setVisible(false);
@@ -209,6 +209,9 @@ export function ScheduleEventComponent({
jobId: event.job.id,
job: event.job,
previousEvent: event.id,
color: event.color,
alt_transport: event.job && event.job.alt_transport,
note: event.note,
},
});
}}

View File

@@ -55,7 +55,6 @@ export default function JobBillsTotalComponent({
);
}
if (il.deductedfromlbr) {
console.log(i, "Deducting from labor.");
lbrAdjustments = lbrAdjustments.add(
Dinero({
amount: Math.round((il.actual_price || 0) * 100),

View File

@@ -186,12 +186,14 @@ export function JobChecklistForm({
allow_text_message: job.owner && job.owner.allow_text_message,
scheduled_completion:
(job && job.scheduled_completion) ||
moment().businessAdd(
(job.labhrs.aggregate.sum.mod_lb_hrs +
job.larhrs.aggregate.sum.mod_lb_hrs) /
bodyshop.target_touchtime,
"days"
),
(job.labbrs && job.larhrs
? moment().businessAdd(
(job.labhrs.aggregate.sum.mod_lb_hrs +
job.larhrs.aggregate.sum.mod_lb_hrs) /
bodyshop.target_touchtime,
"days"
)
: null),
scheduled_delivery: job && job.scheduled_delivery,
}),
...(type === "deliver" && {

View File

@@ -34,7 +34,7 @@ export default function JobIntakeTemplateList({ templates }) {
const renderAllTemplates = async () => {
logImEXEvent("checklist_render_all_templates");
setLoading(true);
console.log("templates :>> ", templates);
await GenerateDocuments(
templates.map((key) => {
return { name: key, variables: { id: jobId } };

View File

@@ -35,7 +35,7 @@ export function JobCostingModalContainer({
async function getData() {
if (jobId && visible) {
const { data } = await axios.post("/job/costing", { jobid: jobId });
console.log(data);
setCostingData(data);
}
}

View File

@@ -77,7 +77,6 @@ export default function JobCostingPartsTable({ data, summaryData }) {
.includes(searchText.toLowerCase())
);
console.log("data :>> ", data);
return (
<div>
<Table

View File

@@ -162,7 +162,11 @@ export function JobLinesComponent({
ellipsis: true,
render: (text, record) => (
<>
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
<CurrencyFormatter>
{record.db_ref === "900510" || record.db_ref === "900511"
? record.prt_dsmk_m
: record.act_price}
</CurrencyFormatter>
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
<span
style={{ marginLeft: ".2rem" }}

View File

@@ -73,7 +73,7 @@ export const reconcileByPrice = (
jobLines.forEach((jl) => {
const matchingBillLineIds = billLines
.filter((bl) => bl.actual_price === jl.act_price && !jl.removed)
.filter((bl) => bl.actual_price === jl.act_price && bl.quantity === jl.part_qty && !jl.removed)
.map((bl) => bl.id);
if (matchingBillLineIds.length > 1) {

View File

@@ -42,9 +42,7 @@ export default function ScoreboardAddButton({
}, [visibility, job.id, callQuery]);
useEffect(() => {
console.log("UE", entryData);
if (entryData && entryData.scoreboard && entryData.scoreboard[0]) {
console.log("Setting FOrm");
form.setFieldsValue(entryData.scoreboard[0]);
}
}, [entryData, form]);

View File

@@ -22,7 +22,6 @@ export function JobsAdminClass({ bodyshop, job }) {
const [updateJob] = useMutation(UPDATE_JOB);
const handleFinish = async (values) => {
console.log(values);
setLoading(true);
const result = await updateJob({
variables: { jobId: job.id, job: values },

View File

@@ -13,7 +13,6 @@ export default function JobsAdminDatesChange({ job }) {
const [updateJob] = useMutation(UPDATE_JOB);
const handleFinish = async (values) => {
console.log(values);
setLoading(true);
const result = await updateJob({
variables: { jobId: job.id, job: values },

View File

@@ -10,7 +10,6 @@ export default function JobAdminOwnerReassociate({ job }) {
const [form] = Form.useForm();
const [updateJob] = useMutation(UPDATE_JOB);
const handleFinish = async (values) => {
console.log(values);
setLoading(true);
const result = await updateJob({
variables: { jobId: job.id, job: { ownerid: values.ownerid } },

View File

@@ -10,7 +10,6 @@ export default function JobAdminOwnerReassociate({ job }) {
const [form] = Form.useForm();
const [updateJob] = useMutation(UPDATE_JOB);
const handleFinish = async (values) => {
console.log(values);
setLoading(true);
const result = await updateJob({
variables: { jobId: job.id, job: { vehicleid: values.vehicleid } },

View File

@@ -3,8 +3,6 @@ import { gql } from "@apollo/client";
import _ from "lodash";
export const GetSupplementDelta = async (client, jobId, newLines) => {
console.log("-----Begin Supplement-----");
const {
data: { joblines: existingLinesFromDb },
} = await client.query({

View File

@@ -498,6 +498,12 @@ async function CheckTaxRates(estData, bodyshop) {
) {
estData.joblines.data[index].tax_part = jl.lbr_tax;
}
//Set markup lines and tax lines as taxable.
//900510 is a mark up. 900510 is a discount.
if (jl.db_ref === "900510") {
estData.joblines.data[index].tax_part = true;
}
});
//}
}

View File

@@ -61,7 +61,7 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
);
};
const overlay = bodyshop.cdk_dealerid && (
const overlay = (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
<Menu onClick={handleMenuClick}>
{bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => (
<Menu.Item key={mapping.name}>{mapping.name}</Menu.Item>
@@ -69,7 +69,7 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
</Menu>
);
return bodyshop.cdk_dealerid ? (
return bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber ? (
<Dropdown overlay={overlay}>
<Button disabled={disabled}>{t("jobs.actions.dmsautoallocate")}</Button>
</Dropdown>

View File

@@ -15,6 +15,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { useHistory } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
@@ -35,7 +36,7 @@ export function JobsCloseExportButton({
const handleQbxml = async () => {
//Check if it's a CDK setup.
if (bodyshop.cdk_dealerid) {
if (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) {
history.push(`/manage/dms?jobId=${jobId}`);
return;
}
@@ -45,10 +46,13 @@ export function JobsCloseExportButton({
//Check if it's a QBO Setup.
let PartnerResponse;
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/receivables`, {
withCredentials: true,
jobIds: [jobId],
});
PartnerResponse = await axios.post(
`/qbo/receivables`,
{
jobIds: [jobId],
},
);
} else {
//Default is QBD

View File

@@ -31,6 +31,7 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
<th>{t("joblines.fields.line_desc")}</th>
<th>{t("joblines.fields.part_type")}</th>
<th>{t("joblines.fields.act_price")}</th>
<th>{t("joblines.fields.prt_dsmk_m")}</th>
<th>{t("joblines.fields.op_code_desc")}</th>
<th>{t("joblines.fields.mod_lbr_ty")}</th>
<th>{t("joblines.fields.mod_lb_hrs")}</th>
@@ -70,6 +71,16 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
<ReadOnlyFormItem type="currency" />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
// label={t("joblines.fields.prt_dsmk_m")}
key={`${index}prt_dsmk_m`}
name={[field.name, "prt_dsmk_m"]}
>
<ReadOnlyFormItem type="currency" />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
@@ -108,7 +119,9 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
labelCol={{ span: 0 }}
rules={[
{
required: !!job.joblines[index].act_price,
required:
!!job.joblines[index].act_price ||
!!job.joblines[index].prt_dsmk_m,
//message: t("general.validation.required"),
},
]}

View File

@@ -35,6 +35,7 @@ export function JobsConvertButton({
refetch,
jobRO,
insertAuditTrail,
parentFormIsFieldsTouched,
}) {
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
@@ -43,6 +44,10 @@ export function JobsConvertButton({
const [form] = Form.useForm();
const handleConvert = async (values) => {
if (parentFormIsFieldsTouched()) {
alert(t("jobs.labels.savebeforeconversion"));
return;
}
setLoading(true);
const res = await mutationConvertJob({
variables: { jobId: job.id, ...values },

View File

@@ -150,6 +150,9 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
<Form.Item label={t("jobs.fields.loss_desc")} name="loss_desc">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.ponumber")} name="po_number">
<Input />
</Form.Item>
@@ -197,7 +200,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
<JobsMarkPstExempt form={form} />
<LayoutFormRow>
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
<CurrencyInput />
<CurrencyInput min={0} />
</Form.Item>
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
<Select>

View File

@@ -0,0 +1,43 @@
import { DownOutlined } from "@ant-design/icons";
import { Dropdown, Menu } from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function JobsDetailChangeEstimator({ disabled, form, bodyshop }) {
const handleClick = ({ item, key, keyPath }) => {
const est = item.props.value;
form.setFieldsValue(est);
};
const menu = (
<div>
<Menu onClick={handleClick}>
{bodyshop.md_estimators.map((est, idx) => (
<Menu.Item value={est} key={idx}>
{`${est.est_ct_fn} ${est.est_ct_ln}`}
</Menu.Item>
))}
</Menu>
</div>
);
return (
<Dropdown overlay={menu} disabled={disabled}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<DownOutlined />
</a>
</Dropdown>
);
}
export default connect(mapStateToProps, null)(JobsDetailChangeEstimator);

View File

@@ -0,0 +1,43 @@
import { DownOutlined } from "@ant-design/icons";
import { Dropdown, Menu } from "antd";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
export function JobsDetailChangeFilehandler({ disabled, form, bodyshop }) {
const handleClick = ({ item, key, keyPath }) => {
const est = item.props.value;
form.setFieldsValue(est);
};
const menu = (
<div>
<Menu onClick={handleClick}>
{bodyshop.md_filehandlers.map((est, idx) => (
<Menu.Item value={est} key={idx}>
{`${est.ins_ct_fn} ${est.ins_ct_ln}`}
</Menu.Item>
))}
</Menu>
</div>
);
return (
<Dropdown overlay={menu} disabled={disabled}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<DownOutlined />
</a>
</Dropdown>
);
}
export default connect(mapStateToProps, null)(JobsDetailChangeFilehandler);

View File

@@ -75,6 +75,12 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
>
<DateTimePicker />
</Form.Item>
<Form.Item
label={t("jobs.fields.date_next_contact")}
name="date_next_contact"
>
<DateTimePicker />
</Form.Item>
<Form.Item
label={t("jobs.fields.scheduled_completion")}
name="scheduled_completion"

View File

@@ -1,4 +1,14 @@
import { Col, Form, Input, InputNumber, Row, Select, Switch } from "antd";
import {
Col,
Divider,
Form,
Input,
InputNumber,
Row,
Select,
Space,
Switch,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -12,8 +22,9 @@ import FormItemPhone, {
PhoneItemFormatterValidation,
} from "../form-items-formatted/phone-form-item.component";
import Car from "../job-damage-visual/job-damage-visual.component";
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
import FormRow from "../layout-form-row/layout-form-row.component";
import JobsDetailChangeFileHandler from "../jobs-detail-change-filehandler/jobs-detail-change-filehandler.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
@@ -44,7 +55,14 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
</Select>
</Form.Item>
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
<CurrencyInput disabled={jobRO} />
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<Form.Item label={t("jobs.fields.ded_note")} name="ded_note">
<Select disabled={jobRO}>
{bodyshop.md_ded_notes.map((n, index) => (
<Select.Option key={index}>{n}</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
<Input disabled={jobRO} />
@@ -68,7 +86,15 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
<Form.Item label={t("jobs.fields.ins_city")} name="ins_city">
<Input disabled={jobRO} />
</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} disabled={jobRO} />
</Space>
}
name="ins_ct_ln"
>
<Input disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.ins_ct_fn")} name="ins_ct_fn">
@@ -118,7 +144,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
label={t("jobs.fields.referral_source_extra")}
name="referral_source_extra"
>
<Input />
<Input disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.alt_transport")} name="alt_transport">
<Select disabled={jobRO} allowClear>
@@ -139,6 +165,9 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
<FormDatePicker disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
<Input disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
<InputNumber precision={0} min={0} disabled={jobRO} />
</Form.Item>
@@ -191,12 +220,27 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
)}
</Col>
</Row>
<Divider
orientation="left"
type="horizontal"
style={{ marginTop: ".8rem", float: "right" }}
>
{t("jobs.forms.appraiserinfo")}
</Divider>
<FormRow header={t("jobs.forms.appraiserinfo")}>
<FormRow noDivider>
<Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm">
<Input disabled={jobRO} />
</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} disabled={jobRO} />
</Space>
}
name="est_ct_fn"
>
<Input disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.est_ct_ln")} name="est_ct_ln">

View File

@@ -101,6 +101,7 @@ export function JobsDetailHeaderActions({
context: {
jobId: job.id,
job: job,
alt_transport: job.alt_transport,
},
});
}}

View File

@@ -147,7 +147,7 @@ export function JobsDetailHeaderCsi({
replyTo: bodyshop.email,
},
template: {
name: TemplateList("job").csi_invitation.key,
name: TemplateList("job_special").csi_invitation_action.key,
variables: {
id: job.csiinvites[0].id,
},

View File

@@ -19,7 +19,6 @@ export default async function DuplicateJob(
variables: { id: jobId },
});
console.log("res", res);
const { jobs_by_pk } = res.data;
const existingJob = _.cloneDeep(jobs_by_pk);
delete existingJob.__typename;

View File

@@ -21,47 +21,57 @@ export function JobsDetailHeaderActionexportCustomerData({
const handleExportCustData = async (e) => {
logImEXEvent("job_export_cust_data");
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/receivables",
{ jobIds: [job.id], custDataOnly: true },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("jobs.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
return;
}
let PartnerResponse;
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
QbXmlResponse.data,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("jobs.errors.exporting-partner"),
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/receivables`, {
jobIds: [job.id],
custDataOnly: true,
});
} else {
//Default is QBD
return;
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/receivables",
{ jobIds: [job.id], custDataOnly: true },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("jobs.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
return;
}
//let PartnerResponse;
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
QbXmlResponse.data,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("jobs.errors.exporting-partner"),
});
return;
}
}
//Check to see if any of them failed. If they didn't don't execute the update.
const failedTransactions = PartnerResponse.data.filter((r) => !r.success);

View File

@@ -16,6 +16,7 @@ import JobEmployeeAssignments from "../job-employee-assignments/job-employee-ass
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import "./jobs-detail-header.styles.scss";
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
import { DateTimeFormatter } from "../../utils/DateFormatter";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -70,6 +71,12 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
{job.production_vars && job.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}
{job.status === bodyshop.md_ro_statuses.default_scheduled &&
job.scheduled_in ? (
<Tag>
<DateTimeFormatter>{job.scheduled_in}</DateTimeFormatter>
</Tag>
) : null}
</Space>
</DataLabel>
<DataLabel label={t("jobs.fields.ins_co_nm_short")}>

View File

@@ -124,7 +124,6 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
{t("jobs.forms.laborrates")}
</Divider>
<Space>
<div></div>
<JobsDetailRatesChangeButton form={form} disabled={jobRO} />
<JobsMarkPstExempt form={form} />
</Space>

View File

@@ -40,7 +40,6 @@ export function JobsDocumentsGalleryReassign({ bodyshop, galleryImages }) {
const updateImage = async (i, jobid) => {
//Move the cloudinary image
console.log(i);
//Update it in the database.
const result = await updateDocument({

View File

@@ -38,7 +38,6 @@ export function JobsExportAllButton({
setLoading(true);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/receivables`, {
withCredentials: true,
jobIds: jobIds,
});
} else {
@@ -170,12 +169,7 @@ export function JobsExportAllButton({
};
return (
<Button
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type="dashed"
>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")}
</Button>
);

View File

@@ -3,17 +3,17 @@ import { notification } from "antd";
import React, { useState } from "react";
//import SpinComponent from "../../components/loading-spinner/loading-spinner.component";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import AlertComponent from "../../components/alert/alert.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {
DELETE_NOTE,
QUERY_NOTES_BY_JOB_PK,
} from "../../graphql/notes.queries";
import JobNotesComponent from "./jobs.notes.component";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import JobNotesComponent from "./jobs.notes.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -63,6 +63,7 @@ export function JobNotesContainer({ jobId, insertAuditTrail }) {
refetch={refetch}
deleteLoading={deleteLoading}
handleNoteDelete={handleNoteDelete}
ro_number={data ? data.jobs_by_pk.ro_number : null}
/>
);
}

View File

@@ -13,7 +13,9 @@ import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import NoteUpsertModal from "../note-upsert-modal/note-upsert-modal.container";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -33,8 +35,12 @@ export function JobNotesComponent({
jobId,
setNoteUpsertContext,
deleteLoading,
ro_number,
}) {
const { t } = useTranslation();
const Templates = TemplateList("job_special", {
ro_number,
});
const columns = [
{
@@ -107,6 +113,18 @@ export function JobNotesComponent({
>
<EditFilled />
</Button>
<PrintWrapperComponent
emailOnly
templateObject={{
name: Templates.individual_job_note.key,
variables: { id: record.id },
}}
messageObject={{
subject: Templates.individual_job_note.subject,
}}
id={record.id}
/>
</Space>
),
},

View File

@@ -74,7 +74,6 @@ export default function OwnerFindModalComponent({
return (
<div>
<Table
title={() => t("owners.labels.existing_owners")}
pagination={{ position: "bottom" }}
columns={columns}
rowKey="id"

View File

@@ -1,6 +1,6 @@
import { Modal } from "antd";
import React from "react";
import { useQuery } from "@apollo/client";
import { useLazyQuery } from "@apollo/client";
import { Input, Modal } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { QUERY_SEARCH_OWNER_BY_IDX } from "../../graphql/owners.queries";
import AlertComponent from "../alert/alert.component";
@@ -17,14 +17,27 @@ export default function OwnerFindModalContainer({
}) {
//use owner object to run query and find what possible owners there are.
const { t } = useTranslation();
const [searchText, setSearchText] = useState(null);
const ownersList = useQuery(QUERY_SEARCH_OWNER_BY_IDX, {
variables: {
search: owner ? `${owner.ownr_fn || ""} ${owner.ownr_ln || ""}` : null,
},
skip: !owner,
fetchPolicy: "network-only",
});
const [callSearchowners, ownersList] = useLazyQuery(
QUERY_SEARCH_OWNER_BY_IDX,
{
variables: {
search: searchText,
},
}
);
useEffect(() => {
if (modalProps.visible && owner) {
const s = `${owner.ownr_fn || ""} ${owner.ownr_ln || ""} ${
owner.ownr_co_nm || ""
}`;
setSearchText(s.trim());
callSearchowners({ variables: { search: s.trim() } });
}
}, [callSearchowners, modalProps.visible, owner]);
return (
<Modal
@@ -35,16 +48,25 @@ export default function OwnerFindModalContainer({
{loading ? <LoadingSpinner /> : null}
{error ? <AlertComponent message={error.message} type="error" /> : null}
{owner ? (
<OwnerFindModalComponent
selectedOwner={selectedOwner}
setSelectedOwner={setSelectedOwner}
ownersListLoading={ownersList.loading}
ownersList={
ownersList.data && ownersList.data.search_owners
? ownersList.data.search_owners
: null
}
/>
<>
<Input.Search
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
onSearch={(val) =>
callSearchowners({ variables: { search: val.trim() } })
}
/>
<OwnerFindModalComponent
selectedOwner={selectedOwner}
setSelectedOwner={setSelectedOwner}
ownersListLoading={ownersList.loading}
ownersList={
ownersList.data && ownersList.data.search_owners
? ownersList.data.search_owners
: null
}
/>
</>
) : null}
</Modal>
);

View File

@@ -29,7 +29,7 @@ export function PartnerPingComponent({ setPartnerVersion }) {
//if (process.env.NODE_ENV === "development") return;
const PartnerResponse = await axios.post("http://localhost:1337/ping/");
const { appver, qbpath } = PartnerResponse.data;
console.log("SETTING PARTNER VERSION.");
setPartnerVersion(appver);
console.log({ appver, qbpath });
if (!qbpath) {

View File

@@ -119,7 +119,7 @@ export function PartsOrderListTableComponent({
</Button>
<Popconfirm
title={t("parts_orders.labels.confirmdelete")}
disabled={jobRO || !record.return}
disabled={jobRO}
onConfirm={async () => {
//Delete the parts return.!
@@ -139,7 +139,7 @@ export function PartsOrderListTableComponent({
});
}}
>
<Button disabled={jobRO || !record.return}>
<Button disabled={jobRO}>
<DeleteFilled />
</Button>
</Popconfirm>

View File

@@ -164,6 +164,7 @@ export default function PartsOrderModalComponent({
<Radio value={"none"}>{t("general.labels.none")}</Radio>
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
<Radio value={"p"}>{t("parts_orders.labels.print")}</Radio>
<Radio value={"oec"}>{t("parts_orders.labels.oec")}</Radio>
</Radio.Group>
</div>
);

View File

@@ -1,13 +1,16 @@
import { useMutation, useQuery } from "@apollo/client";
import { useMutation, useQuery, useApolloClient } from "@apollo/client";
import { Form, Modal, notification } from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { logImEXEvent, auth } from "../../firebase/firebase.utils";
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries";
import { INSERT_NEW_PARTS_ORDERS } from "../../graphql/parts-orders.queries";
import {
INSERT_NEW_PARTS_ORDERS,
QUERY_PARTS_ORDER_OEC,
} from "../../graphql/parts-orders.queries";
import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { setEmailOptions } from "../../redux/email/email.actions";
@@ -26,6 +29,7 @@ import { TemplateList } from "../../utils/TemplateConstants";
import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import PartsOrderModalComponent from "./parts-order-modal.component";
import axios from "axios";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -52,7 +56,7 @@ export function PartsOrderModalContainer({
insertAuditTrail,
}) {
const { t } = useTranslation();
const client = useApolloClient();
const { visible, context, actions } = partsOrderModal;
const {
jobId,
@@ -175,7 +179,7 @@ export function PartsOrderModalContainer({
}
if (refetch) refetch();
toggleModalVisible();
const Templates = TemplateList("partsorder", context);
if (sendType === "e") {
@@ -215,7 +219,50 @@ export function PartsOrderModalContainer({
{},
"p"
);
} else if (sendType === "oec") {
//Send to Partner OEC.
try {
const partsOrder = await client.query({
query: QUERY_PARTS_ORDER_OEC,
variables: {
id: insertResult.data.insert_parts_orders.returning[0].id,
},
});
const oecResponse = await axios.post(
"http://localhost:1337/oec/",
partsOrder.data.parts_orders_by_pk,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
if (oecResponse.data && oecResponse.data.success === false) {
notification.open({
type: "error",
message: t("parts_orders.errors.oec", {
error: oecResponse.data.error,
}),
});
}
} catch (error) {
console.log("Error OEC.", error);
notification["error"]({
message: t("parts_orders.errors.oec", {
error: JSON.stringify(error.message),
}),
});
return;
}
}
toggleModalVisible();
};
const initialValues = {

View File

@@ -60,12 +60,10 @@ export function PartsStatusPie({ bodyshop, joblines_status }) {
[pieColor, t]
);
const memoizedData = useMemo(() => Calculatedata(joblines_status), [
joblines_status,
Calculatedata,
]);
console.log("PartsStatusPie -> memoizedData", memoizedData);
const memoizedData = useMemo(
() => Calculatedata(joblines_status),
[joblines_status, Calculatedata]
);
return (
<div>

View File

@@ -40,8 +40,7 @@ export function PayableExportAll({
setLoading(true);
if (!!loadingCallback) loadingCallback(true);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/receivables`, {
withCredentials: true,
PartnerResponse = await axios.post(`/qbo/payables`, {
bills: billids,
});
} else {
@@ -169,12 +168,7 @@ export function PayableExportAll({
};
return (
<Button
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type="dashed"
>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.exportselected")}
</Button>
);

View File

@@ -42,7 +42,6 @@ export function PayableExportButton({
let PartnerResponse;
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/payables`, {
withCredentials: true,
bills: [billId],
});
} else {
@@ -171,12 +170,7 @@ export function PayableExportButton({
};
return (
<Button
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type="dashed"
>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")}
</Button>
);

View File

@@ -8,6 +8,7 @@ import { createStructuredSelector } from "reselect";
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_PAYMENTS } from "../../graphql/payments.queries";
import {
selectBodyshop,
selectCurrentUser,
@@ -33,55 +34,63 @@ export function PaymentExportButton({
const handleQbxml = async () => {
logImEXEvent("accounting_payment_export");
setLoading(true);
if (!!loadingCallback) loadingCallback(true);
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/payments",
{ payments: [paymentId] },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("payments.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
//Check if it's a QBO Setup.
let PartnerResponse;
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
//"http://609feaeae986.ngrok.io/qb/",
QbXmlResponse.data
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("payments.errors.exporting-partner"),
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/payments`, {
payments: [paymentId],
});
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
return;
} else {
//Default is QBD
if (!!loadingCallback) loadingCallback(true);
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/payments",
{ payments: [paymentId] },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("payments.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
QbXmlResponse.data
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("payments.errors.exporting-partner"),
});
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
}
console.log("handleQbxml -> PartnerResponse", PartnerResponse);
const failedTransactions = PartnerResponse.data.filter((r) => !r.success);
const successfulTransactions = PartnerResponse.data.filter(
(r) => r.success
);
if (failedTransactions.length > 0) {
//Uh oh. At least one was no good.
failedTransactions.map((ft) =>
@@ -123,7 +132,14 @@ export function PaymentExportButton({
const paymentUpdateResponse = await updatePayment({
variables: {
paymentIdList: [paymentId],
paymentIdList: successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
? "paymentid"
: "id"
]
),
payment: {
exportedat: new Date(),
},
@@ -155,12 +171,7 @@ export function PaymentExportButton({
};
return (
<Button
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type="dashed"
>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")}
</Button>
);

View File

@@ -8,6 +8,7 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_PAYMENTS } from "../../graphql/payments.queries";
import {
selectBodyshop,
selectCurrentUser,
@@ -33,42 +34,49 @@ export function PaymentsExportAllButton({
const handleQbxml = async () => {
setLoading(true);
if (!!loadingCallback) loadingCallback(true);
let QbXmlResponse;
try {
QbXmlResponse = await axios.post("/accounting/qbxml/payments", {
let PartnerResponse;
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/payments`, {
payments: paymentIds,
});
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("payments.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
} else {
let QbXmlResponse;
try {
QbXmlResponse = await axios.post("/accounting/qbxml/payments", {
payments: paymentIds,
});
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("payments.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
QbXmlResponse.data
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("payments.errors.exporting-partner"),
});
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
}
let PartnerResponse;
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
QbXmlResponse.data
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("payments.errors.exporting-partner"),
});
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
const groupedData = _.groupBy(PartnerResponse.data, "id");
const groupedData = _.groupBy(
PartnerResponse.data,
bodyshop.accountingconfig.qbo ? "paymentid" : "id"
);
const proms = [];
Object.keys(groupedData).forEach((key) => {
proms.push(
@@ -144,12 +152,7 @@ export function PaymentsExportAllButton({
};
return (
<Button
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type="dashed"
>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.exportselected")}
</Button>
);

View File

@@ -8,6 +8,7 @@ export default function PrintWrapperComponent({
messageObject = {},
children,
id,
emailOnly = false,
}) {
const [loading, setLoading] = useState(false);
const handlePrint = async (type) => {
@@ -19,7 +20,7 @@ export default function PrintWrapperComponent({
return (
<Space>
{children || null}
<PrinterFilled onClick={() => handlePrint("p")} />
{!emailOnly && <PrinterFilled onClick={() => handlePrint("p")} />}
<MailFilled onClick={() => handlePrint("e")} />
{loading && <Spin />}
</Space>

View File

@@ -1,4 +1,4 @@
import { Input, PageHeader, Space, Spin } from "antd";
import { Input, Space, Spin } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -26,8 +26,7 @@ export function ProductionBoardFilters({
const { t } = useTranslation();
return (
<PageHeader
extra={
<Space wrap>
{loading && <Spin />}
<Input.Search
@@ -46,7 +45,6 @@ export function ProductionBoardFilters({
allowClear
/>
</Space>
}
></PageHeader>
);
}

View File

@@ -1,106 +1,171 @@
import { CalendarOutlined, EyeFilled } from "@ant-design/icons";
import { Card, Col, Row, Space } from "antd";
import React from "react";
import { Card, Row, Col, Dropdown } from "antd";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import ProductionAlert from "../production-list-columns/production-list-columns.alert.component";
import { EyeFilled } from "@ant-design/icons";
import { Link } from "react-router-dom";
import "./production-board-card.styles.scss";
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
import { useTranslation } from "react-i18next";
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
import "./production-board-card.styles.scss";
export default function ProductionBoardCard(technician, card) {
export default function ProductionBoardCard(
technician,
card,
bodyshop,
cardSettings
) {
const { t } = useTranslation();
const menu = (
<div>
<Card title={t("general.labels.actions")}>
<ProductionRemoveButton jobId={card.id} />
</Card>
</div>
);
let employee_body, employee_prep, employee_refinish; //employee_csr;
if (card.employee_body) {
employee_body = bodyshop.employees.find((e) => e.id === card.employee_body);
}
if (card.employee_prep) {
employee_prep = bodyshop.employees.find((e) => e.id === card.employee_prep);
}
if (card.employee_refinish) {
employee_refinish = bodyshop.employees.find(
(e) => e.id === card.employee_refinish
);
}
// if (card.employee_csr) {
// employee_csr = bodyshop.employees.find((e) => e.id === card.employee_csr);
// }
return (
<Dropdown overlay={menu} trigger={["contextMenu"]}>
<Card
className="react-kanban-card imex-kanban-card tight-antd-rows"
//style={{ margin: ".2rem 0rem" }}
title={`${card.ro_number || t("general.labels.na")} - ${
card.v_model_yr
} ${card.v_make_desc || ""} ${card.v_model_desc || ""}`}
>
<Row>
<Card
className="react-kanban-card imex-kanban-card"
size="small"
title={
<Space>
<ProductionAlert record={card} key="alert" />
<span style={{ fontWeight: "bolder" }}>
{card.ro_number || t("general.labels.na")}
</span>
</Space>
}
extra={
technician ? (
<Link to={`/tech/joblookup?selected=${card.id}`}>
<EyeFilled />
</Link>
) : (
<Link to={`/manage/jobs/${card.id}`}>
<EyeFilled />
</Link>
)
}
>
<Row>
{cardSettings && cardSettings.ownr_nm && (
<Col span={24}>
<div className="ellipses">{`${card.ownr_fn || ""} ${
card.ownr_ln || ""
} ${card.ownr_co_nm || ""}`}</div>
{cardSettings && cardSettings.compact ? (
<div className="ellipses">{`${card.ownr_ln || ""} ${
card.ownr_co_nm || ""
}`}</div>
) : (
<div className="ellipses">{`${card.ownr_ln || ""}, ${
card.ownr_fn || ""
} ${card.ownr_co_nm || ""}`}</div>
)}
</Col>
</Row>
<Row>
<Col span={12}>
<div className="ellipses">{card.clm_no || ""}</div>
</Col>
<Col span={12}>
)}
<Col span={24}>
<div className="ellipses">{`${card.v_model_yr || ""} ${
card.v_make_desc || ""
} ${card.v_model_desc || ""}`}</div>
</Col>
{cardSettings && cardSettings.ins_co_nm && card.ins_co_nm && (
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
<div className="ellipses">{card.ins_co_nm || ""}</div>
</Col>
</Row>
<Row>
)}
{cardSettings && cardSettings.clm_no && card.clm_no && (
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
<div className="ellipses">{card.clm_no || ""}</div>
</Col>
)}
{cardSettings && cardSettings.employeeassignments && (
<Col span={24}>
<div className="imex-flex-row imex-flex-row__flex-space-around">
<div className="mex-flex-row__margin">
<div>{`B: ${card.labhrs.aggregate.sum.mod_lb_hrs || "?"}`}</div>
<div>{`R: ${card.larhrs.aggregate.sum.mod_lb_hrs || "?"}`}</div>
</div>
<div className="mex-flex-row__margin">
<div>{`B: ${
card.employee_body_rel
? `${card.employee_body_rel.first_name} ${card.employee_body_rel.last_name}`
: ""
}`}</div>
<div>{`P: ${
card.employee_prep_rel
? `${card.employee_prep_rel.first_name} ${card.employee_prep_rel.last_name}`
: ""
}`}</div>
<div>{`R: ${
card.employee_refinish_rel
? `${card.employee_refinish_rel.first_name} ${card.employee_refinish_rel.last_name}`
: ""
}`}</div>
<div>{`CSR: ${
card.employee_csr_rel
? `${card.employee_csr_rel.first_name} ${card.employee_csr_rel.last_name}`
: ""
}`}</div>
</div>
</div>
<Row>
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`B: ${
employee_body
? `${employee_body.first_name.substr(
0,
3
)} ${employee_body.last_name.charAt(0)}`
: ""
} ${card.labhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</Col>
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`P: ${
employee_prep
? `${employee_prep.first_name.substr(
0,
3
)} ${employee_prep.last_name.charAt(0)}`
: ""
}`}</Col>
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`R: ${
employee_refinish
? `${employee_refinish.first_name.substr(
0,
3
)} ${employee_refinish.last_name.charAt(0)}`
: ""
} ${card.larhrs.aggregate.sum.mod_lb_hrs || "?"}h`}</Col>
{/* <Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`C: ${
employee_csr
? `${employee_csr.first_name} ${employee_csr.last_name}`
: ""
}`}</Col> */}
</Row>
</Col>
</Row>
<Row>
<Col span={18}>
<DateTimeFormatter>{card.scheduled_completion}</DateTimeFormatter>
)}
{/* {cardSettings && cardSettings.laborhrs && (
<Col span={24}>
<Row>
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`B: ${
card.labhrs.aggregate.sum.mod_lb_hrs || "?"
} hrs`}</Col>
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>{`R: ${
card.larhrs.aggregate.sum.mod_lb_hrs || "?"
} hrs`}</Col>
</Row>
</Col>
<Col span={6}>
)} */}
{cardSettings &&
cardSettings.scheduled_completion &&
card.scheduled_completion && (
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
<Space>
<CalendarOutlined />
<DateTimeFormatter format="MM/DD">
{card.scheduled_completion}
</DateTimeFormatter>
</Space>
</Col>
)}
{cardSettings && cardSettings.ats && card.alt_transport && (
<Col span={12}>
<div>{card.alt_transport || ""}</div>
</Col>
</Row>
<div>
<ProductionListColumnProductionNote record={card} />
</div>
<div className="imex-flex-row imex-flex-row__flex-space-around">
<ProductionAlert record={card} key="alert" />
<ProductionSubletsManageComponent subletJobLines={card.subletLines} />
{technician ? (
<Link to={`/tech/joblookup?selected=${card.id}`}>
<EyeFilled />
</Link>
) : (
<Link to={`/manage/jobs/${card.id}`}>
<EyeFilled />
</Link>
)}
</div>
</Card>
</Dropdown>
)}
{cardSettings && cardSettings.sublets && (
<Col span={12}>
<ProductionSubletsManageComponent
subletJobLines={card.subletLines}
/>
</Col>
)}
{cardSettings && cardSettings.production_note && (
<Col span={24}>
{cardSettings && cardSettings.production_note && (
<ProductionListColumnProductionNote record={card} />
)}
</Col>
)}
</Row>
</Card>
);
}

View File

@@ -0,0 +1,154 @@
import { useMutation } from "@apollo/client";
import {
Button,
Card,
Col,
Form,
notification,
Popover,
Row,
Switch,
} from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { UPDATE_KANBAN_SETTINGS } from "../../graphql/user.queries";
export default function ProductionBoardKanbanCardSettings({
associationSettings,
}) {
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false);
const [updateKbSettings] = useMutation(UPDATE_KANBAN_SETTINGS);
useEffect(() => {
form.setFieldsValue(
associationSettings && associationSettings.kanban_settings
);
}, [form, associationSettings, visible]);
const { t } = useTranslation();
const handleFinish = async (values) => {
setLoading(true);
const result = await updateKbSettings({
variables: {
id: associationSettings && associationSettings.id,
ks: values,
},
});
if (result.errors) {
notification.open({
type: "error",
message: t("production.errors.settings", {
error: JSON.stringify(result.errors),
}),
});
}
setVisible(false);
setLoading(false);
};
const overlay = (
<div>
<Card>
<Form form={form} onFinish={handleFinish} layout="vertical">
<Row gutter={[16, 16]}>
<Col span={12}>
<Form.Item
label={t("production.labels.compact")}
name="compact"
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
valuePropName="checked"
label={t("production.labels.ownr_nm")}
name="ownr_nm"
>
<Switch />
</Form.Item>
<Form.Item
valuePropName="checked"
label={t("production.labels.clm_no")}
name="clm_no"
>
<Switch />
</Form.Item>
<Form.Item
valuePropName="checked"
label={t("production.labels.ins_co_nm")}
name="ins_co_nm"
>
<Switch />
</Form.Item>
{/* <Form.Item
valuePropName="checked"
label={t("production.labels.laborhrs")}
name="laborhrs"
>
<Switch />
</Form.Item> */}
<Form.Item
valuePropName="checked"
label={t("production.labels.employeeassignments")}
name="employeeassignments"
>
<Switch />
</Form.Item>
</Col>
<Col span={12}>
<Form.Item
valuePropName="checked"
label={t("production.labels.scheduled_completion")}
name="scheduled_completion"
>
<Switch />
</Form.Item>
<Form.Item
valuePropName="checked"
label={t("production.labels.ats")}
name="ats"
>
<Switch />
</Form.Item>
<Form.Item
valuePropName="checked"
label={t("production.labels.production_note")}
name="production_note"
>
<Switch />
</Form.Item>
{/* <Form.Item
valuePropName='checked' label={t("production.labels.alert")} name="alert">
<Switch/>
</Form.Item> */}
<Form.Item
valuePropName="checked"
label={t("production.labels.sublets")}
name="sublets"
>
<Switch />
</Form.Item>
</Col>
</Row>
</Form>
<Button
onClick={() => {
form.submit();
}}
>
{t("general.actions.save")}
</Button>
</Card>
</div>
);
return (
<Popover content={overlay} visible={visible}>
<Button loading={loading} onClick={() => setVisible(true)}>
{t("production.labels.cardsettings")}
</Button>
</Popover>
);
}

View File

@@ -1,7 +1,8 @@
import { useApolloClient } from "@apollo/client";
import Board, { moveCard } from "@lourenci/react-kanban";
import "@lourenci/react-kanban/dist/styles.css";
import { notification } from "antd";
import Board, { moveCard } from "@asseinfo/react-kanban";
//import "@asseinfo/react-kanban/dist/styles.css";
import "./production-board-kanban.styles.scss";
import { Grid, notification, PageHeader, Space, Statistic } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -16,6 +17,8 @@ import ProductionBoardFilters from "../production-board-filters/production-board
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import ProductionBoardKanbanCardSettings from "./production-board-kanban.card-settings.component";
import styled from "styled-components";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -32,6 +35,7 @@ export function ProductionBoardKanbanComponent({
bodyshop,
technician,
insertAuditTrail,
associationSettings,
}) {
const [boardLanes, setBoardLanes] = useState({
columns: [{ id: "Loading...", title: "Loading...", cards: [] }],
@@ -116,6 +120,7 @@ export function ProductionBoardKanbanComponent({
newChildCard ? newChildCard.id : null,
newChildCardNewParent
),
// TODO: optimisticResponse
});
insertAuditTrail({
jobid: card.id,
@@ -131,24 +136,115 @@ export function ProductionBoardKanbanComponent({
}
};
const totalHrs = data
.reduce(
(acc, val) =>
acc +
(val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
(val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
.slice(-1)[0];
const standardSizes = {
xs: "250",
sm: "250",
md: "250",
lg: "250",
xl: "250",
xxl: "250",
};
const compactSizes = {
xs: "150",
sm: "150",
md: "150",
lg: "150",
xl: "155",
xxl: "155",
};
const width = selectedBreakpoint
? associationSettings &&
associationSettings.kanban_settings &&
associationSettings.kanban_settings.compact
? compactSizes[selectedBreakpoint[0]]
: standardSizes[selectedBreakpoint[0]]
: "250";
return (
<div>
<Container width={width}>
<IndefiniteLoading loading={isMoving} />
<ProductionBoardFilters
filter={filter}
setFilter={setFilter}
loading={isMoving}
<PageHeader
title={
<Space>
<Statistic
title={t("dashboard.titles.productionhours")}
value={totalHrs}
/>
<Statistic
title={t("appointments.labels.inproduction")}
value={data && data.length}
/>
</Space>
}
extra={
<Space wrap>
<ProductionBoardFilters
filter={filter}
setFilter={setFilter}
loading={isMoving}
/>
<ProductionBoardKanbanCardSettings
associationSettings={associationSettings}
/>
</Space>
}
/>
<Board
children={boardLanes}
disableCardDrag={isMoving}
renderCard={(card) => ProductionBoardCard(technician, card)}
renderCard={(card) =>
ProductionBoardCard(
technician,
card,
bodyshop,
associationSettings &&
associationSettings.kanban_settings &&
Object.keys(associationSettings.kanban_settings).length > 0
? associationSettings.kanban_settings
: {
ats: true,
clm_no: true,
compact: false,
ownr_nm: true,
sublets: true,
ins_co_nm: true,
production_note: true,
employeeassignments: true,
scheduled_completion: true,
}
)
}
onCardDragEnd={handleDragEnd}
/>
</div>
</Container>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionBoardKanbanComponent);
const Container = styled.div`
.react-kanban-card-skeleton,
.react-kanban-card,
.react-kanban-card-adder-form {
box-sizing: border-box;
max-width: ${(props) => props.width}px;
min-width: ${(props) => props.width}px;
}
`;

View File

@@ -1,25 +1,40 @@
import { useSubscription } from "@apollo/client";
import { useQuery, useSubscription } from "@apollo/client";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { SUBSCRIPTION_JOBS_IN_PRODUCTION } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { QUERY_KANBAN_SETTINGS } from "../../graphql/user.queries";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import ProductionBoardKanbanComponent from "./production-board-kanban.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
});
export function ProductionBoardKanbanContainer({ bodyshop }) {
export function ProductionBoardKanbanContainer({ bodyshop, currentUser }) {
const { loading, data } = useSubscription(
SUBSCRIPTION_JOBS_IN_PRODUCTION,
{}
);
const { loading: associationSettingsLoading, data: associationSettings } =
useQuery(QUERY_KANBAN_SETTINGS, {
variables: { email: currentUser.email },
});
return (
<ProductionBoardKanbanComponent
loading={loading}
loading={loading || associationSettingsLoading}
data={data ? data.jobs : []}
associationSettings={
associationSettings && associationSettings.associations[0]
? associationSettings.associations[0]
: null
}
/>
);
}

View File

@@ -0,0 +1,123 @@
.react-kanban-board {
padding: 5px;
}
.react-kanban-card {
border-radius: 3px;
background-color: #fff;
padding: 4px;
margin-bottom: 7px;
}
// .react-kanban-card-skeleton,
// .react-kanban-card,
// .react-kanban-card-adder-form {
// box-sizing: border-box;
// max-width: 145px;
// min-width: 145px;
// }
.react-kanban-card--dragging {
box-shadow: 2px 2px grey;
}
.react-kanban-card__description {
padding-top: 10px;
}
.react-kanban-card__title {
border-bottom: 1px solid #eee;
padding-bottom: 5px;
font-weight: bold;
display: flex;
justify-content: space-between;
}
.react-kanban-column {
padding: 10px;
border-radius: 2px;
background-color: #eee;
margin: 5px;
}
.react-kanban-column input:focus {
outline: none;
}
.react-kanban-card-adder-form {
border-radius: 3px;
background-color: #fff;
padding: 10px;
margin-bottom: 7px;
}
.react-kanban-card-adder-form input {
border: 0px;
font-family: inherit;
font-size: inherit;
}
.react-kanban-card-adder-button {
width: 100%;
margin-top: 5px;
background-color: transparent;
cursor: pointer;
border: 1px solid #ccc;
transition: 0.3s;
border-radius: 3px;
font-size: 20px;
margin-bottom: 10px;
font-weight: bold;
}
.react-kanban-card-adder-button:hover {
background-color: #ccc;
}
.react-kanban-card-adder-form__title {
font-weight: bold;
border-bottom: 1px solid #eee;
padding-bottom: 5px;
font-weight: bold;
display: flex;
justify-content: space-between;
width: 100%;
padding: 0px;
}
.react-kanban-card-adder-form__title:focus {
outline: none;
}
.react-kanban-card-adder-form__description {
width: 100%;
margin-top: 10px;
}
.react-kanban-card-adder-form__description:focus {
outline: none;
}
.react-kanban-card-adder-form__button {
background-color: #eee;
border: none;
padding: 5px;
width: 45%;
margin-top: 5px;
border-radius: 3px;
}
.react-kanban-card-adder-form__button:hover {
transition: 0.3s;
cursor: pointer;
background-color: #ccc;
}
.react-kanban-column-header {
padding-bottom: 10px;
font-weight: bold;
}
.react-kanban-column-header input:focus {
outline: none;
}
.react-kanban-column-header__button {
color: #333333;
background-color: #ffffff;
border-color: #cccccc;
}
.react-kanban-column-header__button:hover,
.react-kanban-column-header__button:focus,
.react-kanban-column-header__button:active {
background-color: #e6e6e6;
}
.react-kanban-column-adder-button {
border: 2px dashed #eee;
height: 132px;
margin: 5px;
}
.react-kanban-column-adder-button:hover {
cursor: pointer;
}

View File

@@ -6,9 +6,11 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
technician: selectTechnician,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -21,6 +23,7 @@ export default connect(
export function ProductionColumnsComponent({
columnState,
technician,
bodyshop,
tableState,
}) {
const [columns, setColumns] = columnState;
@@ -29,9 +32,11 @@ export function ProductionColumnsComponent({
const handleAdd = (e) => {
setColumns([
...columns,
...dataSource({ technician, state: tableState }).filter(
(i) => i.key === e.key
),
...dataSource({
technician,
state: tableState,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).filter((i) => i.key === e.key),
]);
};
@@ -39,7 +44,11 @@ export function ProductionColumnsComponent({
const menu = (
<Menu onClick={handleAdd}>
{dataSource({ technician, state: tableState })
{dataSource({
technician,
state: tableState,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
})
.filter((i) => !columnKeys.includes(i.key))
.map((item) => (
<Menu.Item key={item.key}>{item.title}</Menu.Item>

View File

@@ -40,7 +40,7 @@ export default function ProductionListColumnBodyPriority({ record }) {
key="set"
title={t("production.actions.bodypriority-set")}
>
{new Array(9).fill().map((value, index) => (
{new Array(15).fill().map((value, index) => (
<Menu.Item key={index + 1}>{index + 1}</Menu.Item>
))}
</Menu.SubMenu>

View File

@@ -1,9 +1,10 @@
import i18n from "i18next";
import moment from "moment";
import React from "react";
import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import JobAltTransportChange from "../job-at-change/job-at-change.component";
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
import ProductionListColumnAlert from "./production-list-columns.alert.component";
@@ -11,13 +12,13 @@ import ProductionListColumnBodyPriority from "./production-list-columns.bodyprio
import ProductionListDate from "./production-list-columns.date.component";
import ProductionListColumnDetailPriority from "./production-list-columns.detailpriority.component";
import ProductionListEmployeeAssignment from "./production-list-columns.empassignment.component";
import ProductionListLastContacted from "./production-list-columns.lastcontacted.component";
import ProductionListColumnPaintPriority from "./production-list-columns.paintpriority.component";
import ProductionListColumnNote from "./production-list-columns.productionnote.component";
import ProductionListColumnStatus from "./production-list-columns.status.component";
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
import ProductionListLastContacted from "./production-list-columns.lastcontacted.component";
const r = ({ technician, state }) => {
const r = ({ technician, state, activeStatuses }) => {
return [
{
title: i18n.t("jobs.actions.viewdetail"),
@@ -109,6 +110,29 @@ const r = ({ technician, state }) => {
state.sortedInfo.order,
render: (text, record) => <ProductionListLastContacted record={record} />,
},
{
title: i18n.t("jobs.fields.date_next_contact"),
dataIndex: "date_next_contact",
key: "date_next_contact",
ellipsis: true,
sorter: (a, b) => dateSort(a.date_next_contact, b.date_next_contact),
sortOrder:
state.sortedInfo.columnKey === "date_next_contact" &&
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"),
dataIndex: "scheduled_delivery",
@@ -210,7 +234,7 @@ const r = ({ technician, state }) => {
dataIndex: "status",
key: "status",
ellipsis: true,
sorter: (a, b) => alphaSort(a.status, b.status),
sorter: (a, b) => statusSort(a.status, b.status, activeStatuses),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
render: (text, record) => <ProductionListColumnStatus record={record} />,
@@ -332,7 +356,7 @@ const r = ({ technician, state }) => {
render: (text, record) => (
<ProductionListEmployeeAssignment
record={record}
type="employee_body_rel"
type="employee_body"
/>
),
},
@@ -343,7 +367,7 @@ const r = ({ technician, state }) => {
render: (text, record) => (
<ProductionListEmployeeAssignment
record={record}
type="employee_prep_rel"
type="employee_prep"
/>
),
},
@@ -352,10 +376,7 @@ const r = ({ technician, state }) => {
dataIndex: "employee_csr",
key: "employee_csr",
render: (text, record) => (
<ProductionListEmployeeAssignment
record={record}
type="employee_csr_rel"
/>
<ProductionListEmployeeAssignment record={record} type="employee_csr" />
),
},
{
@@ -365,7 +386,7 @@ const r = ({ technician, state }) => {
render: (text, record) => (
<ProductionListEmployeeAssignment
record={record}
type="employee_refinish_rel"
type="employee_refinish"
/>
),
},

View File

@@ -40,7 +40,7 @@ export default function ProductionListColumnDetailPriority({ record }) {
key="set"
title={t("production.actions.detailpriority-set")}
>
{new Array(9).fill().map((value, index) => (
{new Array(15).fill().map((value, index) => (
<Menu.Item key={index + 1}>{index + 1}</Menu.Item>
))}
</Menu.SubMenu>

View File

@@ -49,7 +49,7 @@ export function ProductionListEmpAssignment({
const result = await updateJob({
variables: { jobId: record.id, job: { [empAssignment]: employeeid } },
awaitRefetchQueries: true,
// awaitRefetchQueries: true,
});
insertAuditTrail({
@@ -145,13 +145,18 @@ export function ProductionListEmpAssignment({
</Row>
);
let theEmployee;
if (record[type])
theEmployee = bodyshop.employees.find((e) => e.id === record[type]);
return (
<Popover destroyTooltipOnHide content={popContent} visible={visibility}>
<Spin spinning={loading}>
{record[type] ? (
<div>
<span>{`${record[type].first_name || ""} ${
record[type].last_name || ""
<span>{`${theEmployee.first_name || ""} ${
theEmployee.last_name || ""
}`}</span>
<DeleteFilled
style={iconStyle}
@@ -174,13 +179,13 @@ export function ProductionListEmpAssignment({
const determineFieldName = (operation) => {
switch (operation) {
case "employee_body_rel":
case "employee_body":
return "employee_body";
case "employee_prep_rel":
case "employee_prep":
return "employee_prep";
case "employee_refinish_rel":
case "employee_refinish":
return "employee_refinish";
case "employee_csr_rel":
case "employee_csr":
return "employee_csr";
default:
return null;

View File

@@ -29,7 +29,11 @@ export function ProductionLastContacted({ currentUser, record }) {
const [visible, setVisible] = useState(false);
const { t } = useTranslation();
const [form] = Form.useForm();
const handleFinish = async ({ date_last_contacted, note }) => {
const handleFinish = async ({
date_last_contacted,
date_next_contact,
note,
}) => {
logImEXEvent("production_last_contacted");
//e.stopPropagation();
@@ -38,6 +42,7 @@ export function ProductionLastContacted({ currentUser, record }) {
jobId: record.id,
job: {
date_last_contacted,
...(date_next_contact ? { date_next_contact } : {}),
},
},
});
@@ -98,7 +103,16 @@ export function ProductionLastContacted({ currentUser, record }) {
onClick={(e) => e.stopPropagation()}
>
<Form form={form} onFinish={handleFinish} layout="vertical">
<Form.Item name="date_last_contacted">
<Form.Item
name="date_last_contacted"
label={t("jobs.fields.date_last_contacted")}
>
<FormDateTimePickerComponent />
</Form.Item>
<Form.Item
name="date_next_contact"
label={t("jobs.fields.date_next_contact")}
>
<FormDateTimePickerComponent />
</Form.Item>
<Form.Item label={t("notes.labels.notetoadd")} name="note">

View File

@@ -40,7 +40,7 @@ export default function ProductionListColumnPaintPriority({ record }) {
key="set"
title={t("production.actions.paintpriority-set")}
>
{new Array(9).fill().map((value, index) => (
{new Array(15).fill().map((value, index) => (
<Menu.Item key={index + 1}>{index + 1}</Menu.Item>
))}
</Menu.SubMenu>

View File

@@ -37,9 +37,11 @@ export function ProductionListTable({
.filter((pc) => pc.name === value)[0]
.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({ technician, state }).find(
(e) => e.key === k.key
),
...ProductionListColumns({
technician,
state,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).find((e) => e.key === k.key),
width: k.width,
};
})
@@ -88,9 +90,11 @@ export function ProductionListTable({
setColumns(
bodyshop.production_config[0].columns.columnKeys.map((k) => {
return {
...ProductionListColumns({ technician, state }).find(
(e) => e.key === k.key
),
...ProductionListColumns({
technician,
state,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).find((e) => e.key === k.key),
width: k.width,
};
})

View File

@@ -1,4 +1,12 @@
import { Dropdown, Input, Menu, PageHeader, Space, Table } from "antd";
import {
Dropdown,
Input,
Menu,
PageHeader,
Space,
Statistic,
Table,
} from "antd";
import React, { useMemo, useState } from "react";
import ReactDragListView from "react-drag-listview";
import { useTranslation } from "react-i18next";
@@ -16,6 +24,7 @@ import ProductionListSaveConfigButton from "../production-list-save-config-butto
import ProductionListPrint from "./production-list-print.component";
import ProductionListTableViewSelect from "./production-list-table-view-select.component";
import ResizeableTitle from "./production-list-table.resizeable.component";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -31,7 +40,9 @@ export function ProductionListTable({
currentUser,
}) {
const [searchText, setSearchText] = useState("");
const { Production_List_Status_Colors } = useTreatments([
"Production_List_Status_Colors",
]);
const assoc = bodyshop.associations.find(
(a) => a.useremail === currentUser.email
);
@@ -59,9 +70,11 @@ export function ProductionListTable({
matchingColumnConfig &&
matchingColumnConfig.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({ technician, state }).find(
(e) => e.key === k.key
),
...ProductionListColumns({
technician,
state,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).find((e) => e.key === k.key),
width: k.width,
};
})) ||
@@ -156,9 +169,30 @@ export function ProductionListTable({
if (!!!columns) return <div>No columns found.</div>;
const totalHrs = data
.reduce(
(acc, val) =>
acc +
(val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
(val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
0
)
.toFixed(1);
return (
<div>
<PageHeader
title={
<Space>
<Statistic
title={t("dashboard.titles.productionhours")}
value={totalHrs}
/>
<Statistic
title={t("appointments.labels.inproduction")}
value={dataSource && dataSource.length}
/>
</Space>
}
extra={
<Space wrap>
<ProductionListColumnsAdd
@@ -192,8 +226,28 @@ export function ProductionListTable({
handleSelector=".prod-header-dropdown"
>
<Table
sticky
pagination={false}
size="small"
className="production-list-table"
onRow={
Production_List_Status_Colors.treatment === "on" &&
((record, index) => {
if (!bodyshop.md_ro_statuses.production_colors) return null;
const color = bodyshop.md_ro_statuses.production_colors.find(
(x) => x.status === record.status
);
if (!color) return null;
return {
style: {
backgroundColor: `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
},
};
})
}
components={{
header: {
cell: ResizeableTitle,

View File

@@ -56,7 +56,10 @@ export default function ProductionSubletsManageComponent({ subletJobLines }) {
<Button
key="complete"
loading={loading}
onClick={() => handleSubletMark(s, "complete")}
onClick={(e) => {
e.stopPropagation();
handleSubletMark(s, "complete");
}}
type={s.sublet_completed ? "primary" : "ghost"}
>
<CheckCircleFilled
@@ -66,7 +69,10 @@ export default function ProductionSubletsManageComponent({ subletJobLines }) {
<Button
key="sublet"
loading={loading}
onClick={() => handleSubletMark(s, "ignore")}
onClick={(e) => {
e.stopPropagation();
handleSubletMark(s, "ignore");
}}
type={s.sublet_ignored ? "primary" : "ghost"}
>
<EyeInvisibleFilled

View File

@@ -1,4 +1,4 @@
import { Space, Tag } from "antd";
import { Space } from "antd";
import Axios from "axios";
import queryString from "query-string";
import React, { useEffect } from "react";
@@ -9,7 +9,7 @@ import QboSignIn from "../../assets/qbo/C2QB_green_btn_med_default.svg";
export default function QboAuthorizeComponent() {
const location = useLocation();
const history = useHistory();
const [cookies, setCookie] = useCookies(["access_token", "refresh_token"]);
const [, setCookie] = useCookies(["access_token", "refresh_token"]);
const handleQbSignIn = async () => {
const result = await Axios.post("/qbo/authorize");
@@ -24,16 +24,20 @@ export default function QboAuthorizeComponent() {
const hasBeenCalledBack = code && realmId && state;
if (hasBeenCalledBack) {
setCookie("qbo_code", code, { path: "/" });
setCookie("qbo_state", state, { path: "/" });
// setCookie("qbo_code", code, { path: "/" });
// setCookie("qbo_state", state, { path: "/" });
let expires = new Date();
expires.setTime(expires.getTime() + 8726400 * 1000);
// let expires = new Date();
// expires.setTime(expires.getTime() + 8726400 * 1000);
setCookie("qbo_realmId", realmId, {
path: "/",
expires,
});
// setCookie("qbo_realmId", realmId, {
// path: "/",
// expires,
// ...(process.env.NODE_ENV !== "development"
// ? { domain: `.${window.location.host}` }
// : {}),
// });
history.push({ pathname: `/manage/accounting/receivables` });
}
@@ -48,9 +52,7 @@ export default function QboAuthorizeComponent() {
src={QboSignIn}
style={{ cursor: "pointer" }}
/>
{!cookies.qbo_realmId && (
<Tag color="red">No QuickBooks company has been connected.</Tag>
)}
{error && JSON.parse(decodeURIComponent(error)).error_description}
</Space>
);

View File

@@ -71,7 +71,7 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
const start = values.dates[0];
const end = values.dates[1];
const { id } = values;
console.log("values", values);
await GenerateDocument(
{
name: values.key,

View File

@@ -3,6 +3,7 @@ import { Button, Card, Col, PageHeader, Row, Space } from "antd";
import React from "react";
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
//import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
import ScheduleProductionList from "../schedule-production-list/schedule-production-list.component";
export default function ScheduleCalendarComponent({ data, refetch }) {
@@ -21,8 +22,10 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
>
<SyncOutlined />
</Button>
<ScheduleProductionList />
{
// <ScheduleManualEvent />
}
</Space>
}
/>

View File

@@ -5,9 +5,8 @@ import {
Input,
Row,
Select,
Space,
Switch,
Typography,
Space, Switch,
Typography
} from "antd";
import axios from "axios";
import moment from "moment";
@@ -75,6 +74,12 @@ export function ScheduleJobModalComponent({
return (
<Row gutter={[16, 16]}>
<Col span={12}>
<Space>
<Typography.Title level={3}>{lbrHrsData?.jobs_by_pk?.ro_number}</Typography.Title>
<Typography.Title
level={4}
>{`B/R Hrs:${lbrHrsData?.jobs_by_pk.labhrs?.aggregate.sum.mod_lb_hrs}/${lbrHrsData?.jobs_by_pk.larhrs?.aggregate.sum.mod_lb_hrs}`}</Typography.Title>
</Space>
<LayoutFormRow grow>
<Form.Item
name="start"

View File

@@ -63,6 +63,20 @@ export function ScheduleJobModalContainer({
skip: !visible || !!!jobId,
});
useEffect(() => {
if (
existingAppointments.data &&
existingAppointments.data.appointments.length > 0 &&
!existingAppointments.data.appointments[0].canceled
) {
form.setFieldsValue({
color: existingAppointments.data.appointments[0].color,
note: existingAppointments.data.appointments[0].note,
});
}
}, [existingAppointments.data, form]);
const handleFinish = async (values) => {
logImEXEvent("schedule_new_appointment");
@@ -87,7 +101,6 @@ export function ScheduleJobModalContainer({
}
if (existingAppointments.data.appointments.length > 0) {
console.log("Cancelling all previous appts.");
await Promise.all(
existingAppointments.data.appointments.map((app) => {
return cancelAppointment({
@@ -105,7 +118,7 @@ export function ScheduleJobModalContainer({
start: moment(values.start),
end: moment(values.start).add(bodyshop.appt_length || 60, "minutes"),
color: values.color,
note:values.note
note: values.note,
},
jobId: jobId,
altTransport: values.alt_transport,
@@ -188,6 +201,9 @@ export function ScheduleJobModalContainer({
start: null,
// smartDates: [],
scheduled_completion: null,
color: context.color,
alt_transport: context.alt_transport,
note: context.note,
}}
>
<ScheduleJobModalComponent

View File

@@ -0,0 +1,119 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Form, Input, Popover, Space } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {
INSERT_APPOINTMENT,
UPDATE_APPOINTMENT,
} from "../../graphql/appointments.queries";
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
export default function ScheduleManualEvent({ event }) {
const { t } = useTranslation();
const [insertAppointment] = useMutation(INSERT_APPOINTMENT);
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const [visibility, setVisibility] = useState(false);
// const [callQuery, { loading: entryLoading, data: entryData }] = useLazyQuery(
// QUERY_SCOREBOARD_ENTRY
// );
useEffect(() => {
if (visibility && event) {
form.setFieldsValue({ event });
}
}, [visibility, form, event]);
useEffect(() => {
// if (entryData && entryData.scoreboard && entryData.scoreboard[0]) {
// console.log("Setting FOrm");
// // form.setFieldsValue(entryData.scoreboard[0]);
// }
}, [form]);
const handleFinish = async (values) => {
logImEXEvent("job_close_add_to_scoreboard");
setLoading(true);
try {
if (event && event.id) {
updateAppointment();
} else {
insertAppointment();
}
} catch (error) {
console.log(error);
} finally {
setLoading(false);
setVisibility(false);
}
};
const overlay = (
<Card>
<div>
<Form form={form} layout="vertical" onFinish={handleFinish}>
<Form.Item
label={t("schedule.fields.note")}
name="note"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("schedule.fields.start")}
name="start"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDateTimePickerComponent />
</Form.Item>
<Form.Item
label={t("schedule.fields.end")}
name="end"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDateTimePickerComponent />
</Form.Item>
<Space wrap>
<Button type="primary" htmlType="submit">
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisibility(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</div>
</Card>
);
const handleClick = (e) => {
setVisibility(true);
};
return (
<Popover content={overlay} visible={visibility}>
<Button loading={loading} onClick={handleClick}>
{t("schedule.labels.manualevent")}
</Button>
</Popover>
);
}

View File

@@ -3,12 +3,9 @@ import { Form } from "antd";
import ConfigFormComponents from "../config-form-components/config-form-components.component";
export default function ShopCsiConfigForm({ selectedCsi }) {
console.log("ShopCsiConfigForm -> selectedCsi", selectedCsi);
const readOnly = !!selectedCsi;
const [form] = Form.useForm();
const handleFinish = (values) => {
console.log("values :>> ", values);
};
const handleFinish = (values) => {};
return (
<div>

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