Compare commits

...

271 Commits

Author SHA1 Message Date
Allan Carr
98bff6d8f6 IO-2824 Dev Server Instance Switch
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-20 10:56:07 -07:00
Dave Richer
7959dc67ce Merged in release/AIO/2024-06-07 (pull request #1482)
Release/AIO/2024 06 07
2024-06-11 00:38:31 +00:00
Allan Carr
a4116b6c28 Merged in feature/IO-2793-State-Tax-Null-QBO (pull request #1480)
IO-2793 Correction for Sublet Part Tax
2024-06-10 21:41:17 +00:00
Allan Carr
0d2cdec75c IO-2793 Correction for Sublet Part Tax
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-10 14:42:21 -07:00
Allan Carr
269ef25ece IO-2793 Better tax handling
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-10 14:14:15 -07:00
Allan Carr
575fbd5357 Merged in feature/IO-2793-State-Tax-Null-QBO (pull request #1478)
IO-2793 Better tax handling
2024-06-10 21:13:20 +00:00
Allan Carr
2ad887fb82 Merged in feature/IO-2793-State-Tax-Null-QBO (pull request #1476)
IO-2793 Correct passed Variable
2024-06-10 18:25:27 +00:00
Allan Carr
40a1a86f72 IO-2793 Correct passed Variable
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-10 11:25:06 -07:00
Allan Carr
95d43d936c Merged in feature/IO-2793-State-Tax-Null-QBO (pull request #1474)
IO-2793 Correct variables
2024-06-10 18:03:12 +00:00
Allan Carr
9f56568680 IO-2793 Correct variables
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-10 10:58:01 -07:00
Allan Carr
3428940c72 Merged in feature/IO-2793-State-Tax-Null-QBO (pull request #1472)
IO-2793 Change to function for better clarity
2024-06-10 17:49:06 +00:00
Allan Carr
ea604a5e64 IO-2793 Change to function for better clarity
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-10 09:59:45 -07:00
Allan Carr
5b76473cbc IO-2793 Correct Parts Side for Taxes
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-07 15:59:51 -07:00
Allan Carr
db5359e086 Merged in feature/IO-2793-State-Tax-Null-QBO (pull request #1470)
Feature/IO-2793 State Tax Null QBO
2024-06-07 22:58:58 +00:00
Allan Carr
35046f11c2 IO-2793 Add Comment to see output for testing
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-07 14:31:33 -07:00
Allan Carr
69d8d27ad3 Merged in feature/IO-2814-Job-Costing-Correction (pull request #1467)
IO-2814 Remove Comment as api.test.romeonline.io doesn't update w/ CICD
2024-06-07 19:16:01 +00:00
Allan Carr
f4c4005a2a IO-2814 Remove Comment as api.test.romeonline.io doesn't update w/ CICD
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-07 12:16:59 -07:00
Allan Carr
755acd24f0 IO-2814 Correct Parts Price and add Console Log
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-07 11:40:39 -07:00
Allan Carr
079d6cfee6 Merged in feature/IO-2814-Job-Costing-Correction (pull request #1465)
IO-2814 Correct Parts Price and add Console Log
2024-06-07 18:39:43 +00:00
Allan Carr
3e3b3c269a Merged in feature/IO-2814-Job-Costing-Correction (pull request #1462)
IO-2814 Job Cost Correction
2024-06-07 03:18:49 +00:00
Allan Carr
39f1af7d4b Merged in feature/IO-2793-State-Tax-Null-QBO (pull request #1463)
IO-2793 State Tax Null QBO
2024-06-07 03:18:40 +00:00
Allan Carr
1ea4d616d7 IO-2793 State Tax Null QBO
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-06 15:00:58 -07:00
Allan Carr
fdf0ecf6f6 IO-2814 Job Cost Correction
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-06 14:51:44 -07:00
Allan Carr
e5f7285253 Merge branch 'feature/IO-2793-State-Tax-Null-QBO' into release/AIO/2024-06-07
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>

# Conflicts:
#	server/accounting/qb-receivables-lines.js
2024-06-03 17:28:18 -07:00
Allan Carr
e46c304f7c IO-2793 State Tax to QBO refactor
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-03 17:23:33 -07:00
Dave Richer
1aa570db90 Merged in hotfix/revert-tax-import-code (pull request #1459)
revert previous change due to problems with tax import.
2024-06-03 22:04:31 +00:00
Dave Richer
1c2be3c890 Merged in hotfix/revert-tax-import-code (pull request #1458)
revert previous change due to problems with tax import.
2024-06-03 22:04:05 +00:00
Dave Richer
05e4eacf34 revert previous change due to problems with tax import.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-06-03 18:03:22 -04:00
Allan Carr
e1417b03f9 Merged in feature/IO-2801-os-loader-bills-data (pull request #1456)
IO-2801 os-loader bills data

Approved-by: Dave Richer
2024-06-03 18:59:08 +00:00
Allan Carr
7b99de8046 IO-2801 os-loader bills data
add in job object to bills in opensearch for ro_number to be searchable again

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-03 11:50:44 -07:00
Dave Richer
6dc03d7f67 Merged in release/AIO/2024-05-31 (pull request #1452)
Release/AIO/2024 05 31
2024-06-01 17:19:20 +00:00
Allan Carr
2a5e6a51aa Merged in feature/IO-2793-State-Tax-Null-QBO (pull request #1451)
IO-2793 State Tax Rate Null send to QBO

Approved-by: Dave Richer
2024-06-01 17:16:55 +00:00
Allan Carr
885477fa68 IO-2793 State Tax Rate Null send to QBO
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-05-31 12:31:49 -07:00
Dave Richer
c7db792793 Merged in feature/IO-2796-Header-IDS (pull request #1450)
- Add Additional IDS to headers
2024-05-31 17:23:55 +00:00
Dave Richer
31be51ef79 Merged release/AIO/2024-05-31 into feature/IO-2796-Header-IDS 2024-05-31 17:23:45 +00:00
Dave Richer
e4066c1570 - Add Additional IDS to headers
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-05-31 13:20:54 -04:00
Patrick Fic
ec480f2cb1 Resolve Bill Posting errors on Beta. 2024-05-29 14:08:49 -07:00
Patrick Fic
f5b30c9376 Resolve API Route. 2024-05-28 12:23:42 -07:00
Patrick Fic
412508fbcf Merge branch 'hotfix/AIO/2024-05-28' into master-AIO 2024-05-28 12:19:28 -07:00
Patrick Fic
dbfd9bce54 Resolve region for US intellipay. 2024-05-28 12:19:18 -07:00
Dave Richer
52e0558c79 Merged in release/AIO/2024-05-24 (pull request #1449)
Release/AIO/2024 05 24
2024-05-24 20:45:23 +00:00
Allan Carr
562d0b8641 Merged in feature/IO-2785-IO-Rescue-Header (pull request #1444)
IO-2785 AIO IO Header Rescue Link

Approved-by: Dave Richer
2024-05-23 19:47:27 +00:00
Patrick Fic
d48d6cdd91 Merge branch 'hotfix/AIO/2024-05-23' into release/AIO/2024-05-24 2024-05-23 11:40:35 -07:00
Patrick Fic
9363790541 Merge branch 'hotfix/AIO/2024-05-23' into master-AIO 2024-05-23 10:58:13 -07:00
Patrick Fic
2bceba948d Resolve postback logging. 2024-05-23 10:57:39 -07:00
Allan Carr
168d4246af IO-2785 AIO IO Header Rescue Link
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-05-22 11:43:19 -07:00
Patrick Fic
cdf6050ec2 Merge branch 'release/AIO/2024-05-17' into release/AIO/2024-05-24 2024-05-22 08:43:41 -07:00
Patrick Fic
f451155689 Add Firebase Service Worker to public. 2024-05-15 10:27:42 -07:00
Dave Richer
9f06e19346 Fix eslint regression
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-05-14 12:38:56 -04:00
Dave Richer
f4be3e9668 Fix issue with global search 2024-05-14 12:20:49 -04:00
Patrick Fic
ee613db0cb Merged in test-AIO (pull request #1443)
Resolve instance conflict for promanager.
2024-05-13 15:26:07 +00:00
Patrick Fic
55b892a74e Resolve instance conflict for promanager. 2024-05-13 08:25:45 -07:00
Patrick Fic
16754d9657 Merged in test-AIO (pull request #1442)
Test AIO
2024-05-10 22:36:17 +00:00
Patrick Fic
33db67122c Fix RBAC spread. 2024-05-10 15:35:26 -07:00
Patrick Fic
b9b88b0e23 Merged in release/AIO/2024-05-10 (pull request #1441)
Release/AIO/2024 05 10
2024-05-10 16:11:42 +00:00
Patrick Fic
992ad71910 Merge branch 'release/AIO/2024-05-10' into test-AIO 2024-05-10 08:33:31 -07:00
Patrick Fic
51d1f926c2 Improve spread for feature wrapped RBAC items. 2024-05-10 08:29:52 -07:00
Patrick Fic
c70447f337 Merge branch 'release/AIO/2024-05-10' into test-AIO 2024-05-10 08:08:32 -07:00
Patrick Fic
dc367e1a30 Add PAE Part Tax type for USA. 2024-05-10 08:08:15 -07:00
Patrick Fic
ee7997ffbc Merge branch 'release/AIO/2024-05-10' into test-AIO 2024-05-08 15:16:55 -07:00
Patrick Fic
2dbb5adbbb Merge branch 'feature/AIO/promanager' into release/AIO/2024-05-10 2024-05-08 15:16:47 -07:00
Patrick Fic
cc9f342575 Update expired page, remove RBAC, and prevent sign in. 2024-05-08 15:16:03 -07:00
Patrick Fic
252262f4a7 Merge branch 'master-AIO' into feature/AIO/promanager 2024-05-08 14:21:55 -07:00
Patrick Fic
f77a16648f Merged in release/AIO/2024-04-26 (pull request #1440)
Release/AIO/2024 04 26
2024-05-03 21:42:14 +00:00
Patrick Fic
d6e3c54b68 Merge branch 'release/AIO/2024-04-26' into test-AIO 2024-05-03 14:38:38 -07:00
Patrick Fic
7294e96a77 Merge branch 'release/2024-04-26' into release/AIO/2024-04-26 2024-05-03 14:38:04 -07:00
Allan Carr
a4f4f45251 Merged in feature/IO-2717-Paint-Material-from-Scale-for-CDK (pull request #1438)
IO-2717 CDK Use Scale Data for Paint Materials Cost
2024-05-02 05:53:35 +00:00
Allan Carr
acc91abc0c IO-2717 CDK Use Scale Data for Paint Materials Cost
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-05-01 13:26:24 -07:00
Patrick Fic
18d5176cb9 Merge branch 'release/2024-04-26' into release/AIO/2024-04-26 2024-05-01 11:49:09 -07:00
Patrick Fic
d6d6ced7a4 CC modifications. 2024-05-01 11:47:42 -07:00
Patrick Fic
52809cc849 Merge branch 'release/AIO/2024-04-26' into test-AIO 2024-04-29 12:10:03 -07:00
Patrick Fic
c525b7ea3f Place featurewrapped on bills in job line expander. 2024-04-29 12:06:42 -07:00
Patrick Fic
e799417aaf Remove header for partner interaction to comply with CORS. 2024-04-29 10:50:18 -07:00
Patrick Fic
ef077c2d48 Add render manager for part price changes. 2024-04-29 10:33:34 -07:00
Patrick Fic
e78b114544 Merge branch 'release/AIO/2024-04-26' into test-AIO 2024-04-26 08:18:25 -07:00
Patrick Fic
df170ddd27 Merge branch 'release/2024-04-26' into release/AIO/2024-04-26 2024-04-25 17:24:59 -07:00
Patrick Fic
17928741e3 Merge branch 'feature/IO-2766-intellipay-postback-refactor' into release/2024-04-26 2024-04-25 16:44:55 -07:00
Patrick Fic
a6b825ffdf Move intellipay to server side processing. 2024-04-25 16:43:49 -07:00
Patrick Fic
0d3bc0e95f Merge branch 'hotfix/AIO/2024-04-24' into release/AIO/2024-04-26 2024-04-25 08:19:00 -07:00
Patrick Fic
0f62a2d73d Merge branch 'hotfix/AIO/2024-04-24' into master-AIO 2024-04-24 11:13:22 -07:00
Patrick Fic
bcdd32f92f Merge branch 'hotfix/AIO/2024-04-24' into test-AIO 2024-04-24 10:52:18 -07:00
Patrick Fic
6865fcbffc Missing import instance 2024-04-24 10:51:09 -07:00
Patrick Fic
8e623c71a9 Merge branch 'hotfix/AIO/2024-04-24' into test-AIO 2024-04-24 10:17:40 -07:00
Patrick Fic
1e06502464 Correct instance manager for job totals in other files. 2024-04-24 10:17:28 -07:00
Allan Carr
f2914868e2 Merged in feature/IO-2761-Actual-Complete-in-Vehicle-Owner-Details (pull request #1434)
IO-2761 Actual Completion in Vehicle and Owners Job Details lists

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

Approved-by: Dave Richer
2024-04-23 17:39:27 +00:00
Allan Carr
1e27c4e1ae Merged in feature/IO-2763-Job-Action-Button (pull request #1436)
IO-2763 Job Action Button

Approved-by: Dave Richer
2024-04-23 17:38:28 +00:00
Allan Carr
46676ba8eb IO-2763 Job Action Button
Create Courtesy Car and Create Task items added

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-22 15:18:13 -07:00
Allan Carr
02a49efbea IO-2762 Return from Bill Reference in Parts Return Drawer
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-22 14:14:54 -07:00
Patrick Fic
5f2a5e1025 Merge branch 'release/AIO/2024-04-26' into test-AIO 2024-04-22 14:03:50 -07:00
Patrick Fic
42552e4f4f Promanager resolution & remove product fruit dev items. 2024-04-22 14:03:29 -07:00
Patrick Fic
f2af78f056 Merge branch 'release/AIO/2024-04-26' into test-AIO 2024-04-22 13:15:11 -07:00
Patrick Fic
269d55bde3 Add missing translations. 2024-04-22 13:14:58 -07:00
Patrick Fic
71f3dbbeb4 Merge branch 'release/AIO/2024-04-26' into test-AIO 2024-04-22 12:39:53 -07:00
Patrick Fic
73dfb74ed7 ProManager instance server updates. 2024-04-22 12:39:30 -07:00
Patrick Fic
7dd6baef33 Hardcore generic template for ProManager. 2024-04-22 11:28:49 -07:00
Patrick Fic
519b532091 Add download messages for partner. 2024-04-22 11:28:36 -07:00
Allan Carr
4fb9c37c0d IO-2761 Actual Completion in Vehicle and Owners Job Details lists
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-22 09:15:59 -07:00
Patrick Fic
c9b63be29f Hardcore generic template for ProManager. 2024-04-22 08:43:41 -07:00
Patrick Fic
84bc735dce Merged in test-AIO (pull request #1432)
Test AIO
2024-04-19 19:43:56 +00:00
Patrick Fic
a44b9417a1 Merged in feature/IO-2677-Tasks (pull request #1433)
Resolve missing query paramters.
2024-04-19 19:41:48 +00:00
Patrick Fic
6b157ed43c Resolve missing query paramters. 2024-04-19 12:41:26 -07:00
Allan Carr
9050276ea7 Merged in feature/IO-2677-Tasks (pull request #1431)
Feature/IO-2677 Tasks

Approved-by: Patrick Fic
2024-04-19 18:35:50 +00:00
Allan Carr
683293d042 IO-2667 Tasks adjust for no RO
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-19 11:34:18 -07:00
Allan Carr
70d009ab49 IO-2667 Tasks Change multi subject line
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-19 11:27:46 -07:00
Allan Carr
dbc2d10d6d IO-2667 Tasks Email Generation
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-19 11:25:02 -07:00
Patrick Fic
059b854db9 Merged in feature/IO-2677-Tasks (pull request #1430)
Update reminder interval and resolve server side.
2024-04-19 17:54:29 +00:00
Patrick Fic
13569a1785 Update reminder interval and resolve server side. 2024-04-19 10:53:25 -07:00
Dave Richer
6fd70b165b Merged in feature/IO-2760-IDS-for-headers (pull request #1429)
- Add Additional tags (prettier also fixed some double spaced imports)
2024-04-19 17:32:04 +00:00
Dave Richer
11d94cf286 Merged in feature/IO-2760-IDS-for-headers (pull request #1428)
- Add Additional tags (prettier also fixed some double spaced imports)

Approved-by: Patrick Fic
2024-04-19 17:21:40 +00:00
Dave Richer
c98a48ea14 - Add Additional tags (prettier also fixed some double spaced imports)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-19 12:57:45 -04:00
Allan Carr
b8fa80419b Merged in feature/IO-2677-Tasks (pull request #1427)
Feature/IO-2677 Tasks

Approved-by: Patrick Fic
2024-04-19 16:29:32 +00:00
Patrick Fic
2a8846297f Update assigned_to handling on front end & debug task assignment. 2024-04-19 09:22:20 -07:00
Patrick Fic
fb322f760f Hasura migration changes to change assigned_to to uuid not email. 2024-04-19 08:10:45 -07:00
Allan Carr
0b9f718106 IO-2667 Adjust Email messages
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-18 18:25:52 -07:00
Patrick Fic
57b3b3a9cd Remove hasura constraint. 2024-04-18 16:42:10 -07:00
Patrick Fic
6513432993 Merge branch 'feature/IO-2677-Tasks' into test-AIO 2024-04-18 16:28:20 -07:00
Patrick Fic
80a564d4b6 Add back hasura metadata for tasks. 2024-04-18 16:27:51 -07:00
Allan Carr
132ecffc40 Merged in feature/IO-2677-Tasks (pull request #1426)
IO-2667 Employee Tasks report

Approved-by: Dave Richer
2024-04-18 20:52:31 +00:00
Patrick Fic
cdf02a8eac Merged in release/AIO/2024-04-19 (pull request #1420)
Resolve CCC supplement with UNQ_SEQ.
2024-04-18 20:49:45 +00:00
Allan Carr
7d22dcab7c IO-2667 Employee Tasks report
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-18 13:42:42 -07:00
Allan Carr
45c943a78f Merged in feature/IO-2677-Tasks (pull request #1425)
IO-2667 Tasks Reports

Approved-by: Dave Richer
2024-04-18 18:25:27 +00:00
Allan Carr
cf82a8013f IO-2667 Tasks Reports
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-18 10:46:43 -07:00
Dave Richer
25d145d864 Merged in feature/IO-2760-IDS-for-headers (pull request #1423)
Add IDs in Header

Approved-by: Patrick Fic
2024-04-18 17:31:13 +00:00
Dave Richer
7822e3f90e Merged in feature/IO-2760-IDS-for-headers (pull request #1424)
Add Header IDS

Approved-by: Patrick Fic
2024-04-18 17:25:15 +00:00
Dave Richer
a4a612fbe4 - adjust moment import
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-18 13:17:13 -04:00
Dave Richer
07da472e82 Merged in feature/IO-2677-Tasks (pull request #1422)
- adjust moment import
2024-04-18 16:22:24 +00:00
Dave Richer
e9096632a4 - adjust moment import
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-18 12:15:01 -04:00
Dave Richer
6475a8bdce Merged in feature/IO-2677-Tasks (pull request #1421)
- Tasks Email Queue

Approved-by: Patrick Fic
2024-04-18 14:47:20 +00:00
Patrick Fic
535f92b7d2 Additional tasks changes. 2024-04-18 07:46:05 -07:00
Dave Richer
00b91616f5 - Tasks Email Queue
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-17 20:14:21 -04:00
Dave Richer
b8c34762ed - Tasks Email Queue
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-17 19:49:49 -04:00
Dave Richer
9f92347936 Merged in feature/IO-2667 -Tasks (pull request #1389)
Feature/IO-2667 Tasks

Approved-by: Patrick Fic
2024-04-17 20:29:28 +00:00
Dave Richer
b657a893ad - adjust col widths for remind_at and due_date (cosmetic)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-17 13:40:39 -04:00
Dave Richer
387670212a - Fix regression in last refactor.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-17 13:16:42 -04:00
Dave Richer
b8e42544ae - Fix regression in last refactor.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-17 13:09:50 -04:00
Dave Richer
bcea47c2c6 - Fix regression in last refactor.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-17 13:06:48 -04:00
Dave Richer
b38aaba56c - Merge in Test-AIO
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-17 12:45:19 -04:00
Patrick Fic
8099607d90 Merge branch 'release/AIO/2024-04-19' into test-AIO 2024-04-16 16:10:51 -07:00
Patrick Fic
2d9412e4e8 Merge branch 'release/2024-04-19' into release/AIO/2024-04-19 2024-04-16 16:10:33 -07:00
Patrick Fic
069d508528 Update cron trigger timing. 2024-04-16 16:09:54 -07:00
Patrick Fic
d68ce67e4f Merge branch 'release/AIO/2024-04-19' into test-AIO 2024-04-16 16:04:06 -07:00
Patrick Fic
a9d38e743f Merge branch 'release/2024-04-19' into release/AIO/2024-04-19 2024-04-16 16:03:06 -07:00
Patrick Fic
878e81dc8f Hasura schema changes for tasks. 2024-04-16 16:02:25 -07:00
Dave Richer
5ccf74f99c Merge remote-tracking branch 'origin/test-AIO' into feature/IO-2677-Tasks 2024-04-16 18:56:14 -04:00
Patrick Fic
6c823f4914 Merge branch 'release/AIO/2024-04-19' into test-AIO 2024-04-16 15:33:27 -07:00
Patrick Fic
de35155ffe Resolve CCC supplement with UNQ_SEQ. 2024-04-16 15:15:25 -07:00
Dave Richer
470eb19a2f - Final Push Prior to Live Testing
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-16 16:11:41 -04:00
Dave Richer
947bc33946 Merge remote-tracking branch 'origin/test-AIO' into feature/IO-2677-Tasks 2024-04-16 15:56:41 -04:00
Dave Richer
8bcaabfb57 Merged in release/2024-04-19 (pull request #1418)
Release/2024 04 19
2024-04-16 19:56:29 +00:00
Dave Richer
a1f7e7b755 Merged in feature/IO-2667-Migrations-For-Remind-At-Sent (pull request #1416)
- Migrations for remind_at_sent
2024-04-16 19:52:13 +00:00
Dave Richer
c8f8a86a98 - Migrations for remind_at_sent
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-16 15:45:56 -04:00
Dave Richer
bb205af019 - Progress
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-16 15:36:27 -04:00
Patrick Fic
443c6046f9 Merged in release/2024-04-19 (pull request #1415)
Resolve schedule header display.
2024-04-15 21:02:39 +00:00
Patrick Fic
1620b94a7b Merged in release/2024-04-19 (pull request #1414)
Resolve schedule header display.
2024-04-15 20:47:48 +00:00
Patrick Fic
81f94eac6c Resolve schedule header display. 2024-04-15 13:47:19 -07:00
Patrick Fic
ffada75d9e Merged in test-AIO (pull request #1412)
Test AIO

Approved-by: Dave Richer
2024-04-12 18:05:35 +00:00
Dave Richer
34d773bcd8 - Cosmetic requests
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-12 13:51:57 -04:00
Allan Carr
ec7509670d Merged in release/2024-04-12 (pull request #1413)
Release/2024 04 12

Approved-by: Dave Richer
2024-04-12 17:08:21 +00:00
Dave Richer
eceac11af2 - Cleanup
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-12 12:58:24 -04:00
Dave Richer
dd64598850 - Dynamic Date Time Presets for Tasks
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-12 12:29:04 -04:00
Dave Richer
650ace6be6 - cleanup
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-12 11:53:03 -04:00
Dave Richer
1c71a5c5e0 - small bug fix on modifying task by key via url
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-12 11:43:21 -04:00
Dave Richer
9c699a634b - optimization
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-12 11:35:53 -04:00
Dave Richer
a47d17bbf5 - priority
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-12 11:21:08 -04:00
Dave Richer
164f67d6ce - Move refresh button at Allans behest
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-11 23:48:48 -04:00
Dave Richer
465b9e7177 - Simplified solution in previous commit
- Additional regression testing fixes

Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-11 23:47:02 -04:00
Dave Richer
f5a914c318 - Merge in test-AIO
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-11 22:21:53 -04:00
Dave Richer
de486d2e73 - Fix console warn, add two missing try catch blocks
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-11 22:15:48 -04:00
Dave Richer
d7f946ec2a - Fix an issue with not having an upper context to store intermediate values. Thus allowing me to fix the top level 'Add Task'
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-11 21:01:38 -04:00
Dave Richer
4d1480bb61 - allan found bug
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-11 19:12:06 -04:00
Patrick Fic
25a49473f9 Merge branch 'release/AIO/2024-04-12' into test-AIO 2024-04-11 15:22:26 -07:00
Patrick Fic
8e86e7fba5 Resolve linting errors from merge. 2024-04-11 15:22:05 -07:00
Patrick Fic
f9b380a0d4 Merged in release/AIO/2024-04-12 (pull request #1411)
Release/AIO/2024 04 12
2024-04-11 22:16:32 +00:00
Patrick Fic
f664a56b16 Merge branch 'release/2024-04-12' into release/AIO/2024-04-12 2024-04-11 15:15:54 -07:00
Allan Carr
0acfd3c4b1 Merged in feature/IO-2609-Calendar-BPT-HRS (pull request #1409)
IO-2609 Fix Spelling Mistake in object name
2024-04-11 21:39:06 +00:00
Allan Carr
bfc4cb1ad9 IO-2609 Fix Spelling Mistake in object name
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-11 14:38:08 -07:00
Patrick Fic
66dd1a9a2b Merge branch 'feature/AIO/promanager' into test-AIO 2024-04-11 14:29:09 -07:00
Allan Carr
1f42be2e54 Merged in feature/IO-2753-Qty-Parts-Order-Modal (pull request #1405)
IO-2753 Parts Order/Return Quantity restrict to above 0
2024-04-11 21:22:37 +00:00
Allan Carr
30d344af6b Merged in feature/IO-2752-BO-ETA-Jobline-Expander (pull request #1404)
IO-2752 BO ETA Jobline Expander
2024-04-11 21:22:19 +00:00
Allan Carr
3ca989fd8c Merged in release/2024-04-05 (pull request #1401)
Release/2024 04 05

Approved-by: Dave Richer
2024-04-11 21:22:03 +00:00
Patrick Fic
73c38b3ae4 Merged in feature/IO-2458-RO-Closer (pull request #1406)
Feature/IO-2458 RO Closer
2024-04-11 20:25:25 +00:00
Patrick Fic
08749b0c92 Add audit log to job close with bypass. 2024-04-11 13:24:45 -07:00
Patrick Fic
ea73af371e Minor bug fixes. 2024-04-11 13:07:43 -07:00
Allan Carr
ce2086a480 IO-2753 Parts Order/Return Quantity restrict to above 0
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-11 12:43:14 -07:00
Allan Carr
63f7106d2b IO-2752 BO ETA Jobline Expander
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-11 11:55:55 -07:00
Dave Richer
8cce6ea6e3 - additional cleanup and validation / fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-11 14:44:21 -04:00
Dave Richer
77c486b4c9 - additional cleanup and validation / fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-11 14:25:16 -04:00
Dave Richer
7c8f276bb0 - small regression fix
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-11 12:56:41 -04:00
Patrick Fic
e991586254 Merged in feature/IO-2458-RO-Closer (pull request #1403)
Bug fixes and formatting for RO guard.
2024-04-11 16:54:35 +00:00
Dave Richer
d61ab796a7 - small regression fix
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-11 12:52:53 -04:00
Dave Richer
e634369975 - small regression fix
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-11 12:50:24 -04:00
Patrick Fic
453236cf3a Bug fixes and formatting for RO guard. 2024-04-11 09:49:00 -07:00
Dave Richer
3530476b07 -
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-11 12:27:56 -04:00
Allan Carr
e75d8d1874 Merged in feature/IO-2609-Calendar-BPT-HRS (pull request #1402)
IO-2609 Body & Refinish Times included in Calendar View

Approved-by: Dave Richer
2024-04-11 16:27:02 +00:00
Patrick Fic
719fa6a67d ProMan totals changes. 2024-04-11 09:23:20 -07:00
Dave Richer
148cd43c5d - Fix a bug where a relationship deeplink would not pop if the user was already on the jobdetails page
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-10 16:57:37 -04:00
Dave Richer
3d753a2d19 - PR Change Requests (Progress)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-10 16:48:06 -04:00
Dave Richer
693d02de87 - PR Change Requests
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-09 16:53:37 -04:00
Dave Richer
80b4ef3ae8 - PR Change Requests
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-09 16:49:03 -04:00
Dave Richer
6b9269eb2d - Progress
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-09 16:32:26 -04:00
Dave Richer
15c9529885 Merge remote-tracking branch 'origin/test-AIO' into feature/IO-2677-Tasks 2024-04-09 15:24:04 -04:00
Patrick Fic
512cd70d13 Merge branch 'feature/IO-2458-RO-Closer' into test-AIO 2024-04-09 12:23:49 -07:00
Patrick Fic
e5599ff4c4 Update prettier config and add as dev dependency. 2024-04-09 12:23:10 -07:00
Dave Richer
32041ee3fc - Progress
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-09 15:03:02 -04:00
Allan Carr
07bf84ed69 IO-2609 Body & Refinish Times included in Calendar View
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-09 11:55:20 -07:00
Dave Richer
0c0449aa17 - Hasura and PrettierRC
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-09 12:54:58 -04:00
Dave Richer
26cb527d37 - Add additional field to tasks table
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-09 11:18:43 -04:00
Dave Richer
33c282051b Fix Formatting issues
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-08 22:25:07 -04:00
Dave Richer
df0f8ef9dc Progress
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-08 22:07:14 -04:00
Dave Richer
e23f13a1b3 - Merge Test-AIO
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-08 20:25:08 -04:00
Patrick Fic
f5b8bf1d74 Resolve unnecessary import. 2024-04-08 14:32:02 -07:00
Patrick Fic
61766017ea Resolve supplement import for CCC. 2024-04-08 14:28:58 -07:00
Patrick Fic
706984a53b Resolve CCC supplement import. 2024-04-08 14:27:33 -07:00
Patrick Fic
e137feca20 Resolve ESLint Warnings 2024-04-08 14:20:34 -07:00
Patrick Fic
9e66d7c929 Merge branch 'release/AIO/2024-04-05' into test-AIO 2024-04-08 13:56:10 -07:00
Patrick Fic
9605bf5c21 Merge branch 'release/2024-04-05' into release/AIO/2024-04-05 2024-04-08 13:55:43 -07:00
Allan Carr
c2cc7b1e9e Merged in feature/IO-2731-Payment-Edit (pull request #1399)
IO-2731 Payment Edit
2024-04-08 19:56:43 +00:00
Allan Carr
fe55eccbf9 Merged in feature/IO-2749-Parts-Return-Pass-Jobs-Data (pull request #1398)
IO-2749 Pass Jobs data from Parts Return to Parts Order Modal
2024-04-08 19:55:49 +00:00
Patrick Fic
47e17dc78a Merge branch 'feature/IO-2727-resolve-payment-refetch' into release/AIO/2024-04-05 2024-04-08 12:43:15 -07:00
Allan Carr
88a71dd647 IO-2731 Payment Edit
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-08 12:23:04 -07:00
Patrick Fic
ce0b3a8635 Merge branch 'feature/IO-2727-resolve-payment-refetch' into test-AIO 2024-04-08 11:08:57 -07:00
Dave Richer
bd3d86a6dd - Tasks Audit Trail Additions
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-08 13:26:37 -04:00
Allan Carr
c3b395c99e IO-2749 Pass Jobs data from Parts Return to Parts Order Modal
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-08 09:53:20 -07:00
Dave Richer
eb4e5d9576 Merge remote-tracking branch 'origin/test-AIO' into feature/IO-2677-Tasks 2024-04-08 12:31:00 -04:00
Allan Carr
19a03ec080 Merge branch 'release/2024-04-05' into test-AIO
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>

# Conflicts:
#	client/src/components/print-center-jobs-labels/print-center-jobs-labels.component.jsx
2024-04-05 12:21:35 -07:00
Allan Carr
33d5d9b462 Merged in feature/IO-2568-Payment-Modal-Button-Spacing (pull request #1396)
IO-2568 Button Padding in Print Center Label Modal
2024-04-05 19:03:09 +00:00
Allan Carr
b5a371d0cf IO-2568 Button Padding in Print Center Label Modal
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-05 12:01:26 -07:00
Dave Richer
b61fd17879 - add smart refetch to mark-export and reexport
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-05 14:54:16 -04:00
Dave Richer
6722f8b1e5 - reversion
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-05 13:41:37 -04:00
Dave Richer
69ff75157d - reversion
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-05 13:35:44 -04:00
Dave Richer
7fad968ad2 - Cleanups
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-05 13:24:38 -04:00
Allan Carr
7c619f5439 Merged in release/2024-04-05 (pull request #1395)
IO-2750 Missing Mutation return fields
2024-04-05 16:00:52 +00:00
Allan Carr
3b35f38ad5 Merged in feature/IO-2750-Missing-Jobline-Fields (pull request #1393)
IO-2750 Missing Mutation return fields
2024-04-05 15:55:13 +00:00
Dave Richer
096017c3d6 Merge remote-tracking branch 'origin/test-AIO' into feature/IO-2677-Tasks 2024-04-05 11:22:13 -04:00
Dave Richer
4ff2ab1bc8 - Remove actions params in Payment modal, which was causing issues.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-05 11:20:16 -04:00
Dave Richer
ef698529d7 - Changes required by ESLint.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-05 10:21:31 -04:00
Dave Richer
167d5bd89a - Changes required by ESLint.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-05 10:08:56 -04:00
Dave Richer
c328a55453 Merge remote-tracking branch 'origin/test-AIO' into feature/IO-2677-Tasks 2024-04-05 09:59:57 -04:00
Dave Richer
83a976e98f - Add ESLint back to Vite
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-05 09:59:30 -04:00
Dave Richer
d004133ad6 - Merge AIO
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-05 09:35:35 -04:00
Allan Carr
1f5c1b9658 IO-2750 Missing Mutation return fields
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-04-04 17:00:30 -07:00
Patrick Fic
56dfd174dd Merge branch 'hotfix/AIO/2024-04-04' into master-AIO 2024-04-04 11:32:09 -07:00
Patrick Fic
532cd4937b Merge branch 'hotfix/AIO/2024-04-04' into master-AIO 2024-04-04 10:51:57 -07:00
Dave Richer
91bc73baf2 Merge remote-tracking branch 'origin/test-AIO' into feature/IO-2677-Tasks 2024-04-03 14:18:22 -04:00
Dave Richer
ab031c01de - reapply proper prettier formatting.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-03 14:09:09 -04:00
Dave Richer
e51f72ff98 - its sign, not sing :D
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-03 13:47:40 -04:00
Dave Richer
3eb010285d - Update date picker presets
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-03 13:01:56 -04:00
Dave Richer
2b172f9999 - fix
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-02 21:54:07 -04:00
Dave Richer
0803f5af35 - Progress Commit (Emailzzz)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-02 21:53:07 -04:00
Dave Richer
69ac2f0a6c - Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-02 17:33:54 -04:00
Dave Richer
0c842e0e15 - Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-02 17:20:00 -04:00
Dave Richer
d94678d4f4 - Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-02 15:20:23 -04:00
Dave Richer
90814f41a2 - Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-02 15:01:51 -04:00
Dave Richer
282dbd0913 - Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-01 20:20:01 -04:00
Dave Richer
1343b68cc6 - Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-01 16:34:31 -04:00
Dave Richer
ae07f71e76 - Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-04-01 14:45:55 -04:00
Dave Richer
54dc9c8587 - merge AIO
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-28 15:03:43 -04:00
Dave Richer
9f9fa3b952 - Progress commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-28 15:02:06 -04:00
Dave Richer
cc7c98336f - Progress commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-28 13:03:09 -04:00
Dave Richer
dc22b96bed - Progress commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-28 12:30:14 -04:00
Dave Richer
ae9e9f4b72 - Progress commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-27 17:00:07 -04:00
Dave Richer
301c680bff - Progress commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-26 21:36:27 -04:00
Dave Richer
595159f24d - Progress commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-26 19:59:30 -04:00
Dave Richer
3bf1ec25c1 merge aio 2024-03-26 10:09:29 -04:00
Dave Richer
40c801592d - Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-22 12:20:56 -04:00
Dave Richer
9012e4deec - Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-21 13:42:08 -04:00
Dave Richer
ab2323e5c1 - Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-20 22:19:52 -04:00
Dave Richer
f31ae9ac6d - Progress Commit
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-03-20 18:18:24 -04:00
Dave Richer
27c24619c3 Merge remote-tracking branch 'origin/master-AIO' into feature/IO-2677-Tasks 2024-03-20 15:11:26 -04:00
Dave Richer
38aef71269 Progress 2024-03-20 15:11:08 -04:00
149 changed files with 10211 additions and 1729 deletions

View File

@@ -9,10 +9,10 @@ const config = {
arrowParens: "always",
jsxSingleQuote: false,
bracketSameLine: false,
endOfLine: "lf",
importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
importOrderSeparation: true,
importOrderSortSpecifiers: true
endOfLine: "lf"
// importOrder: ["^@core/(.*)$", "^@server/(.*)$", "^@ui/(.*)$", "^[./]"],
// importOrderSeparation: true,
// importOrderSortSpecifiers: true
};
module.exports = config;

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,8 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
VITE_APP_GA_CODE=231099835
VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
# VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
VITE_APP_CLOUDINARY_API_KEY=957865933348715

View File

@@ -1,7 +1,8 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
VITE_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
VITE_APP_GA_CODE=231099835
VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
# VITE_APP_FIREBASE_CONFIG={ "apiKey": "AIzaSyAuLQR9SV5LsVxjU8wh9hvFLdhcAHU6cxE", "authDomain": "rome-prod-1.firebaseapp.com", "projectId": "rome-prod-1", "storageBucket": "rome-prod-1.appspot.com", "messagingSenderId": "147786367145", "appId": "1:147786367145:web:9d4cba68071c3f29a8a9b8", "measurementId": "G-G8Z9DRHTZS"}
VITE_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
VITE_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
VITE_APP_CLOUDINARY_API_KEY=957865933348715

8
client/.eslintrc Normal file
View File

@@ -0,0 +1,8 @@
{
"extends": [
"react-app"
],
"rules": {
"no-useless-rename": "off"
}
}

View File

@@ -96,6 +96,8 @@
"browserslist-to-esbuild": "^2.1.1",
"cross-env": "^7.0.3",
"cypress": "^13.6.6",
"eslint": "^8.57.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-cypress": "^2.15.1",
"memfs": "^4.6.0",
"os-browserify": "^0.3.0",
@@ -104,6 +106,7 @@
"source-map-explorer": "^2.5.3",
"vite": "^5.0.11",
"vite-plugin-babel": "^1.2.0",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-legacy": "^2.1.0",
"vite-plugin-node-polyfills": "^0.19.0",
"vite-plugin-pwa": "^0.19.0",
@@ -11934,7 +11937,8 @@
},
"node_modules/eslint": {
"version": "8.57.0",
"license": "MIT",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
"integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
@@ -11987,7 +11991,8 @@
},
"node_modules/eslint-config-react-app": {
"version": "7.0.1",
"license": "MIT",
"resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz",
"integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==",
"dependencies": {
"@babel/core": "^7.16.0",
"@babel/eslint-parser": "^7.16.3",
@@ -26439,6 +26444,36 @@
"vite": ">=5.0.0"
}
},
"node_modules/vite-plugin-eslint": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/vite-plugin-eslint/-/vite-plugin-eslint-1.8.1.tgz",
"integrity": "sha512-PqdMf3Y2fLO9FsNPmMX+//2BF5SF8nEWspZdgl4kSt7UvHDRHVVfHvxsD7ULYzZrJDGRxR81Nq7TOFgwMnUang==",
"dev": true,
"dependencies": {
"@rollup/pluginutils": "^4.2.1",
"@types/eslint": "^8.4.5",
"rollup": "^2.77.2"
},
"peerDependencies": {
"eslint": ">=7",
"vite": ">=2"
}
},
"node_modules/vite-plugin-eslint/node_modules/rollup": {
"version": "2.79.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.79.1.tgz",
"integrity": "sha512-uKxbd0IhMZOhjAiD5oAFp7BqvkA4Dv47qpOCtaNvng4HBwdbWtdOh8f5nZNuk2rp51PMGk3bzfWu5oayNEuYnw==",
"dev": true,
"bin": {
"rollup": "dist/bin/rollup"
},
"engines": {
"node": ">=10.0.0"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/vite-plugin-legacy": {
"version": "2.1.0",
"dev": true,

View File

@@ -140,6 +140,8 @@
"browserslist-to-esbuild": "^2.1.1",
"cross-env": "^7.0.3",
"cypress": "^13.6.6",
"eslint": "^8.57.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-cypress": "^2.15.1",
"memfs": "^4.6.0",
"os-browserify": "^0.3.0",
@@ -148,6 +150,7 @@
"source-map-explorer": "^2.5.3",
"vite": "^5.0.11",
"vite-plugin-babel": "^1.2.0",
"vite-plugin-eslint": "^1.8.1",
"vite-plugin-legacy": "^2.1.0",
"vite-plugin-node-polyfills": "^0.19.0",
"vite-plugin-pwa": "^0.19.0",

View File

@@ -0,0 +1,56 @@
// Scripts for firebase and firebase messaging
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-app.js");
importScripts("https://www.gstatic.com/firebasejs/8.2.0/firebase-messaging.js");
// Initialize the Firebase app in the service worker by passing the generated config
let firebaseConfig;
switch (this.location.hostname) {
case "localhost":
firebaseConfig = {
apiKey: "AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc",
authDomain: "imex-dev.firebaseapp.com",
databaseURL: "https://imex-dev.firebaseio.com",
projectId: "imex-dev",
storageBucket: "imex-dev.appspot.com",
messagingSenderId: "759548147434",
appId: "1:759548147434:web:e8239868a48ceb36700993",
measurementId: "G-K5XRBVVB4S",
};
break;
case "test.imex.online":
firebaseConfig = {
apiKey: "AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c",
authDomain: "imex-test.firebaseapp.com",
projectId: "imex-test",
storageBucket: "imex-test.appspot.com",
messagingSenderId: "991923618608",
appId: "1:991923618608:web:633437569cdad78299bef5",
// measurementId: "${config.measurementId}",
};
break;
case "imex.online":
default:
firebaseConfig = {
apiKey: "AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU",
authDomain: "imex-prod.firebaseapp.com",
databaseURL: "https://imex-prod.firebaseio.com",
projectId: "imex-prod",
storageBucket: "imex-prod.appspot.com",
messagingSenderId: "253497221485",
appId: "1:253497221485:web:3c81c483b94db84b227a64",
measurementId: "G-NTWBKG2L0M",
};
}
firebase.initializeApp(firebaseConfig);
// Retrieve firebase messaging
const messaging = firebase.messaging();
messaging.onBackgroundMessage(function (payload) {
// Customize notification here
const channel = new BroadcastChannel("imex-sw-messages");
channel.postMessage(payload);
//self.registration.showNotification(notificationTitle, notificationOptions);
});

View File

@@ -150,7 +150,7 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
<ProductFruits
workspaceCode={InstanceRenderMgr({
imex: null,
rome: null,
rome: "9BkbEseqNqxw8jUH",
promanager: "aoJoEifvezYI0Z0P"
})}
debug

View File

@@ -34,6 +34,7 @@ export function BillDetailEditReturn({ setPartsOrderContext, insertAuditTrail, b
actions: {},
context: {
jobId: data.bills_by_pk.jobid,
job: data.bills_by_pk.job,
vendorId: data.bills_by_pk.vendorid,
returnFromBill: data.bills_by_pk.id,
invoiceNumber: data.bills_by_pk.invoice_number,

View File

@@ -25,31 +25,27 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
);
}}
notFoundContent={"Removed."}
{...restProps}
>
<Select.Option key={null} value={"noline"} cost={0} line_desc={""}>
{t("billlines.labels.other")}
</Select.Option>
{options
? options.map((item) => (
<Option
disabled={allowRemoved ? false : item.removed}
key={item.id}
value={item.id}
cost={item.act_price ? item.act_price : 0}
part_type={item.part_type}
line_desc={item.line_desc}
part_qty={item.part_qty}
oem_partno={item.oem_partno}
alt_partno={item.alt_partno}
act_price={item.act_price}
style={{
...(item.removed ? { textDecoration: "line-through" } : {})
}}
name={`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
>
options={[
{ value: "noline", label: t("billlines.labels.other"), name: t("billlines.labels.other") },
...options.map((item) => ({
disabled: allowRemoved ? false : item.removed,
key: item.id,
value: item.id,
cost: item.act_price ? item.act_price : 0,
part_type: item.part_type,
line_desc: item.line_desc,
part_qty: item.part_qty,
oem_partno: item.oem_partno,
alt_partno: item.alt_partno,
act_price: item.act_price,
style: {
...(item.removed ? { textDecoration: "line-through" } : {})
},
name: `${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim(),
label: (
<>
<span>
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
@@ -60,14 +56,15 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
<span style={{ float: "right", paddingleft: "1rem" }}>{`${item.mod_lb_hrs} units`}</span>
)
})}
<span style={{ float: "right", paddingleft: "1rem" }}>
{item.act_price ? `$${item.act_price && item.act_price.toFixed(2)}` : ``}
</span>
</Option>
))
: null}
</Select>
</>
)
}))
]}
{...restProps}
></Select>
);
};
export default forwardRef(BillLineSearchSelect);

View File

@@ -14,6 +14,7 @@ import { TemplateList } from "../../utils/TemplateConstants";
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import { FaTasks } from "react-icons/fa";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -21,9 +22,21 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
setReconciliationContext: (context) => dispatch(setModalContext({ context: context, modal: "reconciliation" }))
setBillEnterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "billEnter"
})
),
setReconciliationContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "reconciliation"
})
),
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
});
export function BillsListTableComponent({
@@ -32,9 +45,9 @@ export function BillsListTableComponent({
job,
billsQuery,
handleOnRowClick,
setPartsOrderContext,
setBillEnterContext,
setReconciliationContext
setReconciliationContext,
setTaskUpsertContext
}) {
const { t } = useTranslation();
@@ -48,6 +61,7 @@ export function BillsListTableComponent({
const Templates = TemplateList("bill");
const bills = billsQuery.data ? billsQuery.data.bills : [];
const { refetch } = billsQuery;
const recordActions = (record, showView = false) => (
<Space wrap>
{showView && (
@@ -55,9 +69,22 @@ export function BillsListTableComponent({
<EditFilled />
</Button>
)}
<Button
title={t("tasks.buttons.create")}
onClick={() => {
setTaskUpsertContext({
context: {
jobid: job.id,
billid: record.id
}
});
}}
>
<FaTasks />
</Button>
<BillDeleteButton bill={record} jobid={job.id} />
<BillDetailEditReturnComponent
data={{ bills_by_pk: { ...record, jobid: job.id } }}
data={{ bills_by_pk: { ...record, jobid: job.id, job: job } }}
disabled={record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid || jobRO}
/>

View File

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

View File

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

View File

@@ -61,7 +61,11 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
{
text: t("courtesycars.status.leasereturn"),
value: "courtesycars.status.leasereturn"
}
},
{
text: t("courtesycars.status.unavailable"),
value: "courtesycars.status.unavailable",
},
],
onFilter: (value, record) => record.status === value,
sortOrder: state.sortedInfo.columnKey === "status" && state.sortedInfo.order,

View File

@@ -0,0 +1,37 @@
import { Select, Space, Tag } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
const { Option } = Select;
//To be used as a form element only.
const EmployeeSearchSelectEmail = ({ options, ...props }) => {
const { t } = useTranslation();
return (
<Select
showSearch
// value={option}
style={{
width: 400
}}
optionFilterProp="search"
{...props}
>
{options
? options.map((o) => (
<Option key={o.id} value={o.user_email} search={`${o.employee_number} ${o.first_name} ${o.last_name}`}>
<Space>
{`${o.employee_number} ${o.first_name} ${o.last_name}`}
<Tag color="green">
{o.flat_rate ? t("timetickets.labels.flat_rate") : t("timetickets.labels.straight_time")}
</Tag>
</Space>
</Option>
))
: null}
</Select>
);
};
export default EmployeeSearchSelectEmail;

View File

@@ -0,0 +1,48 @@
import { DatePicker } from "antd";
import dayjs from "../../utils/day.js";
import React, { useRef } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors.js";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(FormDateTimePickerEnhanced);
const dateFormat = "MM/DD/YYYY h:mm a";
export function FormDateTimePickerEnhanced({
bodyshop,
value,
onBlur,
onlyFuture,
onlyToday,
isDateOnly = true,
...restProps
}) {
const ref = useRef();
return (
<div>
<DatePicker
ref={ref}
value={value ? dayjs(value) : null}
format={dateFormat}
onBlur={onBlur}
showToday={false}
disabledDate={(d) => {
if (onlyToday) {
return !dayjs().isSame(d, "day");
} else if (onlyFuture) {
return dayjs().subtract(1, "day").isAfter(d);
}
}}
{...restProps}
/>
</div>
);
}

View File

@@ -1,7 +1,7 @@
import React, { forwardRef } from "react";
//import DatePicker from "react-datepicker";
//import "react-datepicker/src/stylesheets/datepicker.scss";
import { TimePicker } from "antd";
import { Space, TimePicker } from "antd";
import dayjs from "../../utils/day";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
//To be used as a form element only.
@@ -14,7 +14,7 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps
// };
return (
<div id={id}>
<Space direction="vertical" style={{ width: "100%" }} id={id}>
<FormDatePicker
{...restProps}
{...(onlyFuture && {
@@ -39,7 +39,7 @@ const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, ...restProps
format="hh:mm a"
{...restProps}
/>
</div>
</Space>
);
};

View File

@@ -3,14 +3,13 @@ import axios from "axios";
import _ from "lodash";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import { Link } from "react-router-dom";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearchOs() {
const { t } = useTranslation();
const history = useNavigate();
const [loading, setLoading] = useState(false);
const [data, setData] = useState(false);
@@ -178,15 +177,7 @@ export default function GlobalSearchOs() {
};
return (
<AutoComplete
options={data}
onSearch={handleSearch}
defaultActiveFirstOption
onSelect={(val, opt) => {
history(opt.label.props.to);
}}
onClear={() => setData([])}
>
<AutoComplete options={data} onSearch={handleSearch} defaultActiveFirstOption onClear={() => setData([])}>
<Input.Search
size="large"
placeholder={t("general.labels.globalsearch")}

View File

@@ -3,7 +3,7 @@ import { AutoComplete, Divider, Input, Space } from "antd";
import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next";
import { Link, useNavigate } from "react-router-dom";
import { Link } from "react-router-dom";
import { GLOBAL_SEARCH_QUERY } from "../../graphql/search.queries";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import AlertComponent from "../alert/alert.component";
@@ -12,7 +12,6 @@ import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.compon
export default function GlobalSearch() {
const { t } = useTranslation();
const history = useNavigate();
const [callSearch, { loading, error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
const executeSearch = (v) => {
@@ -157,14 +156,7 @@ export default function GlobalSearch() {
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<AutoComplete
options={options}
onSearch={handleSearch}
defaultActiveFirstOption
onSelect={(val, opt) => {
history(opt.label.props.to);
}}
>
<AutoComplete options={options} onSearch={handleSearch} defaultActiveFirstOption>
<Input.Search
size="large"
placeholder={t("general.labels.globalsearch")}

View File

@@ -17,6 +17,7 @@ import Icon, {
LineChartOutlined,
PaperClipOutlined,
PhoneOutlined,
PlusCircleOutlined,
QuestionCircleFilled,
ScheduleOutlined,
SettingOutlined,
@@ -30,7 +31,8 @@ import { Layout, Menu, Switch, Tooltip } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { BsKanban } from "react-icons/bs";
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar } from "react-icons/fa";
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } from "react-icons/fa";
import { FiLogOut } from "react-icons/fi";
import { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
import { IoBusinessOutline } from "react-icons/io5";
import { RiSurveyLine } from "react-icons/ri";
@@ -41,7 +43,6 @@ import { selectRecentItems, selectSelectedHeader } from "../../redux/application
import { setModalContext } from "../../redux/modals/modals.actions";
import { signOutStart } from "../../redux/user/user.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { FiLogOut } from "react-icons/fi";
import { checkBeta, handleBeta, setBeta } from "../../utils/betaHandler";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
@@ -54,12 +55,43 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })),
setBillEnterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "billEnter"
})
),
setTimeTicketContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "timeTicket"
})
),
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
setReportCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "reportCenter" })),
setReportCenterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "reportCenter"
})
),
signOutStart: () => dispatch(signOutStart()),
setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" }))
setCardPaymentContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "cardPayment"
})
),
setTaskUpsertContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "taskUpsert"
})
)
});
function Header({
@@ -73,7 +105,8 @@ function Header({
setPaymentContext,
setReportCenterContext,
recentItems,
setCardPaymentContext
setCardPaymentContext,
setTaskUpsertContext
}) {
const {
treatments: { ImEXPay, DmsAp, Simple_Inventory }
@@ -108,11 +141,13 @@ function Header({
accountingChildren.push(
{
key: "bills",
id: "header-accounting-bills",
icon: <Icon component={FaFileInvoiceDollar} />,
label: <Link to="/manage/bills">{t("menus.header.bills")}</Link>
},
{
key: "enterbills",
id: "header-accounting-enterbills",
icon: <Icon component={GiPayMoney} />,
label: t("menus.header.enterbills"),
onClick: () => {
@@ -132,6 +167,7 @@ function Header({
},
{
key: "inventory",
id: "header-accounting-inventory",
icon: <Icon component={FaFileInvoiceDollar} />,
label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link>
}
@@ -150,11 +186,13 @@ function Header({
},
{
key: "allpayments",
id: "header-accounting-allpayments",
icon: <BankFilled />,
label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
},
{
key: "enterpayments",
id: "header-accounting-enterpayments",
icon: <Icon component={FaCreditCard} />,
label: t("menus.header.enterpayment"),
onClick: () => {
@@ -170,6 +208,7 @@ function Header({
if (ImEXPay.treatment === "on") {
accountingChildren.push({
key: "entercardpayments",
id: "header-accounting-entercardpayments",
icon: <Icon component={FaCreditCard} />,
label: t("menus.header.entercardpayment"),
onClick: () => {
@@ -194,6 +233,7 @@ function Header({
},
{
key: "timetickets",
id: "header-accounting-timetickets",
icon: <FieldTimeOutlined />,
label: <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>
}
@@ -202,6 +242,7 @@ function Header({
if (bodyshop?.md_tasks_presets?.use_approvals) {
accountingChildren.push({
key: "ttapprovals",
id: "header-accounting-ttapprovals",
icon: <FieldTimeOutlined />,
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
});
@@ -211,6 +252,7 @@ function Header({
key: "entertimetickets",
icon: <Icon component={GiPlayerTime} />,
label: t("menus.header.entertimeticket"),
id: "header-accounting-entertimetickets",
onClick: () => {
setTimeTicketContext({
actions: {},
@@ -231,6 +273,7 @@ function Header({
const accountingExportChildren = [
{
key: "receivables",
id: "header-accounting-receivables",
label: <Link to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link>
}
];
@@ -238,6 +281,7 @@ function Header({
if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) || DmsAp.treatment === "on") {
accountingExportChildren.push({
key: "payables",
id: "header-accounting-payables",
label: <Link to="/manage/accounting/payables">{t("menus.header.accounting-payables")}</Link>
});
}
@@ -245,6 +289,7 @@ function Header({
if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber))) {
accountingExportChildren.push({
key: "payments",
id: "header-accounting-payments",
label: <Link to="/manage/accounting/payments">{t("menus.header.accounting-payments")}</Link>
});
}
@@ -255,6 +300,7 @@ function Header({
},
{
key: "exportlogs",
id: "header-accounting-exportlogs",
label: <Link to="/manage/accounting/exportlogs">{t("menus.header.export-logs")}</Link>
}
);
@@ -268,6 +314,7 @@ function Header({
) {
accountingChildren.push({
key: "accountingexport",
id: "header-accounting-export",
icon: <ExportOutlined />,
label: t("menus.header.export"),
children: accountingExportChildren
@@ -278,10 +325,12 @@ function Header({
{
key: "home",
icon: <HomeFilled />,
id: "header-home",
label: <Link to="/manage/">{t("menus.header.home")}</Link>
},
{
key: "schedule",
id: "header-schedule",
icon: <Icon component={FaCalendarAlt} />,
label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
},
@@ -293,16 +342,19 @@ function Header({
children: [
{
key: "activejobs",
id: "header-active-jobs",
icon: <FileFilled />,
label: <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
},
{
key: "readyjobs",
id: "header-ready-jobs",
icon: <CheckCircleOutlined />,
label: <Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link>
},
{
key: "parts-queue",
id: "header-parts-queue",
icon: <ToolFilled />,
label: <Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
},
@@ -314,6 +366,7 @@ function Header({
},
{
key: "newjob",
id: "header-new-job",
icon: <FileAddOutlined />,
label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
},
@@ -322,6 +375,7 @@ function Header({
},
{
key: "alljobs",
id: "header-all-jobs",
icon: <UnorderedListOutlined />,
label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
},
@@ -330,6 +384,7 @@ function Header({
},
{
key: "productionlist",
id: "header-production-list",
icon: <ScheduleOutlined />,
label: <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link>
},
@@ -341,6 +396,7 @@ function Header({
? [
{
key: "productionboard",
id: "header-production-board",
icon: <Icon component={BsKanban} />,
label: <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link>
}
@@ -358,6 +414,7 @@ function Header({
},
{
key: "scoreboard",
id: "header-scoreboard",
icon: <LineChartOutlined />,
label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
}
@@ -368,15 +425,18 @@ function Header({
{
key: "customers",
icon: <UserOutlined />,
id: "header-customers",
label: t("menus.header.customers"),
children: [
{
key: "owners",
id: "header-owners",
icon: <TeamOutlined />,
label: <Link to="/manage/owners">{t("menus.header.owners")}</Link>
},
{
key: "vehicles",
id: "header-vehicles",
icon: <CarFilled />,
label: <Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link>
}
@@ -390,21 +450,25 @@ function Header({
? [
{
key: "ccs",
id: "header-css",
icon: <CarFilled />,
label: t("menus.header.courtesycars"),
children: [
{
key: "courtesycarsall",
id: "header-courtesycars-all",
icon: <CarFilled />,
label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link>
},
{
key: "contracts",
id: "header-contracts",
icon: <FileFilled />,
label: <Link to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
},
{
key: "newcontract",
id: "header-newcontract",
icon: <FileAddFilled />,
label: <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
}
@@ -417,6 +481,7 @@ function Header({
? [
{
key: "accounting",
id: "header-accounting",
icon: <DollarCircleFilled />,
label: t("menus.header.accounting"),
children: accountingChildren
@@ -425,6 +490,7 @@ function Header({
: []),
{
key: "phonebook",
id: "header-phonebook",
icon: <PhoneOutlined />,
label: <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
},
@@ -436,28 +502,62 @@ function Header({
? [
{
key: "temporarydocs",
id: "header-temporarydocs",
icon: <PaperClipOutlined />,
label: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link>
}
]
: []),
{
key: "tasks",
id: "tasks",
icon: <FaTasks />,
label: t("menus.header.tasks"),
children: [
{
key: "createTask",
icon: <PlusCircleOutlined />,
label: t("menus.header.create_task"),
onClick: () => {
setTaskUpsertContext({
actions: {},
context: {}
});
}
},
{
key: "mytasks",
icon: <FaTasks />,
label: <Link to="/manage/tasks/mytasks">{t("menus.header.my_tasks")}</Link>
},
{
key: "all_tasks",
icon: <FaTasks />,
label: <Link to="/manage/tasks/alltasks">{t("menus.header.all_tasks")}</Link>
}
]
},
{
key: "shopsubmenu",
id: "header-shopsubmenu",
icon: <SettingOutlined />,
label: t("menus.header.shop"),
children: [
{
key: "shop",
id: "header-shop",
icon: <Icon component={GiSettingsKnobs} />,
label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link>
},
{
key: "dashboard",
id: "header-dashboard",
icon: <DashboardFilled />,
label: <Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
},
{
key: "reportcenter",
id: "header-reportcenter",
icon: <BarChartOutlined />,
label: t("menus.header.reportcenter"),
onClick: () => {
@@ -469,6 +569,7 @@ function Header({
},
{
key: "shop-vendors",
id: "header-shop-vendors",
icon: <Icon component={IoBusinessOutline} />,
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
},
@@ -480,6 +581,7 @@ function Header({
? [
{
key: "shop-csi",
id: "header-shop-csi",
icon: <Icon component={RiSurveyLine} />,
label: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
}
@@ -493,6 +595,7 @@ function Header({
children: [
{
key: "signout",
id: "header-signout",
icon: <Icon component={FiLogOut} />,
danger: true,
label: t("user.actions.signout"),
@@ -500,6 +603,7 @@ function Header({
},
{
key: "help",
id: "header-help",
icon: <Icon component={QuestionCircleFilled} />,
label: t("menus.header.help"),
onClick: () => {
@@ -514,14 +618,22 @@ function Header({
);
}
},
// {
// key: 'rescue',
// icon: <Icon component={CarFilled}/>,
// label: t("menus.header.rescueme"),
// onClick: () => {
// window.open("https://imexrescue.com/", "_blank");
// }
// },
...(InstanceRenderManager({
imex: true,
rome: false,
promanager: false
})
? [
{
key: "rescue",
icon: <Icon component={CarFilled} />,
label: t("menus.header.rescueme"),
onClick: () => {
window.open("https://imexrescue.com/", "_blank");
}
}
]
: []),
...(InstanceRenderManager({
imex: true,
@@ -531,6 +643,7 @@ function Header({
? [
{
key: "shiftclock",
id: "header-shiftclock",
icon: <Icon component={GiPlayerTime} />,
label: <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
}
@@ -538,6 +651,7 @@ function Header({
: []),
{
key: "profile",
id: "header-profile",
icon: <UserOutlined />,
label: <Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
}
@@ -573,6 +687,7 @@ function Header({
{
key: "recent",
icon: <ClockCircleFilled />,
id: "header-recent",
children: recentItems.map((i, idx) => ({
key: idx,
label: <Link to={i.url}>{i.label}</Link>
@@ -586,6 +701,7 @@ function Header({
imex: () => {
menuItems.push({
key: "beta-switch",
id: "header-beta-switch",
style: { marginLeft: "auto" },
label: (
<Tooltip

View File

@@ -1,11 +1,11 @@
import { Alert, Card, Col, Row, Space, Statistic, Tooltip, Typography } from 'antd';
import Dinero from 'dinero.js';
import React from 'react';
import { useTranslation } from 'react-i18next';
import InstanceRenderManager from '../../utils/instanceRenderMgr';
import AlertComponent from '../alert/alert.component';
import LoadingSkeleton from '../loading-skeleton/loading-skeleton.component';
import './job-bills-total.styles.scss';
import { Alert, Card, Col, Row, Space, Statistic, Tooltip, Typography } from "antd";
import Dinero from "dinero.js";
import React from "react";
import { useTranslation } from "react-i18next";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import AlertComponent from "../alert/alert.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import "./job-bills-total.styles.scss";
export default function JobBillsTotalComponent({
loading,
@@ -13,16 +13,16 @@ export default function JobBillsTotalComponent({
partsOrders,
jobTotals,
showWarning,
warningCallback,
warningCallback
}) {
const { t } = useTranslation();
if (loading) return <LoadingSkeleton />;
if (!!!jobTotals) {
if (showWarning && warningCallback && typeof warningCallback === 'function') {
warningCallback({ key: 'bills', warning: t('jobs.errors.nofinancial') });
if (showWarning && warningCallback && typeof warningCallback === "function") {
warningCallback({ key: "bills", warning: t("jobs.errors.nofinancial") });
}
return <AlertComponent type="error" message={t('jobs.errors.nofinancial')} />;
return <AlertComponent type="error" message={t("jobs.errors.nofinancial")} />;
}
const totals = jobTotals;
@@ -109,64 +109,60 @@ export default function JobBillsTotalComponent({
const discrepWithCms = discrepWithLbrAdj.add(totalReturns);
const calculatedCreditsNotReceived = totalReturns.subtract(billCms); //billCms is tracked as a negative number.
if (showWarning && warningCallback && typeof warningCallback === 'function') {
if (
discrepWithCms.getAmount() !== 0 ||
discrepWithLbrAdj.getAmount() !== 0 ||
discrepancy.getAmount() !== 0
) {
if (showWarning && warningCallback && typeof warningCallback === "function") {
if (discrepWithCms.getAmount() !== 0) {
warningCallback({
key: 'bills',
warning: t('jobs.labels.outstanding_reconciliation_discrep'),
key: "bills",
warning: t("jobs.labels.outstanding_reconciliation_discrep")
});
}
if (calculatedCreditsNotReceived.getAmount() > 0) {
warningCallback({ key: 'cm', warning: t('jobs.labels.outstanding_credit_memos') });
warningCallback({ key: "cm", warning: t("jobs.labels.outstanding_credit_memos") });
}
}
return (
<Row gutter={[16, 16]}>
<Col md={24} lg={18}>
<Card title={t('jobs.labels.jobtotals')} style={{ height: '100%' }}>
<Card title={t("jobs.labels.jobtotals")} style={{ height: "100%" }}>
<Space wrap size="large">
<Tooltip
title={
<div
dangerouslySetInnerHTML={{
__html: t('jobs.labels.plitooltips.partstotal'),
__html: t("jobs.labels.plitooltips.partstotal")
}}
/>
}
>
<Statistic title={t('jobs.labels.rosaletotal')} value={totalPartsSublet.toFormat()} />
<Statistic title={t("jobs.labels.rosaletotal")} value={totalPartsSublet.toFormat()} />
</Tooltip>
<Typography.Title>-</Typography.Title>
<Tooltip
title={
<div
dangerouslySetInnerHTML={{
__html: t('jobs.labels.plitooltips.billtotal'),
__html: t("jobs.labels.plitooltips.billtotal")
}}
/>
}
>
<Statistic title={t('bills.labels.retailtotal')} value={billTotals.toFormat()} />
<Statistic title={t("bills.labels.retailtotal")} value={billTotals.toFormat()} />
</Tooltip>
<Typography.Title>=</Typography.Title>
<Tooltip
title={
<div
dangerouslySetInnerHTML={{
__html: t('jobs.labels.plitooltips.discrep1'),
__html: t("jobs.labels.plitooltips.discrep1")
}}
/>
}
>
<Statistic
title={t('bills.labels.discrepancy')}
title={t("bills.labels.discrepancy")}
valueStyle={{
color: discrepancy.getAmount() === 0 ? 'green' : 'red',
color: discrepancy.getAmount() === 0 ? "green" : "red"
}}
value={discrepancy.toFormat()}
/>
@@ -176,27 +172,27 @@ export default function JobBillsTotalComponent({
title={
<div
dangerouslySetInnerHTML={{
__html: t('jobs.labels.plitooltips.laboradj'),
__html: t("jobs.labels.plitooltips.laboradj")
}}
/>
}
>
<Statistic title={t('bills.labels.dedfromlbr')} value={lbrAdjustments.toFormat()} />
<Statistic title={t("bills.labels.dedfromlbr")} value={lbrAdjustments.toFormat()} />
</Tooltip>
<Typography.Title>=</Typography.Title>
<Tooltip
title={
<div
dangerouslySetInnerHTML={{
__html: t('jobs.labels.plitooltips.discrep2'),
__html: t("jobs.labels.plitooltips.discrep2")
}}
/>
}
>
<Statistic
title={t('bills.labels.discrepancy')}
title={t("bills.labels.discrepancy")}
valueStyle={{
color: discrepWithLbrAdj.getAmount() === 0 ? 'green' : 'red',
color: discrepWithLbrAdj.getAmount() === 0 ? "green" : "red"
}}
value={discrepWithLbrAdj.toFormat()}
/>
@@ -206,27 +202,27 @@ export default function JobBillsTotalComponent({
title={
<div
dangerouslySetInnerHTML={{
__html: t('jobs.labels.plitooltips.creditmemos'),
__html: t("jobs.labels.plitooltips.creditmemos")
}}
/>
}
>
<Statistic title={t('bills.labels.totalreturns')} value={totalReturns.toFormat()} />
<Statistic title={t("bills.labels.totalreturns")} value={totalReturns.toFormat()} />
</Tooltip>
<Typography.Title>=</Typography.Title>
<Tooltip
title={
<div
dangerouslySetInnerHTML={{
__html: t('jobs.labels.plitooltips.discrep3'),
__html: t("jobs.labels.plitooltips.discrep3")
}}
/>
}
>
<Statistic
title={t('bills.labels.discrepancy')}
title={t("bills.labels.discrepancy")}
valueStyle={{
color: discrepWithCms.getAmount() === 0 ? 'green' : 'red',
color: discrepWithCms.getAmount() === 0 ? "green" : "red"
}}
value={discrepWithCms.toFormat()}
/>
@@ -238,40 +234,40 @@ export default function JobBillsTotalComponent({
discrepWithLbrAdj.getAmount() !== 0 ||
discrepancy.getAmount() !== 0) && (
<Alert
style={{ margin: '8px 0px' }}
style={{ margin: "8px 0px" }}
type="warning"
message={t('jobs.labels.outstanding_reconciliation_discrep')}
message={t("jobs.labels.outstanding_reconciliation_discrep")}
/>
)}
</Card>
</Col>
<Col md={24} lg={6}>
<Card title={t('jobs.labels.returntotals')} style={{ height: '100%' }}>
<Card title={t("jobs.labels.returntotals")} style={{ height: "100%" }}>
<Space wrap>
<Tooltip
title={
<div
dangerouslySetInnerHTML={{
__html: t('jobs.labels.plitooltips.totalreturns'),
__html: t("jobs.labels.plitooltips.totalreturns")
}}
/>
}
>
<Statistic title={t('bills.labels.totalreturns')} value={totalReturns.toFormat()} />
<Statistic title={t("bills.labels.totalreturns")} value={totalReturns.toFormat()} />
</Tooltip>
<Tooltip
title={
<div
dangerouslySetInnerHTML={{
__html: t('jobs.labels.plitooltips.calculatedcreditsnotreceived'),
__html: t("jobs.labels.plitooltips.calculatedcreditsnotreceived")
}}
/>
}
>
<Statistic
title={t('bills.labels.calculatedcreditsnotreceived')}
title={t("bills.labels.calculatedcreditsnotreceived")}
valueStyle={{
color: calculatedCreditsNotReceived.getAmount() <= 0 ? 'green' : 'red',
color: calculatedCreditsNotReceived.getAmount() <= 0 ? "green" : "red"
}}
value={
calculatedCreditsNotReceived.getAmount() >= 0
@@ -284,15 +280,15 @@ export default function JobBillsTotalComponent({
title={
<div
dangerouslySetInnerHTML={{
__html: t('jobs.labels.plitooltips.creditsnotreceived'),
__html: t("jobs.labels.plitooltips.creditsnotreceived")
}}
/>
}
>
<Statistic
title={t('bills.labels.creditsnotreceived')}
title={t("bills.labels.creditsnotreceived")}
valueStyle={{
color: totalReturnsMarkedNotReceived.getAmount() <= 0 ? 'green' : 'red',
color: totalReturnsMarkedNotReceived.getAmount() <= 0 ? "green" : "red"
}}
value={
totalReturnsMarkedNotReceived.getAmount() >= 0
@@ -303,11 +299,7 @@ export default function JobBillsTotalComponent({
</Tooltip>
</Space>
{showWarning && calculatedCreditsNotReceived.getAmount() > 0 && (
<Alert
style={{ margin: '8px 0px' }}
type="warning"
message={t('jobs.labels.outstanding_credit_memos')}
/>
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstanding_credit_memos")} />
)}
</Card>
</Col>

View File

@@ -1,20 +1,20 @@
import React from 'react';
import React from "react";
import { useQuery } from '@apollo/client';
import { useSplitTreatments } from '@splitsoftware/splitio-react';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { GET_LINE_TICKET_BY_PK } from '../../graphql/jobs-lines.queries';
import { selectJobReadOnly } from '../../redux/application/application.selectors';
import { selectBodyshop } from '../../redux/user/user.selectors';
import AlertComponent from '../alert/alert.component';
import LaborAllocationsTableComponent from '../labor-allocations-table/labor-allocations-table.component';
import PayrollLaborAllocationsTable from '../labor-allocations-table/labor-allocations-table.payroll.component';
import LoadingSkeleton from '../loading-skeleton/loading-skeleton.component';
import { useQuery } from "@apollo/client";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { GET_LINE_TICKET_BY_PK } from "../../graphql/jobs-lines.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import LaborAllocationsTableComponent from "../labor-allocations-table/labor-allocations-table.component";
import PayrollLaborAllocationsTable from "../labor-allocations-table/labor-allocations-table.payroll.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
jobRO: selectJobReadOnly
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -24,21 +24,21 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRoGuardLabor
export function JobCloseRoGuardLabor({ job, jobRO, bodyshop, form, warningCallback }) {
const { loading, error, data, refetch } = useQuery(GET_LINE_TICKET_BY_PK, {
variables: { id: job.id },
fetchPolicy: 'network-only',
nextFetchPolicy: 'network-only',
fetchPolicy: "network-only",
nextFetchPolicy: "network-only"
});
const {
treatments: { Enhanced_Payroll },
treatments: { Enhanced_Payroll }
} = useSplitTreatments({
attributes: {},
names: ['Enhanced_Payroll'],
splitKey: bodyshop.imexshopid,
names: ["Enhanced_Payroll"],
splitKey: bodyshop.imexshopid
});
if (loading) return <LoadingSkeleton />;
if (error) return <AlertComponent message={error.message} type="error" />;
return Enhanced_Payroll.treatment === 'on' ? (
return Enhanced_Payroll.treatment === "on" ? (
<PayrollLaborAllocationsTable
jobId={job.id}
timetickets={data ? data.timetickets : []}

View File

@@ -1,19 +1,19 @@
import React, { useEffect } from 'react';
import React, { useEffect } from "react";
import { Alert, Card } from 'antd';
import { useTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { selectJobReadOnly } from '../../redux/application/application.selectors';
import { selectBodyshop } from '../../redux/user/user.selectors';
import { useMemo } from 'react';
import Dinero from 'dinero.js';
import DataLabel from '../data-label/data-label.component';
import { Alert, Card } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useMemo } from "react";
import Dinero from "dinero.js";
import DataLabel from "../data-label/data-label.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
jobRO: selectJobReadOnly
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -41,25 +41,21 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form, warningCallb
useEffect(() => {
if (balance.getAmount() !== 0) {
warningCallback({ key: 'ar', warning: t('jobs.labels.outstanding_ar') });
warningCallback({ key: "ar", warning: t("jobs.labels.outstanding_ar") });
}
}, [balance, t, warningCallback]);
return (
<Card title={t('jobs.labels.accountsreceivable')} style={{ height: '100%' }}>
<DataLabel label={t('payments.labels.totalpayments')}>{total.toFormat()}</DataLabel>
<Card title={t("jobs.labels.accountsreceivable")} style={{ height: "100%" }}>
<DataLabel label={t("payments.labels.totalpayments")}>{total.toFormat()}</DataLabel>
<DataLabel
valueStyle={{ color: balance.getAmount() !== 0 ? 'red' : 'green' }}
label={t('payments.labels.balance')}
valueStyle={{ color: balance.getAmount() !== 0 ? "red" : "green" }}
label={t("payments.labels.balance")}
>
{balance.toFormat()}
</DataLabel>
{balance.getAmount() !== 0 && (
<Alert
style={{ margin: '8px 0px' }}
type="warning"
message={t('jobs.labels.outstanding_ar')}
/>
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstanding_ar")} />
)}
</Card>
);

View File

@@ -1,17 +1,17 @@
import React from 'react';
import React from "react";
import { useQuery } from '@apollo/client';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { QUERY_BILLS_BY_JOBID } from '../../graphql/bills.queries';
import { selectJobReadOnly } from '../../redux/application/application.selectors';
import { selectBodyshop } from '../../redux/user/user.selectors';
import AlertComponent from '../alert/alert.component';
import JobBillsTotalComponent from '../job-bills-total/job-bills-total.component';
import { useQuery } from "@apollo/client";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_BILLS_BY_JOBID } from "../../graphql/bills.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import JobBillsTotalComponent from "../job-bills-total/job-bills-total.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
jobRO: selectJobReadOnly
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -21,8 +21,8 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRoGuardBills
export function JobCloseRoGuardBills({ job, jobRO, bodyshop, form, warningCallback }) {
const { loading, error, data } = useQuery(QUERY_BILLS_BY_JOBID, {
variables: { jobid: job.id },
fetchPolicy: 'network-only',
nextFetchPolicy: 'network-only',
fetchPolicy: "network-only",
nextFetchPolicy: "network-only"
});
if (error) return <AlertComponent message={error.message} type="error" />;

View File

@@ -76,20 +76,21 @@ export function JobCloseRoGuardContainer({ job, jobRO, bodyshop, form }) {
<Col className="ro-guard-col-multiple" md={24} lg={6}>
<Row gutter={[32, 32]} style={{ height: "100%" }}>
<Col span={24}>
<JobCloseRoGuardProfit
job={job}
form={form} //warningCallback={warningCallback}
/>
<JobCloseRoGuardProfit job={job} form={form} warningCallback={warningCallback} />
</Col>
<Col span={24}>
<JobCloseRoGuardAr job={job} form={form} warningCallback={warningCallback} />
</Col>
</Row>
</Col>
<Col className="ro-guard-col-50" md={24} lg={8}>
<JobCloseRoGuardSublet job={job} warningCallback={warningCallback} />
{InstanceRenderManager({ rome: <JobCloseRoGuardPpd job={job} warningCallback={warningCallback} /> })}
</Col>
{InstanceRenderManager({
rome: (
<Col md={24} lg={8}>
{/* <JobCloseRoGuardSublet job={job} warningCallback={warningCallback} /> */}
<JobCloseRoGuardPpd job={job} warningCallback={warningCallback} />
</Col>
)
})}
<Col className="ro-guard-col" md={24} lg={10}>
<JobCloseRoGuardLabor job={job} form={form} warningCallback={warningCallback} />
</Col>

View File

@@ -1,20 +1,19 @@
import React, { useEffect, useState } from 'react';
import React, { useEffect, useState } from "react";
import { LockOutlined } from '@ant-design/icons';
import { Card, Form, Input } from 'antd';
import axios from 'axios';
import { useTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { selectJobReadOnly } from '../../redux/application/application.selectors';
import { selectBodyshop } from '../../redux/user/user.selectors';
import JobCostingStatistics from '../job-costing-statistics/job-costing-statistics.component';
import LoadingSkeleton from '../loading-skeleton/loading-skeleton.component';
import { Alert, Card } from "antd";
import axios from "axios";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobCostingStatistics from "../job-costing-statistics/job-costing-statistics.component";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
jobRO: selectJobReadOnly
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -31,7 +30,7 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form, warningCallb
try {
if (job.id) {
setLoading(true);
const { data } = await axios.post('/job/costing', { jobid: job.id });
const { data } = await axios.post("/job/costing", { jobid: job.id });
setCostingData(data);
}
} catch (error) {}
@@ -45,16 +44,19 @@ export function JobCloseRoGuardProfit({ job, jobRO, bodyshop, form, warningCallb
parseFloat(costingData?.summaryData.gppercent) < bodyshop?.md_ro_guard?.totalgppercent_minimum;
useEffect(() => {
if (enforceProfitPassword && typeof warningCallback === 'function') {
warningCallback({ key: 'profit', warning: t('jobs.labels.profitbypassrequired') });
if (enforceProfitPassword && typeof warningCallback === "function") {
warningCallback({ key: "profit", warning: t("jobs.labels.profitbypassrequired") });
}
}, [enforceProfitPassword, t, warningCallback]);
if (loading || !costingData) return <LoadingSkeleton />;
return (
<Card title={t('jobs.labels.profits')} style={{ height: '100%' }}>
<Card title={t("jobs.labels.profits")} style={{ height: "100%" }}>
<JobCostingStatistics summaryData={costingData?.summaryData} onlyGP />
{enforceProfitPassword && (
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.profitbypassrequired")} />
)}
</Card>
);
}

View File

@@ -1,17 +1,17 @@
import React, { useEffect } from 'react';
import React, { useEffect } from "react";
import { Alert, Card, Table } from 'antd';
import { t } from 'i18next';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { selectJobReadOnly } from '../../redux/application/application.selectors';
import { selectBodyshop } from '../../redux/user/user.selectors';
import CurrencyFormatter from '../../utils/CurrencyFormatter';
import { alphaSort } from '../../utils/sorters';
import { Alert, Card, Table } from "antd";
import { t } from "i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
jobRO: selectJobReadOnly
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -20,69 +20,56 @@ export default connect(mapStateToProps, mapDispatchToProps)(JobCloseRGuardSublet
export function JobCloseRGuardSublet({ job, jobRO, bodyshop, form, warningCallback }) {
const subletsNotDone = job?.joblines.filter(
(j) =>
(j.part_type === 'PAS' || j.part_type === 'PASL') &&
(!j.sublet_completed || !j.sublet_ignored)
(j) => (j.part_type === "PAS" || j.part_type === "PASL") && (!j.sublet_completed || !j.sublet_ignored)
);
const columns = [
{
title: t('joblines.fields.line_desc'),
dataIndex: 'line_desc',
fixed: 'left',
key: 'line_desc',
title: t("joblines.fields.line_desc"),
dataIndex: "line_desc",
fixed: "left",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
onCell: (record) => ({
className: record.manual_line && 'job-line-manual',
className: record.manual_line && "job-line-manual",
style: {
...(record.critical ? { boxShadow: ' -.5em 0 0 #FFC107' } : {}),
},
...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {})
}
}),
ellipsis: true,
ellipsis: true
},
{
title: t('joblines.fields.act_price'),
dataIndex: 'act_price',
key: 'act_price',
title: t("joblines.fields.act_price"),
dataIndex: "act_price",
key: "act_price",
sorter: (a, b) => a.act_price - b.act_price,
ellipsis: true,
render: (text, record) => <CurrencyFormatter>{record.act_price}</CurrencyFormatter>,
render: (text, record) => <CurrencyFormatter>{record.act_price}</CurrencyFormatter>
},
{
title: t('joblines.fields.part_qty'),
dataIndex: 'part_qty',
key: 'part_qty',
title: t("joblines.fields.part_qty"),
dataIndex: "part_qty",
key: "part_qty"
},
{
title: t('joblines.fields.notes'),
dataIndex: 'notes',
key: 'notes',
},
title: t("joblines.fields.notes"),
dataIndex: "notes",
key: "notes"
}
];
useEffect(() => {
if (subletsNotDone.length > 0) {
warningCallback({ key: 'sublet', warning: t('jobs.labels.outstanding_sublets') });
warningCallback({ key: "sublet", warning: t("jobs.labels.outstanding_sublets") });
}
}, [subletsNotDone.length, warningCallback]);
return (
<Card title={t('jobs.labels.subletsnotcompleted')}>
<Table
dataSource={subletsNotDone}
columns={columns}
pagination={false}
rowKey="id"
bordered
size="small"
/>
<Card title={t("jobs.labels.subletsnotcompleted")}>
<Table dataSource={subletsNotDone} columns={columns} pagination={false} rowKey="id" bordered size="small" />
{subletsNotDone.length > 0 && (
<Alert
style={{ margin: '8px 0px' }}
type="warning"
message={t('jobs.labels.outstanding_sublets')}
/>
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstanding_sublets")} />
)}
</Card>
);

View File

@@ -1,14 +1,14 @@
import React from 'react';
import React from "react";
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { selectJobReadOnly } from '../../redux/application/application.selectors';
import { selectBodyshop } from '../../redux/user/user.selectors';
import JobLifecycleComponent from '../job-lifecycle/job-lifecycle.component';
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import JobLifecycleComponent from "../job-lifecycle/job-lifecycle.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
jobRO: selectJobReadOnly
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import { Col, Divider, Row, Skeleton, Space, Timeline, Typography } from "antd";
import { Col, Row, Skeleton, Space, Timeline, Typography } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
@@ -7,17 +7,19 @@ import { GET_JOB_LINE_ORDERS } from "../../graphql/jobs.queries";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { QUERY_JOBLINE_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
import TaskListContainer from "../task-list/task-list.container.jsx";
import FeatureWrapper from "../feature-wrapper/feature-wrapper.component.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(JobLinesExpander);
export function JobLinesExpander({ jobline, jobid, bodyshop }) {
@@ -43,13 +45,25 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
? data.parts_order_lines.map((line) => ({
key: line.id,
children: (
<Space split={<Divider type="vertical" />} wrap>
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}>
{line.parts_order.order_number}
</Link>
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
{line.parts_order.vendor.name}
</Space>
<Row wrap>
<Col span={4}>
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.parts_order.id}`}>
{line.parts_order.order_number}
</Link>
</Col>
<Col span={4}>
<DateFormatter>{line.parts_order.order_date}</DateFormatter>
</Col>
<Col span={4}>{line.parts_order.vendor.name}</Col>
{line.backordered_eta ? (
<Col span={4}>
<span>
{`${t("parts_orders.fields.backordered_eta")}: `}
<DateFormatter>{line.backordered_eta}</DateFormatter>
</span>
</Col>
) : null}
</Row>
)
}))
: [
@@ -61,48 +75,6 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
}
/>{" "}
</Col>
<Col md={24} lg={8}>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Timeline
items={
data.billlines.length > 0
? data.billlines.map((line) => ({
key: line.id,
children: (
<Row wrap>
<Col span={4}>
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
{line.bill.invoice_number}
</Link>
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_price")}: `}
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
</span>
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_cost")}: `}
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
</span>
</Col>
<Col span={4}>
<DateFormatter>{line.bill.date}</DateFormatter>
</Col>
<Col span={4}> {line.bill.vendor.name}</Col>
</Row>
)
}))
: [
{
key: "no-orders",
children: t("bills.labels.nobilllines")
}
]
}
/>
</Col>
<Col md={24} lg={8}>
<Typography.Title level={4}>{t("parts_dispatch.labels.parts_dispatch")}</Typography.Title>
<Timeline
@@ -111,23 +83,84 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
? data.parts_dispatch_lines.map((line) => ({
key: line.id,
children: (
<Space split={<Divider type="vertical" />} wrap>
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
<Space>
{t("parts_dispatch_lines.fields.accepted_at")}
<DateFormatter>{line.accepted_at}</DateFormatter>
</Space>
</Space>
<Row>
<Col span={8}>
<Link to={`/manage/jobs/${jobid}?partsorderid=${line.id}`}>{line.parts_dispatch.number}</Link>
</Col>
<Col span={8}>
{bodyshop.employees.find((e) => e.id === line.parts_dispatch.employeeid)?.first_name}
</Col>
<Col span={8}>
<Space>
{t("parts_dispatch_lines.fields.accepted_at")}
<DateFormatter>{line.accepted_at}</DateFormatter>
</Space>
</Col>
</Row>
)
}))
: {
key: "dispatch-lines",
children: t("parts_orders.labels.notyetordered")
}
: [
{
key: "dispatch-lines",
children: t("parts_dispatch.labels.notyetdispatched")
}
]
}
/>
</Col>
<FeatureWrapper featureName="bills" noauth={() => null}>
<Col md={24} lg={8}>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Timeline
items={
data.billlines.length > 0
? data.billlines.map((line) => ({
key: line.id,
children: (
<Row wrap>
<Col span={4}>
<Link to={`/manage/jobs/${jobid}?tab=partssublet&billid=${line.bill.id}`}>
{line.bill.invoice_number}
</Link>
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_price")}: `}
<CurrencyFormatter>{line.actual_price}</CurrencyFormatter>
</span>
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_cost")}: `}
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
</span>
</Col>
<Col span={4}>
<DateFormatter>{line.bill.date}</DateFormatter>
</Col>
<Col span={4}> {line.bill.vendor.name}</Col>
</Row>
)
}))
: [
{
key: "no-orders",
children: t("bills.labels.nobilllines")
}
]
}
/>
</Col>
</FeatureWrapper>
<Col md={24} lg={24}>
<TaskListContainer
parentJobId={jobid}
relationshipType={"joblineid"}
relationshipId={jobline.id}
query={{ QUERY_JOBLINE_TASKS_PAGINATED }}
titleTranslation="tasks.titles.job_tasks"
/>
</Col>
</Row>
);
}

View File

@@ -42,6 +42,8 @@ import JobSendPartPriceChangeComponent from "../job-send-parts-price-change/job-
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import JobLinesExpander from "./job-lines-expander.component";
import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
import { FaTasks } from "react-icons/fa";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -52,7 +54,8 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({
setJobLineEditContext: (context) => dispatch(setModalContext({ context: context, modal: "jobLineEdit" })),
setPartsOrderContext: (context) => dispatch(setModalContext({ context: context, modal: "partsOrder" })),
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" }))
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
});
export function JobLinesComponent({
@@ -67,7 +70,8 @@ export function JobLinesComponent({
job,
setJobLineEditContext,
form,
setBillEnterContext
setBillEnterContext,
setTaskUpsertContext
}) {
const [deleteJobLine] = useMutation(DELETE_JOB_LINE_BY_PK);
const {
@@ -331,6 +335,24 @@ export function JobLinesComponent({
>
<EditFilled />
</Button>
</>
)}
<Button
title={t("tasks.buttons.create")}
onClick={() => {
setTaskUpsertContext({
context: {
jobid: job.id,
joblineid: record.id
}
});
}}
>
<FaTasks />
</Button>
{(record.manual_line || jobIsPrivate) && (
<>
<Button
disabled={jobRO}
onClick={async () => {
@@ -452,7 +474,7 @@ export function JobLinesComponent({
vendorid: bodyshop.inhousevendorid,
invoice_number: "ih",
isinhouse: true,
date: new dayjs(),
date: dayjs(),
total: 0,
billlines: selectedLines.map((p) => {
return {
@@ -531,7 +553,7 @@ export function JobLinesComponent({
>
{t("joblines.actions.new")}
</Button>
{bodyshop.region_config.toLowerCase().startsWith("us") && <JobSendPartPriceChangeComponent job={job} />}
{InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} /> })}
<JobCreateIOU job={job} selectedJobLines={selectedLines} />
<Input.Search
placeholder={t("general.labels.search")}

View File

@@ -154,7 +154,7 @@ export function JobLineConvertToLabor({ children, jobline, job, insertAuditTrail
setLoading(true);
form.setFieldsValue({
// date: new dayjs(),
// date: dayjs(),
// bodyhrs: Math.round(v.bodyhrs * 10) / 10,
// painthrs: Math.round(v.painthrs * 10) / 10,
});

View File

@@ -1,3 +1,4 @@
import { LoadingOutlined } from "@ant-design/icons";
import { useLazyQuery } from "@apollo/client";
import { Select, Space, Spin, Tag } from "antd";
import _ from "lodash";
@@ -6,8 +7,6 @@ import { useTranslation } from "react-i18next";
import { SEARCH_JOBS_BY_ID_FOR_AUTOCOMPLETE, SEARCH_JOBS_FOR_AUTOCOMPLETE } from "../../graphql/jobs.queries";
import AlertComponent from "../alert/alert.component";
import { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import { SearchOutlined } from "@ant-design/icons";
import { LoadingOutlined } from "@ant-design/icons";
const { Option } = Select;
@@ -75,7 +74,7 @@ const JobSearchSelect = (
filterOption={false}
onSearch={handleSearch}
//loading={loading || idLoading}
suffixIcon={loading && <Spin />}
suffixIcon={(loading || idLoading) && <Spin />}
notFoundContent={loading ? <LoadingOutlined /> : null}
{...restProps}
>
@@ -86,9 +85,9 @@ const JobSearchSelect = (
<span>
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${
o.ro_number || t("general.labels.na")
} | ${OwnerNameDisplayFunction(o)} | ${
o.v_model_yr || ""
} ${o.v_make_desc || ""} ${o.v_model_desc || ""}`}
} | ${OwnerNameDisplayFunction(o)} | ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${
o.v_model_desc || ""
}`}
</span>
<Tag>
<strong>{o.status}</strong>

View File

@@ -165,6 +165,8 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
bold: true
}
];
// TODO: was removed by Patrick during a CI bug fix.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [job.job_totals, job.cieca_pft, t, bodyshop.md_responsibility_centers]);
const columns = [

View File

@@ -15,23 +15,43 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
const linesToUpdate = [];
newLines.forEach((newLine) => {
const matchingIndex = existingLines.findIndex((eL) => eL.unq_seq === newLine.unq_seq);
const matchingIndexLines = [];
existingLines.forEach((eL, index) => {
if (eL.unq_seq === newLine.unq_seq) {
matchingIndexLines.push({ index, record: eL });
}
});
//Should do a check to make sure there is only 1 matching unq sequence number.
if (matchingIndex >= 0) {
//Found a relevant matching line. Add it to lines to update.
linesToUpdate.push({
id: existingLines[matchingIndex].id,
newData: { ...newLine, removed: false, act_price_before_ppc: null }
});
//Splice out item we found for performance.
existingLines.splice(matchingIndex, 1);
} else {
if (matchingIndexLines.length === 0) {
//Didn't find a match. Must be a new line.
linesToInsert.push(newLine);
}
//If we find only 1, we can use the old logic.
else if (matchingIndexLines.length === 1) {
//Found a relevant matching line. Add it to lines to update.
linesToUpdate.push({
id: matchingIndexLines[0].record.id,
newData: { ...newLine, removed: false, act_price_before_ppc: null }
});
//Splice out item we found for performance.
existingLines.splice(matchingIndexLines[0].index, 1);
} else if (matchingIndexLines.length === 2) {
//if we find 2, we need to separate it out by the non-refinish lines and splice one out.
//Find the mathcing one depending on whether this is the refiniish one or not.
const matchingLine = matchingIndexLines.find((i) =>
newLine.mod_lbr_ty === "LAR" ? i.record.mod_lbr_ty === "LAR" : i.record.mod_lbr_ty !== "LAR"
);
linesToUpdate.push({
id: matchingLine.record.id,
newData: { ...newLine, removed: false, act_price_before_ppc: null }
});
//Splice out item we found for performance.
existingLines.splice(matchingLine.index, 1);
} else {
//We found more than 2 matching lines. We should never get here. Throw a warning and back out!
throw new Error("Too many matching lines found. Ensure EMS file is valid.");
}
});
//Wahtever is left in the existing lines, are lines that should be removed.

View File

@@ -237,7 +237,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
executeFunction: true,
rome: ResolveCCCLineIssues,
promanager: ResolveCCCLineIssues,
args: [(supp, bodyshop)]
args: [supp, bodyshop]
});
await InstanceRenderManager({
@@ -608,13 +608,12 @@ function ResolveCCCLineIssues(estData, bodyshop) {
//Web Est seems to have additional costs with UNQ_SEQ 0. Keep them all?
if (line.unq_seq === 0) return;
if (index0ActPrice !== line.act_price) {
line.notes += ` | Price override.`;
// line.notes += ` | Price override.`;
return;
}
const indexInEstData = estData.joblines.data.findIndex((l) => l.unq_seq === line.unq_seq);
estData.joblines.data[
indexInEstData
].notes += ` | Scrubbed due to the line_ref issue. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`;
//estData.joblines.data[indexInEstData].notes +=
// ` | Act Price delete. (prev act price = ${estData.joblines.data[indexInEstData].act_price})`;
estData.joblines.data[indexInEstData].act_price = 0;
estData.joblines.data[indexInEstData].db_price = 0;
});

View File

@@ -1,6 +1,9 @@
import { DownCircleFilled } from "@ant-design/icons";
import { useApolloClient, useMutation } from "@apollo/client";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, Card, Dropdown, Form, Input, Modal, notification, Popconfirm, Popover, Select, Space } from "antd";
import axios from "axios";
import parsePhoneNumber from "libphonenumber-js";
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -8,27 +11,24 @@ import { Link, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries";
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
import { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setEmailOptions } from "../../redux/email/email.actions";
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import dayjs from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
import axios from "axios";
import { setEmailOptions } from "../../redux/email/email.actions";
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
import { GET_CURRENT_QUESTIONSET_ID, INSERT_CSI } from "../../graphql/csi.queries";
import { TemplateList } from "../../utils/TemplateConstants";
import parsePhoneNumber from "libphonenumber-js";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
import dayjs from "../../utils/day";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
const mapStateToProps = createStructuredSelector({
@@ -39,13 +39,57 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({
setScheduleContext: (context) => dispatch(setModalContext({ context: context, modal: "schedule" })),
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
setBillEnterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "billEnter"
})
),
setPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "payment" })),
setJobCostingContext: (context) => dispatch(setModalContext({ context: context, modal: "jobCosting" })),
setTimeTicketContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicket" })),
setCardPaymentContext: (context) => dispatch(setModalContext({ context: context, modal: "cardPayment" })),
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type })),
setTimeTicketTaskContext: (context) => dispatch(setModalContext({ context: context, modal: "timeTicketTask" })),
setJobCostingContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "jobCosting"
})
),
setTimeTicketContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "timeTicket"
})
),
setCardPaymentContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "cardPayment"
})
),
insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(
insertAuditTrail({
jobid,
operation,
type
})
),
setTimeTicketTaskContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "timeTicketTask"
})
),
setTaskUpsertContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "taskUpsert"
})
),
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text))
@@ -67,7 +111,8 @@ export function JobsDetailHeaderActions({
setEmailOptions,
openChatByPhone,
setMessage,
setTimeTicketTaskContext
setTimeTicketTaskContext,
setTaskUpsertContext
}) {
const { t } = useTranslation();
const client = useApolloClient();
@@ -596,6 +641,7 @@ export function JobsDetailHeaderActions({
const menuItems = [
{
key: "schedule",
id: "job-actions-schedule",
disabled: !jobInPreProduction || !job.converted || jobRO,
label: t("jobs.actions.schedule"),
onClick: () => {
@@ -612,6 +658,7 @@ export function JobsDetailHeaderActions({
},
{
key: "cancelallappointments",
id: "job-actions-cancelallappointments",
onClick: () => {
if (job.status !== bodyshop.md_ro_statuses.default_scheduled) {
return;
@@ -625,6 +672,7 @@ export function JobsDetailHeaderActions({
imex: [
{
key: "intake",
id: "job-actions-intake",
disabled: !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO,
label:
!!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO ? (
@@ -635,6 +683,7 @@ export function JobsDetailHeaderActions({
},
{
key: "deliver",
id: "job-actions-deliver",
disabled: !jobInProduction || jobRO,
label: !jobInProduction ? (
t("jobs.actions.deliver")
@@ -644,6 +693,7 @@ export function JobsDetailHeaderActions({
},
{
key: "checklist",
id: "job-actions-checklist",
disabled: !job.converted,
label: <Link to={`/manage/jobs/${job.id}/checklist`}>{t("jobs.actions.viewchecklist")}</Link>
}
@@ -652,6 +702,7 @@ export function JobsDetailHeaderActions({
promanager: [
{
key: "toggleproduction",
id: "job-actions-toggleproduction",
disabled: !job.converted || jobRO,
label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch} />
}
@@ -665,6 +716,7 @@ export function JobsDetailHeaderActions({
? [
{
key: "entertimetickets",
id: "job-actions-entertimetickets",
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
label: t("timetickets.actions.enter"),
onClick: () => {
@@ -688,6 +740,7 @@ export function JobsDetailHeaderActions({
if (bodyshop.md_tasks_presets.enable_tasks) {
menuItems.push({
key: "claimtimetickettasks",
id: "job-actions-claimtimetickettasks",
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
onClick: () => {
setTimeTicketTaskContext({
@@ -701,6 +754,7 @@ export function JobsDetailHeaderActions({
menuItems.push({
key: "enterpayments",
id: "job-actions-enterpayments",
disabled: !job.converted,
label: t("menus.header.enterpayment"),
onClick: () => {
@@ -716,22 +770,24 @@ export function JobsDetailHeaderActions({
if (ImEXPay.treatment === "on") {
menuItems.push({
key: "entercardpayments",
id: "job-actions-entercardpayments",
disabled: !job.converted,
label: t("menus.header.entercardpayment"),
onClick: () => {
logImEXEvent("job_header_enter_card_payment");
setCardPaymentContext({
actions: {},
actions: { refetch },
context: { jobid: job.id }
});
}
});
}
if (HasFeatureAccess({ featureName: "courtesycars" })) {
if (HasFeatureAccess({ featureName: "courtesycars", bodyshop })) {
menuItems.push({
key: "cccontract",
id: "job-actions-cccontract",
disabled: jobRO || !job.converted,
label: (
<Link state={{ jobId: job.id }} to="/manage/courtesycars/contracts/new">
@@ -741,16 +797,29 @@ export function JobsDetailHeaderActions({
});
}
menuItems.push({
key: "createtask",
id: "job-actions-createtask",
label: t("menus.header.create_task"),
onClick: () =>
setTaskUpsertContext({
actions: {},
context: { jobid: job.id }
})
});
menuItems.push(
job.inproduction
? {
key: "addtoproduction",
key: "removefromproduction",
id: "job-actions-removefromproduction",
disabled: !job.converted,
label: t("jobs.actions.removefromproduction"),
onClick: () => AddToProduction(client, job.id, refetch, true)
}
: {
key: "addtoproduction",
id: "job-actions-addtoproduction",
disabled: !job.converted,
label: t("jobs.actions.addtoproduction"),
onClick: () => AddToProduction(client, job.id, refetch)
@@ -760,12 +829,14 @@ export function JobsDetailHeaderActions({
menuItems.push(
{
key: "togglesuspend",
id: "job-actions-togglesuspend",
onClick: handleSuspend,
label: job.suspended ? t("production.actions.unsuspend") : t("production.actions.suspend")
},
{
key: "toggleAlert",
onClick: handleAlertToggle,
id: "job-actions-togglealert",
label:
job.production_vars && job.production_vars.alert
? t("production.labels.alertoff")
@@ -777,6 +848,7 @@ export function JobsDetailHeaderActions({
children: [
{
key: "duplicate",
id: "job-actions-duplicate",
label: (
<Popconfirm
title={t("jobs.labels.duplicateconfirm")}
@@ -792,6 +864,7 @@ export function JobsDetailHeaderActions({
},
{
key: "duplicatenolines",
id: "job-actions-duplicatenolines",
label: (
<Popconfirm
title={t("jobs.labels.duplicateconfirm")}
@@ -815,6 +888,7 @@ export function JobsDetailHeaderActions({
? [
{
key: "postbills",
id: "job-actions-postbills",
disabled: !job.converted,
label: t("jobs.actions.postbills"),
onClick: () => {
@@ -833,6 +907,7 @@ export function JobsDetailHeaderActions({
{
key: "addtopartsqueue",
id: "job-actions-addtopartsqueue",
disabled: !job.converted || !jobInProduction || jobRO,
label: t("jobs.actions.addtopartsqueue"),
onClick: async () => {
@@ -858,6 +933,7 @@ export function JobsDetailHeaderActions({
},
{
key: "closejob",
id: "job-actions-closejob",
disabled: !jobInPostProduction,
label: !jobInPostProduction ? (
t("menus.jobsactions.closejob")
@@ -873,6 +949,7 @@ export function JobsDetailHeaderActions({
},
{
key: "admin",
id: "job-actions-admin",
label: (
<Link
to={{
@@ -894,6 +971,7 @@ export function JobsDetailHeaderActions({
) {
menuItems.push({
key: "exportcustdata",
id: "job-actions-exportcustdata",
disabled: !job.converted,
label: t("jobs.actions.exportcustdata"),
onClick: handleExportCustData
@@ -904,18 +982,21 @@ export function JobsDetailHeaderActions({
const children = [
{
key: "email",
id: "job-actions-email",
disabled: !!!job.ownr_ea,
label: t("general.labels.email"),
onClick: handleCreateCsi
},
{
key: "text",
id: "job-actions-text",
disabled: !!!job.ownr_ph1,
label: t("general.labels.text"),
onClick: handleCreateCsi
},
{
key: "generate",
id: "job-actions-generate",
disabled: job.csiinvites && job.csiinvites.length > 0,
label: t("jobs.actions.generatecsi"),
onClick: handleCreateCsi
@@ -949,6 +1030,7 @@ export function JobsDetailHeaderActions({
}
menuItems.push({
key: "sendcsi",
id: "job-actions-sendcsi",
label: t("jobs.actions.sendcsi"),
disabled: !job.converted,
children
@@ -957,6 +1039,7 @@ export function JobsDetailHeaderActions({
menuItems.push({
key: "jobcosting",
id: "job-actions-jobcosting",
disabled: !job.converted,
label: t("jobs.labels.jobcosting"),
onClick: () => {
@@ -974,6 +1057,7 @@ export function JobsDetailHeaderActions({
if (job && !job.converted) {
menuItems.push({
key: "deletejob",
id: "job-actions-deletejob",
label: (
<Popconfirm
title={t("jobs.labels.deleteconfirm")}
@@ -990,6 +1074,7 @@ export function JobsDetailHeaderActions({
menuItems.push({
key: "manualevent",
id: "job-actions-manualevent",
onClick: (e) => {
setVisibility(true);
},
@@ -999,6 +1084,7 @@ export function JobsDetailHeaderActions({
if (!jobRO && job.converted) {
menuItems.push({
key: "voidjob",
id: "job-actions-voidjob",
label: (
<RbacWrapper action="jobs:void" noauth>
<Popconfirm

View File

@@ -96,7 +96,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
}),
onFilter: (value, record) => value.includes(record.status)
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",

View File

@@ -18,8 +18,6 @@ import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { setJoyRideSteps } from "../../redux/application/application.actions";
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
@@ -176,7 +174,6 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
[],
onFilter: (value, record) => value.includes(record.status)
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
@@ -316,34 +313,6 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
title={t("titles.bc.jobs-active")}
extra={
<Space wrap>
{InstanceRenderManager({
promanager: (
<Button
onClick={() =>
setJoyRideSteps([
{
target: "#active-jobs-list",
content: "This is where you will see all work coming in and currently here."
},
{
target: "#header-jobs",
spotlightClicks: true,
disableOverlayClose: true,
content:
"The jobs menu lets you access additional pages to see more information. You can import new jobs, search all jobs, or manage your current production."
},
{
target: "#header-jobs-available",
content: "You can find jobs to import here."
}
])
}
>
Start Walk Through
</Button>
)
})}
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>

View File

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

View File

@@ -1,23 +1,24 @@
import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { useLazyQuery, useMutation } from "@apollo/client";
import { Button, Card, Checkbox, Drawer, Grid, Input, Popconfirm, Space, Table } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import queryString from "query-string";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { QUERY_BILL_BY_PK } from "../../graphql/bills.queries";
import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters";
import DataLabel from "../data-label/data-label.component";
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component";
@@ -26,6 +27,7 @@ import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
import PrintWrapper from "../print-wrapper/print-wrapper.component";
import FeatureWrapperComponent from "../feature-wrapper/feature-wrapper.component";
import { FaTasks } from "react-icons/fa";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -33,8 +35,21 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
setBillEnterContext: (context) => dispatch(setModalContext({ context: context, modal: "billEnter" })),
setPartsReceiveContext: (context) => dispatch(setModalContext({ context: context, modal: "partsReceive" }))
setBillEnterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "billEnter"
})
),
setPartsReceiveContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "partsReceive"
})
),
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
});
export function PartsOrderListTableComponent({
@@ -44,7 +59,8 @@ export function PartsOrderListTableComponent({
job,
billsQuery,
handleOnRowClick,
setPartsReceiveContext
setPartsReceiveContext,
setTaskUpsertContext
}) {
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
@@ -66,19 +82,46 @@ export function PartsOrderListTableComponent({
const [state, setState] = useState({
sortedInfo: {}
});
const [returnfrombill, setReturnFromBill] = useState();
const [billData, setBillData] = useState();
const search = queryString.parse(useLocation().search);
const selectedpartsorder = search.partsorderid;
const [searchText, setSearchText] = useState("");
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
const { refetch } = billsQuery;
useEffect(() => {
if (returnfrombill === null) {
setBillData(null);
} else {
const fetchData = async () => {
const result = await billQuery({
variables: { billid: returnfrombill }
});
setBillData(result.data);
};
fetchData();
}
}, [returnfrombill, billQuery]);
const recordActions = (record, showView = false) => (
<Space wrap>
<Space direction="horizontal" wrap>
{showView && (
<Button onClick={() => handleOnRowClick(record)}>
<Button
onClick={() => {
if (record.returnfrombill) {
setReturnFromBill(record.returnfrombill);
} else {
setReturnFromBill(null);
}
handleOnRowClick(record);
}}
>
<EyeFilled />
</Button>
)}
@@ -108,7 +151,19 @@ export function PartsOrderListTableComponent({
>
{t("parts_orders.actions.receive")}
</Button>
<Button
title={t("tasks.buttons.create")}
onClick={() => {
setTaskUpsertContext({
context: {
jobid: job.id,
partsorderid: record.id
}
});
}}
>
<FaTasks />
</Button>
<Popconfirm
title={t("parts_orders.labels.confirmdelete")}
disabled={jobRO}
@@ -148,7 +203,7 @@ export function PartsOrderListTableComponent({
is_credit_memo: record.return,
billlines: record.parts_order_lines.map((pol) => {
return {
joblineid: pol.job_line_id,
joblineid: pol.job_line_id || "noline",
line_desc: pol.line_desc,
quantity: pol.quantity,
@@ -368,7 +423,14 @@ export function PartsOrderListTableComponent({
return (
<div>
<PageHeader title={record && `${record.vendor.name} - ${record.order_number}`} extra={recordActions(record)} />
<PageHeader
title={
billData
? `${record.vendor.name} - ${record.order_number} - ${t("bills.labels.returnfrombill")}: ${billData.bills_by_pk.invoice_number}`
: `${record.vendor.name} - ${record.order_number}`
}
extra={recordActions(record)}
/>
<Table
scroll={{
x: true //y: "50rem"

View File

@@ -182,7 +182,7 @@ export function PartsOrderModalComponent({ bodyshop, vendorList, sendTypeState,
}
]}
>
<InputNumber />
<InputNumber min={1} />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.act_price")}

View File

@@ -158,7 +158,7 @@ export function PartsOrderModalContainer({
vendorid: bodyshop.inhousevendorid,
invoice_number: "ih",
isinhouse: true,
date: new dayjs(),
date: dayjs(),
total: 0,
billlines: values.parts_order_lines.data.map((p) => {
return {

View File

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

View File

@@ -7,8 +7,8 @@ import { createStructuredSelector } from "reselect";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import { selectPayment } from "../../redux/modals/modals.selectors";
import { selectCurrentUser } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -58,8 +58,10 @@ const PaymentMarkForExportButton = ({ bodyshop, payment, refetch, setPaymentCont
refetch
},
context: {
...paymentModal.context,
...paymentModal.context,
...payment,
smartRefetch: true,
exportedat: today
}
});

View File

@@ -1,32 +1,30 @@
import { useMutation } from "@apollo/client";
import { Button, Form, Modal, notification, Space } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_NEW_PAYMENT, UPDATE_PAYMENT } from "../../graphql/payments.queries";
import { setEmailOptions } from "../../redux/email/email.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectPayment } from "../../redux/modals/modals.selectors";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import {useMutation} from "@apollo/client";
import {Button, Form, Modal, notification, Space} from "antd";
import React, {useEffect, useState} from "react";
import {useTranslation} from "react-i18next";
import {connect} from "react-redux";
import {createStructuredSelector} from "reselect";
import {INSERT_NEW_PAYMENT, UPDATE_PAYMENT} from "../../graphql/payments.queries";
import {toggleModalVisible} from "../../redux/modals/modals.actions";
import {selectPayment} from "../../redux/modals/modals.selectors";
import {selectBodyshop} from "../../redux/user/user.selectors";
import {GenerateDocument} from "../../utils/RenderTemplate";
import {TemplateList} from "../../utils/TemplateConstants";
import PaymentForm from "../payment-form/payment-form.component";
import PaymentMarkForExportButton from "../payment-mark-export-button/payment-mark-export-button-component";
import PaymentMarkForExportButton
from "../payment-mark-export-button/payment-mark-export-button-component";
import PaymentReexportButton from "../payment-reexport-button/payment-reexport-button.component";
const mapStateToProps = createStructuredSelector({
paymentModal: selectPayment,
bodyshop: selectBodyshop,
currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
toggleModalVisible: () => dispatch(toggleModalVisible("payment"))
});
function PaymentModalContainer({ paymentModal, toggleModalVisible, bodyshop, currentUser, setEmailOptions }) {
function PaymentModalContainer({paymentModal, toggleModalVisible, bodyshop }) {
const [form] = Form.useForm();
const [enterAgain, setEnterAgain] = useState(false);
const [insertPayment] = useMutation(INSERT_NEW_PAYMENT);
@@ -34,7 +32,6 @@ function PaymentModalContainer({ paymentModal, toggleModalVisible, bodyshop, cur
const { t } = useTranslation();
const { context, actions, open } = paymentModal;
const [loading, setLoading] = useState(false);
const handleFinish = async (values) => {
@@ -84,9 +81,9 @@ function PaymentModalContainer({ paymentModal, toggleModalVisible, bodyshop, cur
});
if (!!!updatedPayment.errors) {
notification["success"]({ message: t("payments.successes.payment") });
notification["success"]({ message: t("payments.successes.paymentupdate") });
} else {
notification["error"]({ message: t("payments.errors.payment") });
notification["error"]({ message: t("payments.errors.paymentupdate") });
}
}

View File

@@ -3,10 +3,10 @@ import { Button, notification } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_PAYMENT } from "../../graphql/payments.queries";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectPayment } from "../../redux/modals/modals.selectors";
import { createStructuredSelector } from "reselect";
const mapStateToProps = createStructuredSelector({
paymentModal: selectPayment
@@ -40,6 +40,7 @@ const PaymentReexportButton = ({ paymentModal, payment, refetch, setPaymentConte
refetch
},
context: {
...paymentModal.context,
...paymentModal.context,
...payment,
exportedat: null

View File

@@ -14,11 +14,11 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import { pageLimit } from "../../utils/config";
import { alphaSort } from "../../utils/sorters";
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import { pageLimit } from "../../utils/config";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser

View File

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

View File

@@ -2,23 +2,24 @@ import { useLazyQuery } from "@apollo/client";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, Card, Col, DatePicker, Form, Input, Radio, Row, Typography } from "antd";
import _ from "lodash";
import dayjs from "../../utils/day";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_ACTIVE_EMPLOYEES } from "../../graphql/employees.queries";
import { QUERY_ACTIVE_EMPLOYEES, QUERY_ACTIVE_EMPLOYEES_WITH_EMAIL } from "../../graphql/employees.queries";
import { QUERY_ALL_VENDORS } from "../../graphql/vendors.queries";
import { selectReportCenter } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import DatePickerRanges from "../../utils/DatePickerRanges";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import dayjs from "../../utils/day";
import EmployeeSearchSelectEmail from "../employee-search-select/employee-search-select-email.component";
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import "./report-center-modal.styles.scss";
import ReportCenterModalFiltersSortersComponent from "./report-center-modal-filters-sorters-component";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import ReportCenterModalFiltersSortersComponent from "./report-center-modal-filters-sorters-component";
import "./report-center-modal.styles.scss";
const mapStateToProps = createStructuredSelector({
reportCenterModal: selectReportCenter,
@@ -66,6 +67,13 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
skip: !(open && Templates[form.getFieldValue("key")] && Templates[form.getFieldValue("key")].idtype)
});
const [callEmployeeWithEmailQuery, { data: employeeWithEmailData, called: employeeWithEmailCalled }] = useLazyQuery(
QUERY_ACTIVE_EMPLOYEES_WITH_EMAIL,
{
skip: !(open && Templates[form.getFieldValue("key")] && Templates[form.getFieldValue("key")].idtype)
}
);
const handleFinish = async (values) => {
setLoading(true);
const start = values.dates ? values.dates[0] : null;
@@ -197,6 +205,7 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
}
if (!vendorCalled && idtype === "vendor") callVendorQuery();
if (!employeeCalled && idtype === "employee") callEmployeeQuery();
if (!employeeWithEmailCalled && idtype === "employeeWithEmail") callEmployeeWithEmailQuery();
if (idtype === "vendor")
return (
<Form.Item
@@ -227,6 +236,22 @@ export function ReportCenterModalComponent({ reportCenterModal, bodyshop }) {
<EmployeeSearchSelect options={employeeData ? employeeData.employees : []} />
</Form.Item>
);
//This was introduced with tasks before assigned_to was shifted to UUID. Keeping in place for reference in the future if needed.
if (idtype === "employeeWithEmail")
return (
<Form.Item
name="id"
label={t("reportcenter.labels.employee")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<EmployeeSearchSelectEmail options={employeeWithEmailData ? employeeWithEmailData.employees : []} />
</Form.Item>
);
else return null;
}}
</Form.Item>

View File

@@ -59,25 +59,33 @@ export function ScheduleCalendarHeaderComponent({
{loadData && loadData.allJobsOut ? (
loadData.allJobsOut.map((j) => (
<tr key={j.id}>
<td>
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
<td style={{ padding: "2.5px" }}>
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link> (
{j.status})
</td>
<td>
<td style={{ padding: "2.5px" }}>
<OwnerNameDisplay ownerObject={j} />
</td>
<td>
{`(${(j.labhrs.aggregate.sum.mod_lb_hrs + j.larhrs.aggregate.sum.mod_lb_hrs).toFixed(
1
)} ${t("general.labels.hours")})`}
<td style={{ padding: "2.5px" }}>
{`(${j.labhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0}/${
j.larhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0
}/${(
j.labhrs.aggregate?.sum?.mod_lb_hrs +
j.larhrs.aggregate?.sum?.mod_lb_hrs
).toFixed(1)} ${t("general.labels.hours")})`}
</td>
<td>
<DateTimeFormatter>{j.scheduled_completion}</DateTimeFormatter>
<td style={{ padding: "2.5px" }}>
<DateTimeFormatter>
{j.scheduled_completion}
</DateTimeFormatter>
</td>
</tr>
))
) : (
<tr>
<td>{t("appointments.labels.nocompletingjobs")}</td>
<td style={{ padding: "2.5px" }}>
{t("appointments.labels.nocompletingjobs")}
</td>
</tr>
)}
</tbody>
@@ -92,26 +100,30 @@ export function ScheduleCalendarHeaderComponent({
{loadData && loadData.allJobsIn ? (
loadData.allJobsIn.map((j) => (
<tr key={j.id}>
<td>
<td style={{ padding: "2.5px" }}>
<Link to={`/manage/jobs/${j.id}`}>{j.ro_number}</Link>
{j.status}
</td>
<td>
<td style={{ padding: "2.5px" }}>
<OwnerNameDisplay ownerObject={j} />
</td>
<td>
{`(${(j.labhrs.aggregate.sum.mod_lb_hrs + j.larhrs.aggregate.sum.mod_lb_hrs).toFixed(
1
)} ${t("general.labels.hours")})`}
<td style={{ padding: "2.5px" }}>
{`(${j.labhrs?.aggregate?.sum.mod_lb_hrs?.toFixed(1) || 0}/${
j.larhrs?.aggregate?.sum?.mod_lb_hrs?.toFixed(1) || 0
}/${(
j.labhrs?.aggregate?.sum?.mod_lb_hrs +
j.larhrs?.aggregate?.sum?.mod_lb_hrs
).toFixed(1)} ${t("general.labels.hours")})`}
</td>
<td>
<td style={{ padding: "2.5px" }}>
<DateTimeFormatter>{j.scheduled_in}</DateTimeFormatter>
</td>
</tr>
))
) : (
<tr>
<td>{t("appointments.labels.noarrivingjobs")}</td>
<td style={{ padding: "2.5px" }}>
{t("appointments.labels.noarrivingjobs")}
</td>
</tr>
)}
</tbody>
@@ -121,27 +133,33 @@ export function ScheduleCalendarHeaderComponent({
const LoadComponent = loadData ? (
<div>
<Space align="center">
<Popover
placement={"bottom"}
content={jobsInPopup}
trigger="hover"
title={t("appointments.labels.arrivingjobs")}
>
<Icon component={MdFileDownload} style={{ color: "green" }} />
{(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(2)}
</Popover>
<Popover
placement={"bottom"}
content={jobsOutPopup}
trigger="hover"
title={t("appointments.labels.completingjobs")}
>
<Icon component={MdFileUpload} style={{ color: "red" }} />
{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(2)}
</Popover>
<ScheduleCalendarHeaderGraph loadData={loadData} />
</Space>
<Space align="center">
<Popover
placement={"bottom"}
content={jobsInPopup}
trigger="hover"
title={t("appointments.labels.arrivingjobs")}
>
<Icon component={MdFileDownload} style={{ color: "green" }} />
{(loadData.allHoursInBody || 0) &&
loadData.allHoursInBody.toFixed(1)}
/
{(loadData.allHoursInRefinish || 0) &&
loadData.allHoursInRefinish.toFixed(1)}
/{(loadData.allHoursIn || 0) && loadData.allHoursIn.toFixed(1)}
</Popover>
<Popover
placement={"bottom"}
content={jobsOutPopup}
trigger="hover"
title={t("appointments.labels.completingjobs")}
>
<Icon component={MdFileUpload} style={{ color: "red" }} />
{(loadData.allHoursOut || 0) && loadData.allHoursOut.toFixed(1)}
</Popover>
<ScheduleCalendarHeaderGraph loadData={loadData} />
</Space>
<div>
<ul style={{ listStyleType: "none", columns: "2 auto", padding: 0 }}>
{Object.keys(ATSToday).map((key, idx) => (

View File

@@ -1,25 +1,25 @@
import { useSplitTreatments } from '@splitsoftware/splitio-react';
import { Button, Card, Tabs } from 'antd';
import React from 'react';
import { useTranslation } from 'react-i18next';
import { connect } from 'react-redux';
import { createStructuredSelector } from 'reselect';
import { selectBodyshop } from '../../redux/user/user.selectors';
import ShopInfoGeneral from './shop-info.general.component';
import ShopInfoIntakeChecklistComponent from './shop-info.intake.component';
import ShopInfoLaborRates from './shop-info.laborrates.component';
import ShopInfoOrderStatusComponent from './shop-info.orderstatus.component';
import ShopInfoPartsScan from './shop-info.parts-scan';
import ShopInfoRbacComponent from './shop-info.rbac.component';
import ShopInfoResponsibilityCenterComponent from './shop-info.responsibilitycenters.component';
import ShopInfoROStatusComponent from './shop-info.rostatus.component';
import ShopInfoSchedulingComponent from './shop-info.scheduling.component';
import ShopInfoSpeedPrint from './shop-info.speedprint.component';
import { useLocation, useNavigate } from 'react-router-dom';
import ShopInfoTaskPresets from './shop-info.task-presets.component';
import queryString from 'query-string';
import InstanceRenderManager from '../../utils/instanceRenderMgr';
import ShopInfoRoGuard from './shop-info.roguard.component';
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, Card, Tabs } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ShopInfoGeneral from "./shop-info.general.component";
import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component";
import ShopInfoLaborRates from "./shop-info.laborrates.component";
import ShopInfoOrderStatusComponent from "./shop-info.orderstatus.component";
import ShopInfoPartsScan from "./shop-info.parts-scan";
import ShopInfoRbacComponent from "./shop-info.rbac.component";
import ShopInfoResponsibilityCenterComponent from "./shop-info.responsibilitycenters.component";
import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
import ShopInfoSchedulingComponent from "./shop-info.scheduling.component";
import ShopInfoSpeedPrint from "./shop-info.speedprint.component";
import { useLocation, useNavigate } from "react-router-dom";
import ShopInfoTaskPresets from "./shop-info.task-presets.component";
import queryString from "query-string";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import ShopInfoRoGuard from "./shop-info.roguard.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -47,44 +47,52 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
{
key: "general",
label: t("bodyshop.labels.shopinfo"),
children: <ShopInfoGeneral form={form} />
children: <ShopInfoGeneral form={form} />,
id: "tab-shop-general"
},
{
key: "speedprint",
label: t("bodyshop.labels.speedprint"),
children: <ShopInfoSpeedPrint form={form} />
children: <ShopInfoSpeedPrint form={form} />,
id: "tab-shop-speedprint"
},
{
key: "rbac",
label: t("bodyshop.labels.rbac"),
children: <ShopInfoRbacComponent form={form} />
children: <ShopInfoRbacComponent form={form} />,
id: "tab-shop-rbac"
},
{
key: "roStatus",
label: t("bodyshop.labels.jobstatuses"),
children: <ShopInfoROStatusComponent form={form} />
children: <ShopInfoROStatusComponent form={form} />,
id: "tab-shop-rostatus"
},
{
key: "scheduling",
label: t("bodyshop.labels.scheduling"),
children: <ShopInfoSchedulingComponent form={form} />
children: <ShopInfoSchedulingComponent form={form} />,
id: "tab-shop-scheduling"
},
{
key: "orderStatus",
label: t("bodyshop.labels.orderstatuses"),
children: <ShopInfoOrderStatusComponent form={form} />
children: <ShopInfoOrderStatusComponent form={form} />,
id: "tab-shop-orderstatus"
},
{
key: "responsibilityCenters",
label: t("bodyshop.labels.responsibilitycenters.title"),
children: <ShopInfoResponsibilityCenterComponent form={form} />
children: <ShopInfoResponsibilityCenterComponent form={form} />,
id: "tab-shop-responsibilitycenters"
},
...InstanceRenderManager({
imex: [
{
key: "checklists",
label: t("bodyshop.labels.checklists"),
children: <ShopInfoIntakeChecklistComponent form={form} />
children: <ShopInfoIntakeChecklistComponent form={form} />,
id: "tab-shop-checklists"
}
],
rome: "USE_IMEX",
@@ -93,14 +101,16 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
{
key: "laborrates",
label: t("bodyshop.labels.laborrates"),
children: <ShopInfoLaborRates form={form} />
children: <ShopInfoLaborRates form={form} />,
id: "tab-shop-laborrates"
},
...(CriticalPartsScanning.treatment === "on"
? [
{
key: "partsscan",
label: t("bodyshop.labels.partsscan"),
children: <ShopInfoPartsScan form={form} />
children: <ShopInfoPartsScan form={form} />,
id: "tab-shop-partsscan"
}
]
: []),
@@ -109,21 +119,23 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
{
key: "task-presets",
label: t("bodyshop.labels.task-presets"),
children: <ShopInfoTaskPresets form={form} />
children: <ShopInfoTaskPresets form={form} />,
id: "tab-shop-task-presets"
}
]
: []),
...InstanceRenderManager({
imex: [
{
key: 'roguard',
label: t('bodyshop.labels.roguard.title'),
children: <ShopInfoRoGuard form={form} />,
},
],
rome: 'USE_IMEX',
promanager: [],
}),
...InstanceRenderManager({
imex: [
{
key: "roguard",
label: t("bodyshop.labels.roguard.title"),
children: <ShopInfoRoGuard form={form} />,
id: "tab-shop-roguard"
}
],
rome: "USE_IMEX",
promanager: []
})
];
return (
<Card

View File

@@ -13,6 +13,7 @@ import PhoneFormItem, { PhoneItemFormatterValidation } from "../form-items-forma
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import FeatureWrapper, { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
// TODO: Client Update, this might break
const timeZonesList = Intl.supportedValuesOf("timeZone");
const mapStateToProps = createStructuredSelector({
@@ -144,285 +145,289 @@ export function ShopInfoGeneral({ form, bodyshop }) {
<InputNumber min={0} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")} id="accountingsetup">
<Form.Item label={t("bodyshop.labels.qbo")} valuePropName="checked" name={["accountingconfig", "qbo"]}>
<Switch />
</Form.Item>
{InstanceRenderManager({
imex: (
<Form.Item shouldUpdate noStyle>
{() => (
<FeatureWrapper featureName="export" noauth={() => null}>
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")} id="accountingsetup">
<Form.Item label={t("bodyshop.labels.qbo")} valuePropName="checked" name={["accountingconfig", "qbo"]}>
<Switch />
</Form.Item>
{InstanceRenderManager({
imex: (
<Form.Item shouldUpdate noStyle>
{() => (
<Form.Item
label={t("bodyshop.labels.qbo_usa")}
shouldUpdate
valuePropName="checked"
name={["accountingconfig", "qbo_usa"]}
>
<Switch disabled={!form.getFieldValue(["accountingconfig", "qbo"])} />
</Form.Item>
)}
</Form.Item>
)
})}
<Form.Item label={t("bodyshop.labels.qbo_departmentid")} name={["accountingconfig", "qbo_departmentid"]}>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.accountingtiers")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["accountingconfig", "tiers"]}
>
<Radio.Group>
<Radio value={2}>2</Radio>
<Radio value={3}>3</Radio>
</Radio.Group>
</Form.Item>
<Form.Item shouldUpdate>
{() => {
return (
<Form.Item
label={t("bodyshop.labels.qbo_usa")}
label={t("bodyshop.labels.2tiersetup")}
shouldUpdate
valuePropName="checked"
name={["accountingconfig", "qbo_usa"]}
rules={[
{
required: form.getFieldValue(["accountingconfig", "tiers"]) === 2
//message: t("general.validation.required"),
}
]}
name={["accountingconfig", "twotierpref"]}
>
<Switch disabled={!form.getFieldValue(["accountingconfig", "qbo"])} />
<Radio.Group disabled={form.getFieldValue(["accountingconfig", "tiers"]) === 3}>
<Radio value="name">{t("bodyshop.labels.2tiername")}</Radio>
<Radio value="source">{t("bodyshop.labels.2tiersource")}</Radio>
</Radio.Group>
</Form.Item>
)}
</Form.Item>
)
})}
<Form.Item label={t("bodyshop.labels.qbo_departmentid")} name={["accountingconfig", "qbo_departmentid"]}>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.accountingtiers")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["accountingconfig", "tiers"]}
>
<Radio.Group>
<Radio value={2}>2</Radio>
<Radio value={3}>3</Radio>
</Radio.Group>
</Form.Item>
<Form.Item shouldUpdate>
{() => {
return (
);
}}
</Form.Item>
<Form.Item
label={t("bodyshop.labels.printlater")}
valuePropName="checked"
name={["accountingconfig", "printlater"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.emaillater")}
valuePropName="checked"
name={["accountingconfig", "emaillater"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.inhousevendorid")}
name={"inhousevendorid"}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.default_adjustment_rate")}
name={"default_adjustment_rate"}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={2} />
</Form.Item>
{InstanceRenderManager({
imex: (
<Form.Item label={t("bodyshop.fields.federal_tax_id")} name="federal_tax_id">
<Input />
</Form.Item>
)
})}
<Form.Item label={t("bodyshop.fields.state_tax_id")} name="state_tax_id">
<Input />
</Form.Item>
{InstanceRenderManager({
imex: (
<Form.Item
label={t("bodyshop.labels.2tiersetup")}
shouldUpdate
label={t("bodyshop.fields.invoice_federal_tax_rate")}
name={["bill_tax_rates", "federal_tax_rate"]}
rules={[
{
required: form.getFieldValue(["accountingconfig", "tiers"]) === 2
required: true
//message: t("general.validation.required"),
}
]}
name={["accountingconfig", "twotierpref"]}
>
<Radio.Group disabled={form.getFieldValue(["accountingconfig", "tiers"]) === 3}>
<Radio value="name">{t("bodyshop.labels.2tiername")}</Radio>
<Radio value="source">{t("bodyshop.labels.2tiersource")}</Radio>
</Radio.Group>
<InputNumber />
</Form.Item>
);
}}
</Form.Item>
<Form.Item
label={t("bodyshop.labels.printlater")}
valuePropName="checked"
name={["accountingconfig", "printlater"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.emaillater")}
valuePropName="checked"
name={["accountingconfig", "emaillater"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.inhousevendorid")}
name={"inhousevendorid"}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.default_adjustment_rate")}
name={"default_adjustment_rate"}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={2} />
</Form.Item>
{InstanceRenderManager({
imex: (
<Form.Item label={t("bodyshop.fields.federal_tax_id")} name="federal_tax_id">
<Input />
</Form.Item>
)
})}
<Form.Item label={t("bodyshop.fields.state_tax_id")} name="state_tax_id">
<Input />
</Form.Item>
{InstanceRenderManager({
imex: (
<Form.Item
label={t("bodyshop.fields.invoice_federal_tax_rate")}
name={["bill_tax_rates", "federal_tax_rate"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
)
})}
<Form.Item
label={t("bodyshop.fields.invoice_state_tax_rate")}
name={["bill_tax_rates", "state_tax_rate"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.invoice_local_tax_rate")}
name={["bill_tax_rates", "local_tax_rate"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
<Form.Item
name={["md_payment_types"]}
label={t("bodyshop.fields.md_payment_types")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item
name={["md_categories"]}
label={t("bodyshop.fields.md_categories")}
rules={[
{
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item name={["enforce_class"]} label={t("bodyshop.fields.enforce_class")} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField1"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 1 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField2"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 2 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField3"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 3 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["md_classes"]}
label={t("bodyshop.fields.md_classes")}
rules={[
({ getFieldValue }) => {
return {
required: getFieldValue("enforce_class"),
)
})}
<Form.Item
label={t("bodyshop.fields.invoice_state_tax_rate")}
name={["bill_tax_rates", "state_tax_rate"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.invoice_local_tax_rate")}
name={["bill_tax_rates", "local_tax_rate"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber />
</Form.Item>
<Form.Item
name={["md_payment_types"]}
label={t("bodyshop.fields.md_payment_types")}
rules={[
{
required: true,
//message: t("general.validation.required"),
type: "array"
};
}
]}
>
<Select mode="tags" />
</Form.Item>
{ClosingPeriod.treatment === "on" && (
<>
<Form.Item
name={["accountingconfig", "ClosingPeriod"]}
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
>
<DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} />
</Form.Item>
</>
)}
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.scoreboardsetup")} id="scoreboardsetup">
<Form.Item
label={t("bodyshop.fields.dailypainttarget")}
name={["scoreboard_target", "dailyPaintTarget"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={0} />
</Form.Item>
}
]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item
name={["md_categories"]}
label={t("bodyshop.fields.md_categories")}
rules={[
{
//message: t("general.validation.required"),
type: "array"
}
]}
>
<Select mode="tags" />
</Form.Item>
<Form.Item name={["enforce_class"]} label={t("bodyshop.fields.enforce_class")} valuePropName="checked">
<Switch />
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField1"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 1 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField2"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 2 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["accountingconfig", "ReceivableCustomField3"]}
label={t("bodyshop.fields.ReceivableCustomField", { number: 3 })}
>
{ReceivableCustomFieldSelect}
</Form.Item>
<Form.Item
name={["md_classes"]}
label={t("bodyshop.fields.md_classes")}
rules={[
({ getFieldValue }) => {
return {
required: getFieldValue("enforce_class"),
//message: t("general.validation.required"),
type: "array"
};
}
]}
>
<Select mode="tags" />
</Form.Item>
{ClosingPeriod.treatment === "on" && (
<>
<Form.Item
name={["accountingconfig", "ClosingPeriod"]}
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")}
>
<DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} />
</Form.Item>
</>
)}
</LayoutFormRow>
</FeatureWrapper>
<FeatureWrapper featureName="scoreboard" noauth={() => null}>
<LayoutFormRow header={t("bodyshop.labels.scoreboardsetup")} id="scoreboardsetup">
<Form.Item
label={t("bodyshop.fields.dailypainttarget")}
name={["scoreboard_target", "dailyPaintTarget"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.dailybodytarget")}
name={["scoreboard_target", "dailyBodyTarget"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.dailybodytarget")}
name={["scoreboard_target", "dailyBodyTarget"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.lastnumberworkingdays")}
name={["scoreboard_target", "lastNumberWorkingDays"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} max={12} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.ignoreblockeddays")}
name={["scoreboard_target", "ignoreblockeddays"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.prodtargethrs")}
name={["prodtargethrs"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={1} precision={1} />
</Form.Item>
</LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.lastnumberworkingdays")}
name={["scoreboard_target", "lastNumberWorkingDays"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={0} max={12} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.ignoreblockeddays")}
name={["scoreboard_target", "ignoreblockeddays"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.prodtargethrs")}
name={["prodtargethrs"]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<InputNumber min={1} precision={1} />
</Form.Item>
</LayoutFormRow>
</FeatureWrapper>
<LayoutFormRow header={t("bodyshop.labels.systemsettings")} id="systemsettings">
<Form.Item
name={["md_referral_sources"]}
@@ -567,27 +572,32 @@ export function ShopInfoGeneral({ form, bodyshop }) {
>
<Select mode="tags" />
</Form.Item>
<Form.Item
name={["tt_allow_post_to_invoiced"]}
label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["tt_enforce_hours_for_tech_console"]}
label={t("bodyshop.fields.tt_enforce_hours_for_tech_console")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["bill_allow_post_to_closed"]}
label={t("bodyshop.fields.bill_allow_post_to_closed")}
valuePropName="checked"
>
<Switch />
</Form.Item>
{HasFeatureAccess({ featureName: "timetickets", bodyshop }) && (
<>
<Form.Item
name={["tt_allow_post_to_invoiced"]}
label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["tt_enforce_hours_for_tech_console"]}
label={t("bodyshop.fields.tt_enforce_hours_for_tech_console")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["bill_allow_post_to_closed"]}
label={t("bodyshop.fields.bill_allow_post_to_closed")}
valuePropName="checked"
>
<Switch />
</Form.Item>
</>
)}
<Form.Item
name={["md_ded_notes"]}
label={t("bodyshop.fields.md_ded_notes")}
@@ -1042,111 +1052,118 @@ export function ShopInfoGeneral({ form, bodyshop }) {
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")} id="md_ccc_rates">
<Form.List name={["md_ccc_rates"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
<Input />
</Form.Item>
<Form.Item label={t("contracts.fields.actax")} key={`${index}actax`} name={[field.name, "actax"]}>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.dailyfreekm")}
key={`${index}dailyfreekm`}
name={[field.name, "dailyfreekm"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.refuelcharge")}
key={`${index}refuelcharge`}
name={[field.name, "refuelcharge"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.excesskmrate")}
key={`${index}excesskmrate`}
name={[field.name, "excesskmrate"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.cleanupcharge")}
key={`${index}cleanupcharge`}
name={[field.name, "cleanupcharge"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.damagewaiver")}
key={`${index}damagewaiver`}
name={[field.name, "damagewaiver"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.federaltax")}
key={`${index}federaltax`}
name={[field.name, "federaltax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.statetax")}
key={`${index}statetax`}
name={[field.name, "statetax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.localtax")}
key={`${index}localtax`}
name={[field.name, "localtax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.coverage")}
key={`${index}coverage`}
name={[field.name, "coverage"]}
>
<InputNumber precision={2} />
</Form.Item>
<FeatureWrapper featureName="courtesycars" noauth={() => null}>
<LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")} id="md_ccc_rates">
<Form.List name={["md_ccc_rates"]}>
{(fields, { add, remove, move }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow noDivider>
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
<Input />
</Form.Item>
<Form.Item
label={t("contracts.fields.actax")}
key={`${index}actax`}
name={[field.name, "actax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.dailyfreekm")}
key={`${index}dailyfreekm`}
name={[field.name, "dailyfreekm"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.refuelcharge")}
key={`${index}refuelcharge`}
name={[field.name, "refuelcharge"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.excesskmrate")}
key={`${index}excesskmrate`}
name={[field.name, "excesskmrate"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.cleanupcharge")}
key={`${index}cleanupcharge`}
name={[field.name, "cleanupcharge"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.damagewaiver")}
key={`${index}damagewaiver`}
name={[field.name, "damagewaiver"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.federaltax")}
key={`${index}federaltax`}
name={[field.name, "federaltax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.statetax")}
key={`${index}statetax`}
name={[field.name, "statetax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.localtax")}
key={`${index}localtax`}
name={[field.name, "localtax"]}
>
<InputNumber precision={2} />
</Form.Item>
<Form.Item
label={t("contracts.fields.coverage")}
key={`${index}coverage`}
name={[field.name, "coverage"]}
>
<InputNumber precision={2} />
</Form.Item>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
<Space wrap>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</LayoutFormRow>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</LayoutFormRow>
</div>
);
}}
</Form.List>
</LayoutFormRow>
</FeatureWrapper>
<LayoutFormRow grow header={t("bodyshop.fields.md_jobline_presets")} id="md_jobline_presets">
<Form.List name={["md_jobline_presets"]}>
{(fields, { add, remove, move }) => {

View File

@@ -1,12 +1,13 @@
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Form, InputNumber } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -29,210 +30,219 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
return (
<RbacWrapper action="shop:rbac">
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.exportlog")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:exportlog"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payables")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:payables"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payments")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:payments"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.receivables")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:receivables"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.delete")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:delete"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.enter")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:enter"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.reexport")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:reexport"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.contracts.create")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:create"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.contracts.detail")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:detail"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.contracts.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.create")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:create"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.detail")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:detail"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.csi.export")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "csi:export"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.csi.page")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "csi:page"]}
>
<InputNumber />
</Form.Item>
{...HasFeatureAccess({ featureName: "export", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.accounting.exportlog")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:exportlog"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payables")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:payables"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payments")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:payments"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.accounting.receivables")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "accounting:receivables"]}
>
<InputNumber />
</Form.Item>
]:[]}
{...HasFeatureAccess({ featureName: "bills", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.bills.delete")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:delete"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.enter")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:enter"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.reexport")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:reexport"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.bills.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:view"]}
>
<InputNumber />
</Form.Item>
]:[]}
{...HasFeatureAccess({ featureName: "courtesycars", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.contracts.create")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:create"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.contracts.detail")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:detail"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.contracts.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "contracts:list"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.create")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:create"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.detail")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:detail"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.courtesycar.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "courtesycar:list"]}
>
<InputNumber />
</Form.Item>
]:[]}
{...HasFeatureAccess({ featureName: "csi", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.csi.export")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "csi:export"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.csi.page")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "csi:page"]}
>
<InputNumber />
</Form.Item>
]:[]}
<Form.Item
label={t("bodyshop.fields.rbac.employees.page")}
rules={[
@@ -425,18 +435,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.employees.page")}
rules={[
@@ -510,18 +508,21 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.production.board")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "production:board"]}
>
<InputNumber />
</Form.Item>
{HasFeatureAccess({ featureName: "visualboard", bodyshop }) && (
<Form.Item
label={t("bodyshop.fields.rbac.production.board")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "production:board"]}
>
<InputNumber />
</Form.Item>
)}
<Form.Item
label={t("bodyshop.fields.rbac.production.list")}
rules={[
@@ -546,102 +547,142 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.scoreboard.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "scoreboard:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shiftclock.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shiftclock:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.config")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shop:config"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.edit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:edit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:editcommitted"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.ttapprovals.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "ttapprovals:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "ttapprovals:approve"]}
>
<InputNumber />
</Form.Item>
{HasFeatureAccess({ featureName: "scoreboard", bodyshop }) && (
<Form.Item
label={t("bodyshop.fields.rbac.scoreboard.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "scoreboard:view"]}
>
<InputNumber />
</Form.Item>
)}
{...HasFeatureAccess({ featureName: "timetickets", bodyshop }) ? [
<Form.Item
label={t("bodyshop.fields.rbac.shiftclock.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shiftclock:view"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.shop.config")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shop:config"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.edit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:edit"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.editcommitted")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:editcommitted"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.ttapprovals.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "ttapprovals:view"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.ttapprovals.approve")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "ttapprovals:approve"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.enter")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:enter"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:list"]}
>
<InputNumber />
</Form.Item>,
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>
]:[]}
<Form.Item
label={t("bodyshop.fields.rbac.shop.vendors")}
rules={[
@@ -690,7 +731,7 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
>
<InputNumber />
</Form.Item>
<Form.Item
{/* <Form.Item
label={t("bodyshop.fields.rbac.shop.templates")}
rules={[
{
@@ -701,79 +742,22 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
name={["md_rbac", "shop:templates"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.vendors")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "shop:vendors"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.temporarydocs.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "temporarydocs:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.edit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:edit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.enter")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:enter"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.list")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>
</Form.Item> */}
{HasFeatureAccess({ featureName: "media", bodyshop }) && (
<Form.Item
label={t("bodyshop.fields.rbac.temporarydocs.view")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
name={["md_rbac", "temporarydocs:view"]}
>
<InputNumber />
</Form.Item>
)}
<Form.Item
label={t("bodyshop.fields.rbac.users.editaccess")}
rules={[

View File

@@ -339,7 +339,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
{(fields, { add, remove, move }) => {
return (
<div>
<LayoutFormRow>
<Space size='large' wrap>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<Space direction="vertical">
@@ -386,7 +386,7 @@ export function ShopInfoROStatusComponent({ bodyshop, form }) {
</Space>
</Form.Item>
))}
</LayoutFormRow>
</Space>
<Form.Item>
<Button
type="dashed"

View File

@@ -1,4 +1,4 @@
import { Button, Result } from "antd";
import { Result } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -16,19 +16,5 @@ export default connect(mapStateToProps, mapDispatchToProps)(ShopSubStatus);
export function ShopSubStatus({ bodyshop }) {
const { t } = useTranslation();
const { sub_status } = bodyshop;
return (
<Result
status="403"
title={t(`general.labels.sub_status.${sub_status}`)}
extra={
<Button
onClick={() => {
alert("Not implemented yet.");
}}
>
{t("general.actions.submitticket")}
</Button>
}
/>
);
return <Result status="403" title={t(`general.labels.sub_status.${sub_status}`)} />;
}

View File

@@ -0,0 +1,382 @@
import { Button, Card, Space, Switch, Table } from "antd";
import queryString from "query-string";
import React, { useCallback, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { pageLimit } from "../../utils/config";
import dayjs from "../../utils/day";
import {
CheckCircleFilled,
CheckCircleOutlined,
DeleteFilled,
DeleteOutlined,
EditFilled,
ExclamationCircleFilled,
PlusCircleFilled,
SyncOutlined
} from "@ant-design/icons";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
import { connect } from "react-redux";
import { setModalContext } from "../../redux/modals/modals.actions";
/**
* Task List Component
* @param dueDate
* @returns {Element}
* @constructor
*/
const DueDateRecord = ({ dueDate }) => {
if (!dueDate) return <></>;
const dueDateDayjs = dayjs(dueDate);
const relativeDueDate = dueDateDayjs.fromNow();
const isBeforeToday = dueDateDayjs.isBefore(dayjs());
return (
<div title={relativeDueDate} style={isBeforeToday ? { color: "red" } : {}}>
<DateFormatter>{dueDate}</DateFormatter>
</div>
);
};
const RemindAtRecord = ({ remindAt }) => {
if (!remindAt) return <></>;
const remindAtDayjs = dayjs(remindAt);
const relativeRemindAtDate = remindAtDayjs.fromNow();
const isBeforeToday = remindAtDayjs.isBefore(dayjs());
return (
<div title={relativeRemindAtDate} style={isBeforeToday ? { color: "red" } : {}}>
<DateTimeFormatter>{remindAt}</DateTimeFormatter>
</div>
);
};
/**
* Priority Label Component
* @param priority
* @returns {Element}
* @constructor
*/
const PriorityLabel = ({ priority }) => {
switch (priority) {
case 1:
return (
<div>
High <ExclamationCircleFilled style={{ marginLeft: "5px", color: "red" }} />
</div>
);
case 2:
return (
<div>
Medium <ExclamationCircleFilled style={{ marginLeft: "5px", color: "yellow" }} />
</div>
);
case 3:
return (
<div>
Low <ExclamationCircleFilled style={{ marginLeft: "5px", color: "green" }} />
</div>
);
default:
return (
<div>
None <ExclamationCircleFilled style={{ marginLeft: "5px" }} />
</div>
);
}
};
const mapDispatchToProps = (dispatch) => ({
// Existing dispatch props...
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
});
const mapStateToProps = (state) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(TaskListComponent);
function TaskListComponent({
bodyshop,
loading,
tasks,
total,
titleTranslation,
refetch,
toggleCompletedStatus,
setTaskUpsertContext,
toggleDeletedStatus,
relationshipType,
relationshipId,
onlyMine,
parentJobId,
query,
showRo = true
}) {
const { t } = useTranslation();
const location = useLocation();
const search = queryString.parse(useLocation().search);
// Extract Query Params
const { page, sortcolumn, sortorder, deleted, completed, mine } = search;
const history = useNavigate();
const columns = [];
useEffect(() => {
// This is a hack to force the page to change if the query params change (partssublet for example)
}, [location]);
columns.push({
title: t("tasks.fields.created_at"),
dataIndex: "created_at",
key: "created_at",
width: "10%",
defaultSortOrder: "descend",
sorter: true,
sortOrder: sortcolumn === "created_at" && sortorder,
render: (text, record) => <DateTimeFormatter>{record.created_at}</DateTimeFormatter>
});
if (!onlyMine) {
columns.push({
title: t("tasks.fields.assigned_to"),
dataIndex: "assigned_to",
key: "assigned_to",
width: "8%",
sorter: true,
sortOrder: sortcolumn === "assigned_to" && sortorder,
render: (text, record) => {
const employee = bodyshop?.employees?.find((e) => e.id === record.assigned_to);
return employee ? `${employee.first_name} ${employee.last_name}` : t("general.labels.na");
}
});
}
if (showRo) {
columns.push({
title: t("tasks.fields.job.ro_number"),
dataIndex: ["job", "ro_number"],
key: "job.ro_number",
width: "8%",
render: (text, record) =>
record.job ? (
<Link to={`/manage/jobs/${record.job.id}?tab=tasks`}>{record.job.ro_number || t("general.labels.na")}</Link>
) : (
t("general.labels.na")
)
});
}
columns.push(
{
title: t("tasks.fields.jobline"),
dataIndex: ["jobline", "id"],
key: "jobline.id",
width: "8%",
render: (text, record) => record?.jobline?.line_desc || ""
},
{
title: t("tasks.fields.parts_order"),
dataIndex: ["parts_order", "id"],
key: "part_order.id",
width: "8%",
render: (text, record) =>
record.parts_order ? (
<Link to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
{record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name
? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}`
: t("general.labels.na")}
</Link>
) : (
""
)
},
{
title: t("tasks.fields.bill"),
dataIndex: ["bill", "id"],
key: "bill.id",
width: "10%",
render: (text, record) =>
record.bill ? (
<Link to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
{record.bill.invoice_number && record.bill.vendor && record.bill.vendor.name
? `${record.bill.invoice_number} - ${record.bill.vendor.name}`
: t("general.labels.na")}
</Link>
) : (
""
)
},
{
title: t("tasks.fields.title"),
dataIndex: "title",
key: "title",
sorter: true,
sortOrder: sortcolumn === "title" && sortorder
},
{
title: t("tasks.fields.due_date"),
dataIndex: "due_date",
key: "due_date",
sorter: true,
sortOrder: sortcolumn === "due_date" && sortorder,
width: "8%",
render: (text, record) => <DueDateRecord dueDate={record.due_date} />
},
{
title: t("tasks.fields.remind_at"),
dataIndex: "remind_at",
key: "remind_at",
sorter: true,
sortOrder: sortcolumn === "remind_at" && sortorder,
width: "10%",
render: (text, record) => <RemindAtRecord remindAt={record.remind_at} />
},
{
title: t("tasks.fields.priority"),
dataIndex: "priority",
key: "priority",
sorter: true,
sortOrder: sortcolumn === "priority" && sortorder,
width: "8%",
render: (text, record) => <PriorityLabel priority={record.priority} />
},
{
title: t("tasks.fields.actions"),
key: "toggleCompleted",
width: "5%",
render: (text, record) => (
<Space direction="horizontal">
<Button
title={t("tasks.buttons.edit")}
onClick={() => {
setTaskUpsertContext({
context: {
existingTask: record,
query
}
});
}}
>
<EditFilled />
</Button>
<Button
title={t("tasks.buttons.complete")}
onClick={() => toggleCompletedStatus(record.id, record.completed)}
>
{record.completed ? <CheckCircleOutlined /> : <CheckCircleFilled />}
</Button>
<Button title={t("tasks.buttons.delete")} onClick={() => toggleDeletedStatus(record.id, record.deleted)}>
{record.deleted ? <DeleteOutlined /> : <DeleteFilled />}
</Button>
</Space>
)
}
);
const handleCreateTask = useCallback(() => {
setTaskUpsertContext({
actions: {},
context: {
jobid: parentJobId,
[relationshipType]: relationshipId,
query
}
});
}, [parentJobId, relationshipId, relationshipType, setTaskUpsertContext, query]);
const handleTableChange = (pagination, filters, sorter) => {
search.page = pagination.current;
search.sortcolumn = sorter.columnKey;
search.sortorder = sorter.order;
history({ search: queryString.stringify(search) });
};
const handleSwitchChange = useCallback(
(param, value) => {
if (value) {
search[param] = "true";
} else {
delete search[param];
}
history({ search: queryString.stringify(search) });
},
[history, search]
);
const expandableRow = (record) => {
return (
<Card title={t("tasks.fields.description")} size="small">
{record.description}
</Card>
);
};
/**
* Extra actions for the tasks
* @type {Function}
*/
const tasksExtra = useCallback(() => {
return (
<Space direction="horizontal">
{!onlyMine && (
<Switch
checkedChildren={t("tasks.buttons.myTasks")}
unCheckedChildren={t("tasks.buttons.allTasks")}
title={t("tasks.titles.mine")}
checked={mine === "true"}
onChange={(value) => handleSwitchChange("mine", value)}
/>
)}
<Switch
checkedChildren={<CheckCircleFilled />}
unCheckedChildren={<CheckCircleOutlined />}
title={t("tasks.titles.completed")}
checked={completed === "true"}
onChange={(value) => handleSwitchChange("completed", value)}
/>
<Switch
checkedChildren={<DeleteFilled />}
unCheckedChildren={<DeleteOutlined />}
title={t("tasks.titles.deleted")}
checked={deleted === "true"}
onChange={(value) => handleSwitchChange("deleted", value)}
/>
<Button title={t("tasks.buttons.refresh")} onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Button title={t("tasks.buttons.create")} onClick={handleCreateTask}>
<PlusCircleFilled />
{t("tasks.buttons.create")}
</Button>
</Space>
);
}, [refetch, deleted, completed, mine, onlyMine, t, handleSwitchChange, handleCreateTask]);
return (
<Card title={titleTranslation} extra={tasksExtra()}>
<Table
loading={loading}
pagination={{
pageSize: pageLimit,
current: parseInt(page || 1),
total: total,
responsive: true,
showQuickJumper: true
}}
columns={columns}
rowKey="id"
scroll={{ x: true }}
dataSource={tasks}
onChange={handleTableChange}
expandable={{
expandedRowRender: expandableRow,
rowExpandable: (record) => record.description
}}
/>
</Card>
);
}

View File

@@ -0,0 +1,193 @@
import queryString from "query-string";
import { useLocation } from "react-router-dom";
import { useMutation, useQuery } from "@apollo/client";
import { MUTATION_TOGGLE_TASK_COMPLETED, MUTATION_TOGGLE_TASK_DELETED } from "../../graphql/tasks.queries.js";
import { pageLimit } from "../../utils/config.js";
import AlertComponent from "../alert/alert.component.jsx";
import React from "react";
import TaskListComponent from "./task-list.component.jsx";
import { notification } from "antd";
import { useTranslation } from "react-i18next";
import { connect, useDispatch } from "react-redux";
import { insertAuditTrail } from "../../redux/application/application.actions.js";
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
import dayjs from "../../utils/day";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(TaskListContainer);
export function TaskListContainer({
bodyshop,
titleTranslation,
query,
relationshipType,
relationshipId,
currentUser,
onlyMine,
parentJobId,
showRo = true,
disableJobRefetch = false
}) {
const { t } = useTranslation();
const searchParams = queryString.parse(useLocation().search);
const {
page,
sortcolumn,
sortorder,
deleted,
completed //mine
} = searchParams;
const dispatch = useDispatch();
const { loading, error, data, refetch } = useQuery(query[Object.keys(query)[0]], {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
variables: {
bodyshop: bodyshop.id,
[relationshipType]: relationshipId,
deleted: deleted === "true",
completed: completed === "true", //TODO: Find where mine is set.
assigned_to: onlyMine ? bodyshop?.employees?.find((e) => e.user_email === currentUser.email)?.id : undefined, // replace currentUserID with the actual ID of the current user
offset: page ? (page - 1) * pageLimit : 0,
limit: pageLimit,
order: [
{
[sortcolumn || "created_at"]: sortorder ? (sortorder === "descend" ? "desc" : "asc") : "desc"
}
]
}
});
/**
* Toggle task completed mutation
*/
const [toggleTaskCompleted] = useMutation(MUTATION_TOGGLE_TASK_COMPLETED);
/**
* Toggle task completed status
* @param id
* @param currentStatus
* @returns {Promise<void>}
*/
const toggleCompletedStatus = async (id, currentStatus) => {
const completed_at = !currentStatus ? dayjs().toISOString() : null;
try {
const toggledTaskObject = {
variables: {
id: id,
completed: !currentStatus,
completed_at: completed_at
},
refetchQueries: [Object.keys(query)[0]]
};
if (!disableJobRefetch) {
toggledTaskObject.refetchQueries.push("GET_JOB_BY_PK");
}
const toggledTask = await toggleTaskCompleted(toggledTaskObject);
if (!toggledTask.errors) {
dispatch(
insertAuditTrail({
jobid: toggledTask.data.update_tasks_by_pk.jobid,
operation: toggledTask?.data?.update_tasks_by_pk?.completed
? AuditTrailMapping.tasksCompleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email)
: AuditTrailMapping.tasksUncompleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email),
type: toggledTask?.data?.update_tasks_by_pk?.completed ? "tasksCompleted" : "tasksUncompleted"
})
);
}
notification["success"]({
message: t("tasks.successes.completed")
});
} catch (err) {
notification["error"]({
message: t("tasks.failures.completed")
});
}
};
/**
* Toggle task deleted mutation
*/
const [toggleTaskDeleted] = useMutation(MUTATION_TOGGLE_TASK_DELETED);
/**
* Toggle task deleted status
* @param id
* @param currentStatus
* @returns {Promise<void>}
*/
const toggleDeletedStatus = async (id, currentStatus) => {
const deleted_at = !currentStatus ? dayjs().toISOString() : null;
try {
const toggledTaskObject = {
variables: {
id: id,
deleted: !currentStatus,
deleted_at: deleted_at
},
refetchQueries: [Object.keys(query)[0]]
};
if (!disableJobRefetch) {
toggledTaskObject.refetchQueries.push("GET_JOB_BY_PK");
}
const toggledTask = await toggleTaskDeleted(toggledTaskObject);
if (!toggledTask.errors) {
dispatch(
insertAuditTrail({
jobid: toggledTask.data.update_tasks_by_pk.jobid,
operation: toggledTask?.data?.update_tasks_by_pk?.deleted
? AuditTrailMapping.tasksDeleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email)
: AuditTrailMapping.tasksUndeleted(toggledTask.data.update_tasks_by_pk.title, currentUser.email),
type: toggledTask?.data?.update_tasks_by_pk?.deleted ? "tasksDeleted" : "tasksUndeleted"
})
);
}
notification["success"]({
message: t("tasks.successes.deleted")
});
} catch (err) {
notification["error"]({
message: t("tasks.failures.deleted")
});
}
};
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<TaskListComponent
loading={loading}
tasks={data ? data.tasks : null}
total={data ? data.tasks_aggregate.aggregate.count : 0}
titleTranslation={t(titleTranslation || "tasks.title")}
refetch={refetch}
toggleCompletedStatus={toggleCompletedStatus}
toggleDeletedStatus={toggleDeletedStatus}
relationshipType={relationshipType}
relationshipId={relationshipId}
onlyMine={onlyMine}
showRo={showRo}
parentJobId={parentJobId}
bodyshop={bodyshop}
query={query}
/>
);
}

View File

@@ -0,0 +1,299 @@
import { Col, Form, Input, Row, Select, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { FormDatePicker } from "../form-date-picker/form-date-picker.component.jsx";
import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
import dayjs from "../../utils/day";
import { connect } from "react-redux";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component.jsx";
import JobSearchSelectComponent from "../job-search-select/job-search-select.component.jsx";
import { FormDateTimePickerEnhanced } from "../form-date-time-picker-enhanced/form-date-time-picker-enhanced.component.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser
});
const mapDispatchToProps = () => ({});
export default connect(mapStateToProps, mapDispatchToProps)(TaskUpsertModalComponent);
export function TaskUpsertModalComponent({
form,
bodyshop,
currentUser,
selectedJobId,
setSelectedJobId,
selectedJobDetails,
existingTask,
loading,
error
}) {
const { t } = useTranslation();
const datePickerPresets = [
{ label: t("tasks.date_presets.today"), value: dayjs().add(1, "hour") },
{ label: t("tasks.date_presets.tomorrow"), value: dayjs().add(1, "day").startOf("day") },
{ label: t("tasks.date_presets.next_week"), value: dayjs().add(1, "week").startOf("day") },
{ label: t("tasks.date_presets.two_weeks"), value: dayjs().add(2, "weeks").startOf("day") },
{ label: t("tasks.date_presets.three_weeks"), value: dayjs().add(3, "weeks").startOf("day") },
{ label: t("tasks.date_presets.one_month"), value: dayjs().add(1, "month").startOf("day") },
{ label: t("tasks.date_presets.three_months"), value: dayjs().add(3, "month").startOf("day") }
];
const generatePresets = (job) => {
if (!job || !selectedJobDetails) return datePickerPresets; // return default presets if no job selected
const relativePresets = [];
if (selectedJobDetails?.scheduled_completion) {
const scheduledCompletion = dayjs(selectedJobDetails.scheduled_completion);
if (scheduledCompletion.isAfter(dayjs())) {
relativePresets.push(
{
label: `${t("tasks.date_presets.completion")} -1 ${t("tasks.date_presets.day")}`,
value: scheduledCompletion.subtract(1, "day").startOf("day")
},
{
label: `${t("tasks.date_presets.completion")} -2 ${t("tasks.date_presets.days")}`,
value: scheduledCompletion.subtract(2, "day").startOf("day")
},
{
label: `${t("tasks.date_presets.completion")} -3 ${t("tasks.date_presets.days")}`,
value: scheduledCompletion.subtract(3, "day").startOf("day")
}
);
}
}
if (selectedJobDetails?.scheduled_delivery) {
const scheduledDelivery = dayjs(selectedJobDetails.scheduled_delivery);
if (scheduledDelivery.isAfter(dayjs())) {
relativePresets.push(
{
label: `${t("tasks.date_presets.delivery")} -1 ${t("tasks.date_presets.day")}`,
value: scheduledDelivery.subtract(1, "day").startOf("day")
},
{
label: `${t("tasks.date_presets.delivery")} -2 ${t("tasks.date_presets.days")}`,
value: scheduledDelivery.subtract(2, "day").startOf("day")
},
{
label: `${t("tasks.date_presets.delivery")} -3 ${t("tasks.date_presets.days")}`,
value: scheduledDelivery.subtract(3, "day").startOf("day")
}
);
}
}
return [...relativePresets, ...datePickerPresets];
};
const clearRelations = () => {
form.setFieldsValue({
billid: null,
partsorderid: null,
joblineid: null
});
};
/**
* Change the selected job id
* @param jobId
*/
const changeJobId = (jobId) => {
setSelectedJobId(jobId || null);
// Reset the form fields when selectedJobId changes
clearRelations();
};
if (loading || error) return <LoadingSkeleton active />;
return (
<>
<Row gutter={[16, 16]}>
<Col span={16}>
<Form.Item
label={t("tasks.fields.title")}
name="title"
rules={[
{
required: true
}
]}
>
<Input placeholder={t("tasks.fields.title")} />
</Form.Item>
</Col>
<Col span={4}>
<Form.Item label={t("tasks.fields.priority")} name="priority" initialValue={2}>
<Select
options={[
{ value: 3, label: t("tasks.fields.priorities.low") },
{ value: 2, label: t("tasks.fields.priorities.medium") },
{ value: 1, label: t("tasks.fields.priorities.high") }
]}
/>
</Form.Item>
</Col>
<Col span={4}>
<Form.Item
label={t("tasks.fields.completed")}
name="completed"
valuePropName="checked"
initialValue={false}
rules={[
{
required: true
}
]}
>
<Switch />
</Form.Item>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={24}>
<Form.Item
name="jobid"
initialValue={selectedJobId}
label={t("tasks.fields.jobid")}
rules={[
{
required: true
}
]}
>
<JobSearchSelectComponent
placeholder={t("tasks.placeholders.jobid")}
onSelect={changeJobId}
onClear={changeJobId}
autoFocus={false}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={8}>
<Form.Item label={t("tasks.fields.joblineid")} name="joblineid">
<Select
allowClear
placeholder={t("tasks.placeholders.joblineid")}
disabled={!selectedJobDetails || !selectedJobId}
options={selectedJobDetails?.joblines?.map((jobline) => ({
key: jobline.id,
value: jobline.id,
label: jobline.line_desc
}))}
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label={t("tasks.fields.partsorderid")} name="partsorderid">
<Select
allowClear
placeholder={t("tasks.placeholders.partsorderid")}
disabled={!selectedJobDetails || !selectedJobId}
options={selectedJobDetails?.parts_orders?.map((partsOrder) => ({
key: partsOrder.id,
value: partsOrder.id,
label: `${partsOrder.order_number} - ${partsOrder.vendor.name}`
}))}
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label={t("tasks.fields.billid")} name="billid">
<Select
allowClear
placeholder={t("tasks.placeholders.billid")}
disabled={!selectedJobDetails || !selectedJobId}
options={selectedJobDetails?.bills?.map((bill) => ({
key: bill.id,
value: bill.id,
label: `${bill.invoice_number} - ${bill.vendor.name}`
}))}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={8}>
<Form.Item
label={t("tasks.fields.assigned_to")}
name="assigned_to"
initialValue={
bodyshop.employees.find((employee) => employee?.user_email === currentUser.email && employee.active)?.id
}
rules={[
{
required: true
}
]}
>
<Select
placeholder={t("tasks.placeholders.assigned_to")}
options={bodyshop.employees
.filter((x) => x.active && x.user_email)
.map((employee) => ({
key: employee.id,
value: employee.id,
label: `${employee.first_name} ${employee.last_name}`
}))}
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item label={t("tasks.fields.due_date")} name="due_date">
<FormDatePicker
onlyFuture
format="MM/DD/YYYY"
presets={generatePresets(selectedJobDetails)}
rules={[
{
validator: (_, value) => {
if (!value || existingTask?.due_date === value || dayjs(value).isAfter(dayjs())) {
return Promise.resolve();
}
return Promise.reject(new Error(t("tasks.validation.due_at_error_message")));
}
}
]}
/>
</Form.Item>
</Col>
<Col span={8}>
<Form.Item
label={t("tasks.fields.remind_at")}
name="remind_at"
rules={[
{
validator: (_, value) => {
if (!value || existingTask?.remind_at === value || dayjs(value).isAfter(dayjs().add(15, "minute"))) {
return Promise.resolve();
}
return Promise.reject(new Error(t("tasks.validation.remind_at_error_message")));
}
}
]}
>
<FormDateTimePickerEnhanced
onlyFuture
showTime
minuteStep={15}
presets={generatePresets(selectedJobDetails)}
/>
</Form.Item>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={24}>
<Form.Item label={t("tasks.fields.description")} name="description">
<Input.TextArea rows={8} placeholder={t("tasks.fields.description")} />
</Form.Item>
</Col>
</Row>
</>
);
}

View File

@@ -0,0 +1,296 @@
import { useMutation, useQuery } from "@apollo/client";
import { Form, Modal, notification } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { MUTATION_INSERT_NEW_TASK, MUTATION_UPDATE_TASK, QUERY_GET_TASK_BY_ID } from "../../graphql/tasks.queries";
import { GET_JOB_BY_PK, QUERY_GET_TASKS_JOB_DETAILS_BY_ID } from "../../graphql/jobs.queries.js";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectTaskUpsert } from "../../redux/modals/modals.selectors";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import TaskUpsertModalComponent from "./task-upsert-modal.component";
import { replaceUndefinedWithNull } from "../../utils/undefinedtonull.js";
import { useLocation, useNavigate } from "react-router-dom";
import { insertAuditTrail } from "../../redux/application/application.actions.js";
import AuditTrailMapping from "../../utils/AuditTrailMappings.js";
import { isEqual } from "lodash";
import refetchRouteMappings from "./task-upsert-modal.route.mappings";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
bodyshop: selectBodyshop,
taskUpsert: selectTaskUpsert
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("taskUpsert")),
insertAuditTrail: ({ jobid, billid, operation, type }) =>
dispatch(insertAuditTrail({ jobid, billid, operation, type }))
});
export function TaskUpsertModalContainer({ bodyshop, currentUser, taskUpsert, toggleModalVisible, insertAuditTrail }) {
const { t } = useTranslation();
const history = useNavigate();
const [insertTask] = useMutation(MUTATION_INSERT_NEW_TASK);
const [updateTask] = useMutation(MUTATION_UPDATE_TASK);
const { open, context } = taskUpsert;
const { jobid, joblineid, billid, partsorderid, taskId, existingTask, query } = context;
const [form] = Form.useForm();
const [selectedJobId, setSelectedJobId] = useState(null);
const [selectedJobDetails, setSelectedJobDetails] = useState(null);
const [jobIdState, setJobIdState] = useState(null);
const [isTouched, setIsTouched] = useState(false);
const location = useLocation();
const { loading, error, data } = useQuery(QUERY_GET_TASKS_JOB_DETAILS_BY_ID, {
variables: { id: jobIdState },
skip: !jobIdState
});
const {
loading: taskLoading,
error: taskError,
data: taskData
} = useQuery(QUERY_GET_TASK_BY_ID, {
variables: { id: taskId },
skip: !taskId
});
// Use Effect to hydrate existing task if only a taskid is provided
useEffect(() => {
if (!taskLoading && !taskError && taskData && taskData?.tasks_by_pk) {
form.setFieldsValue(taskData.tasks_by_pk);
setSelectedJobId(taskData.tasks_by_pk.jobid);
}
}, [taskLoading, taskError, taskData, form]);
// Use Effect to hydrate selected job details
useEffect(() => {
if (!loading && !error && data) {
setSelectedJobDetails(data.jobs_by_pk);
}
}, [loading, error, data]);
// Use Effect to toggle to set jobid state
useEffect(() => {
if (selectedJobId) {
setJobIdState(selectedJobId);
}
}, [selectedJobId]);
// Use Effect to hydrate form fields
useEffect(() => {
if (jobid || existingTask?.id) {
setSelectedJobId(jobid || existingTask.jobid);
}
if (existingTask && open) {
form.setFieldsValue(existingTask);
} else if (!existingTask && open) {
form.resetFields();
if (joblineid) form.setFieldsValue({ joblineid });
if (billid) form.setFieldsValue({ billid });
if (partsorderid) form.setFieldsValue({ partsorderid });
}
return () => {
setSelectedJobId(null);
setIsTouched(false);
};
}, [jobid, existingTask, form, open, joblineid, billid, partsorderid]);
/**
* Remove the taskid from the URL
*/
const removeTaskIdFromUrl = () => {
const urlParams = new URLSearchParams(window.location.search);
if (!urlParams.has("taskid")) return;
urlParams.delete("taskid");
history(`${window.location.pathname}?${urlParams}`);
};
/**
* Generate refetch queries
* @param jobId
* @returns {*[]}
*/
const generateRefetchQueries = (jobId) => {
const refetchQueries = [];
if (location.pathname.includes("/manage/jobs") && jobId) {
refetchQueries.push({
query: GET_JOB_BY_PK,
variables: {
id: jobId
}
});
}
// We have a specified query
if (query && Object.keys(query).length) {
refetchQueries.push(Object.keys(query)[0]);
}
// We don't have a specified query, check the page
else {
refetchRouteMappings.forEach((mapping) => {
if (location.pathname.includes(mapping.route)) {
refetchQueries.push(mapping.query);
}
});
}
return refetchQueries;
};
/**
* Handle existing task
* @param id
* @param jobId
* @param values
* @returns {Promise<void>}
*/
const handleExistingTask = async (id, jobId, values) => {
const task = replaceUndefinedWithNull(values);
// Remind at is dirty so lets clear remind_at_sent
if (task?.remind_at) {
task.remind_at_sent = null;
}
const taskObject = {
variables: {
taskId: id,
task
}
};
taskObject.refetchQueries = generateRefetchQueries(jobId);
const taskData = await updateTask(taskObject);
if (!taskData.errors) {
const oldTask = taskData?.data?.update_tasks?.returning[0];
insertAuditTrail({
jobid: oldTask.jobid,
operation: AuditTrailMapping.tasksUpdated(oldTask.title, currentUser.email),
type: "tasksUpdated"
});
}
notification["success"]({
message: t("tasks.successes.updated")
});
toggleModalVisible();
};
/**
* Handle new task
* @param values
* @returns {Promise<void>}
*/
const handleNewTask = async (values) => {
const taskObject = {
variables: {
taskInput: [
{
...values,
created_by: currentUser.email,
bodyshopid: bodyshop.id
}
]
}
};
taskObject.refetchQueries = generateRefetchQueries(values.jobid);
const newTaskData = await insertTask(taskObject);
const newTask = newTaskData?.data?.insert_tasks?.returning[0];
if (!newTaskData.errors) {
insertAuditTrail({
jobid: newTask.jobid,
operation: AuditTrailMapping.tasksCreated(newTask.title, currentUser.email),
type: "tasksCreated"
});
}
form.resetFields();
toggleModalVisible();
notification["success"]({
message: t("tasks.successes.created")
});
};
/**
* Handle the form submit
* @param formValues
* @returns {Promise<[{jobid, bodyshopid, created_by},...*]>}
*/
const handleFinish = async (formValues) => {
if (existingTask || taskData?.tasks_by_pk) {
const taskSource = existingTask || taskData?.tasks_by_pk;
const dirtyValues = Object.keys(formValues).reduce((acc, key) => {
if (!isEqual(formValues[key], taskSource[key])) {
acc[key] = formValues[key];
}
return acc;
}, {});
try {
await handleExistingTask(taskSource.id, taskSource.jobid, dirtyValues);
} catch (e) {
notification["error"]({
message: t("tasks.failures.updated")
});
}
} else {
try {
await handleNewTask(formValues);
} catch (e) {
notification["error"]({
message: t("tasks.failures.created")
});
}
}
};
return (
<Modal
title={existingTask ? t("tasks.actions.edit") : t("tasks.actions.new")}
open={open}
okText={t("general.actions.save")}
width="50%"
onOk={() => {
removeTaskIdFromUrl();
form.submit();
}}
onCancel={() => {
removeTaskIdFromUrl();
toggleModalVisible();
}}
okButtonProps={{ disabled: !isTouched }}
destroyOnClose
>
<Form
form={form}
onFinish={handleFinish}
layout="vertical"
onFieldsChange={() => {
setIsTouched(true);
}}
>
<TaskUpsertModalComponent
form={form}
loading={loading || (taskId && taskLoading)}
error={error}
data={data}
existingTask={existingTask || taskData?.tasks_by_pk}
selectedJobId={selectedJobId}
setSelectedJobId={setSelectedJobId}
selectedJobDetails={selectedJobDetails}
/>
</Form>
</Modal>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(TaskUpsertModalContainer);

View File

@@ -0,0 +1,15 @@
import {
QUERY_ALL_TASKS_PAGINATED,
QUERY_JOB_TASKS_PAGINATED,
QUERY_MY_TASKS_PAGINATED
} from "../../graphql/tasks.queries.js";
const getQueryName = (query) => Object.keys(query)[0];
const refetchRouteMappings = [
{query: getQueryName({QUERY_MY_TASKS_PAGINATED}), route: "/manage/tasks/mytasks"},
{query: getQueryName({QUERY_ALL_TASKS_PAGINATED}), route: "/manage/tasks/alltasks"},
{query: getQueryName({QUERY_JOB_TASKS_PAGINATED}), route: "/manage/jobs"}
];
export default refetchRouteMappings;

View File

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

View File

@@ -59,9 +59,14 @@ export const QUERY_BILLS_BY_JOBID = gql`
name
email
}
tasks {
id
due_date
}
order_date
deliver_by
return
returnfrombill
orderedby
parts_order_lines {
id

View File

@@ -67,6 +67,24 @@ export const QUERY_ACTIVE_EMPLOYEES = gql`
}
`;
export const QUERY_ACTIVE_EMPLOYEES_WITH_EMAIL = gql`
query QUERY_ACTIVE_EMPLOYEES_WITH_EMAIL {
employees(where: {active: {_eq: true}, user_email: {_is_null: false}}) {
last_name
id
first_name
employee_number
active
termination_date
hire_date
flat_rate
rates
pin
user_email
}
}
`;
export const INSERT_EMPLOYEES = gql`
mutation INSERT_EMPLOYEES($employees: [employees_insert_input!]!) {
insert_employees(objects: $employees) {

View File

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

View File

@@ -499,6 +499,11 @@ export const QUERY_JOB_COSTING_DETAILS = gql`
export const GET_JOB_BY_PK = gql`
query GET_JOB_BY_PK($id: uuid!) {
jobs_by_pk(id: $id) {
tasks_aggregate(where: { completed: { _eq: false }, deleted: { _eq: false } }) {
aggregate {
count
}
}
actual_completion
actual_delivery
actual_in
@@ -1969,19 +1974,19 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
kmout
qb_multiple_payers
lbr_adjustments
payments {
amount
created_at
date
exportedat
id
jobid
memo
payer
paymentnum
transactionid
type
}
payments {
amount
created_at
date
exportedat
id
jobid
memo
payer
paymentnum
transactionid
type
}
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
id
removed
@@ -1994,7 +1999,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
db_price
act_price
part_qty
notes
notes
mod_lbr_ty
db_hrs
mod_lb_hrs
@@ -2006,9 +2011,9 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
prt_dsmk_p
convertedtolbr
convertedtolbr_data
act_price_before_ppc
sublet_ignored
sublet_completed
act_price_before_ppc
sublet_ignored
sublet_completed
}
}
}
@@ -2056,12 +2061,12 @@ export const generate_UPDATE_JOB_KANBAN = (
}`;
return gql`
mutation UPDATE_JOB_KANBAN {
${oldChildId ? oldChildQuery : ""}
${movedId ? movedQuery : ""}
${newChildId ? newChildQuery : ""}
}
`;
mutation UPDATE_JOB_KANBAN {
${oldChildId ? oldChildQuery : ""}
${movedId ? movedQuery : ""}
${newChildId ? newChildQuery : ""}
}
`;
};
export const QUERY_JOB_LBR_ADJUSTMENTS = gql`
@@ -2081,6 +2086,34 @@ export const DELETE_JOB = gql`
}
`;
export const QUERY_GET_TASKS_JOB_DETAILS_BY_ID = gql`
query QUERY_GET_TASKS_JOB_DETAILS_BY_ID($id: uuid!) {
jobs_by_pk(id: $id) {
id
scheduled_delivery
scheduled_completion
joblines {
id
line_desc
}
bills {
id
vendor {
name
}
invoice_number
}
parts_orders {
id
vendor {
name
}
order_number
}
}
}
`;
export const GET_JOB_FOR_CC_CONTRACT = gql`
query GET_JOB_FOR_CC_CONTRACT($id: uuid!) {
jobs_by_pk(id: $id) {
@@ -2238,6 +2271,8 @@ export const GET_JOB_LINE_ORDERS = gql`
parts_order_lines(where: { job_line_id: { _eq: $joblineid } }) {
id
act_price
backordered_eta
backordered_on
parts_order {
id
order_date

View File

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

View File

@@ -0,0 +1,383 @@
import { gql } from "@apollo/client";
export const PARTIAL_TASK_FIELDS = gql`
fragment TaskFields on tasks {
id
created_at
updated_at
title
description
deleted
deleted_at
due_date
created_by
assigned_to
assigned_to_employee {
id
user_email
}
completed
completed_at
remind_at
priority
job {
id
ro_number
joblines {
id
line_desc
}
bills {
id
vendor {
name
}
invoice_number
}
parts_orders {
id
vendor {
name
}
order_number
}
}
jobid
jobline {
id
line_desc
}
joblineid
parts_order {
id
vendor {
name
}
order_number
}
partsorderid
bill {
id
vendor {
name
}
invoice_number
}
billid
}
`;
export const QUERY_GET_TASK_BY_ID = gql`
${PARTIAL_TASK_FIELDS}
query QUERY_GET_TASK_BY_ID($id: uuid!) {
tasks_by_pk(id: $id) {
...TaskFields
}
}
`;
export const QUERY_ALL_TASKS_PAGINATED = gql`
${PARTIAL_TASK_FIELDS}
query QUERY_ALL_TASKS_PAGINATED(
$offset: Int
$limit: Int
$bodyshop: uuid!
$deleted: Boolean
$completed: Boolean
$assigned_to: uuid
$order: [tasks_order_by!]!
) {
tasks(
offset: $offset
limit: $limit
order_by: $order
where: {
bodyshopid: { _eq: $bodyshop }
deleted: { _eq: $deleted }
assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed }
}
) {
...TaskFields
}
tasks_aggregate(
where: {
bodyshopid: { _eq: $bodyshop }
deleted: { _eq: $deleted }
assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed }
}
) {
aggregate {
count
}
}
}
`;
// Query for joblineid
export const QUERY_JOBLINE_TASKS_PAGINATED = gql`
${PARTIAL_TASK_FIELDS}
query QUERY_JOBLINE_TASKS_PAGINATED(
$offset: Int
$limit: Int
$joblineid: uuid!
$bodyshop: uuid!
$deleted: Boolean
$completed: Boolean
$assigned_to: uuid
$order: [tasks_order_by!]!
) {
tasks(
offset: $offset
limit: $limit
order_by: $order
where: {
joblineid: { _eq: $joblineid }
bodyshopid: { _eq: $bodyshop }
deleted: { _eq: $deleted }
assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed }
}
) {
...TaskFields
}
tasks_aggregate(
where: {
joblineid: { _eq: $joblineid }
bodyshopid: { _eq: $bodyshop }
deleted: { _eq: $deleted }
assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed }
}
) {
aggregate {
count
}
}
}
`;
// Query for partsorderid
export const QUERY_PARTSORDER_TASKS_PAGINATED = gql`
${PARTIAL_TASK_FIELDS}
query QUERY_PARTSORDER_TASKS_PAGINATED(
$offset: Int
$limit: Int
$partsorderid: uuid!
$bodyshop: uuid!
$deleted: Boolean
$completed: Boolean
$assigned_to: uuid
$order: [tasks_order_by!]!
) {
tasks(
offset: $offset
limit: $limit
order_by: $order
where: {
partsorderid: { _eq: $partsorderid }
bodyshopid: { _eq: $bodyshop }
deleted: { _eq: $deleted }
assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed }
}
) {
...TaskFields
}
tasks_aggregate(
where: {
partsorderid: { _eq: $partsorderid }
bodyshopid: { _eq: $bodyshop }
deleted: { _eq: $deleted }
assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed }
}
) {
aggregate {
count
}
}
}
`;
// Query for billid
export const QUERY_BILL_TASKS_PAGINATED = gql`
${PARTIAL_TASK_FIELDS}
query QUERY_BILL_TASKS_PAGINATED(
$offset: Int
$limit: Int
$billid: uuid!
$bodyshop: uuid!
$deleted: Boolean
$completed: Boolean
$assigned_to: uuid
$order: [tasks_order_by!]!
) {
tasks(
offset: $offset
limit: $limit
order_by: $order
where: {
billid: { _eq: $billid }
bodyshopid: { _eq: $bodyshop }
deleted: { _eq: $deleted }
assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed }
}
) {
...TaskFields
}
tasks_aggregate(
where: {
billid: { _eq: $billid }
bodyshopid: { _eq: $bodyshop }
deleted: { _eq: $deleted }
assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed }
}
) {
aggregate {
count
}
}
}
`;
// Use the fragment in your queries
export const QUERY_JOB_TASKS_PAGINATED = gql`
${PARTIAL_TASK_FIELDS}
query QUERY_JOB_TASKS_PAGINATED(
$offset: Int
$limit: Int
$jobid: uuid!
$bodyshop: uuid!
$deleted: Boolean
$completed: Boolean
$assigned_to: uuid
$order: [tasks_order_by!]!
) {
tasks(
offset: $offset
limit: $limit
order_by: $order
where: {
jobid: { _eq: $jobid }
bodyshopid: { _eq: $bodyshop }
deleted: { _eq: $deleted }
assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed }
}
) {
...TaskFields
}
tasks_aggregate(
where: {
jobid: { _eq: $jobid }
bodyshopid: { _eq: $bodyshop }
deleted: { _eq: $deleted }
assigned_to: { _eq: $assigned_to }
completed: { _eq: $completed }
}
) {
aggregate {
count
}
}
}
`;
export const QUERY_MY_TASKS_PAGINATED = gql`
${PARTIAL_TASK_FIELDS}
query QUERY_MY_TASKS_PAGINATED(
$offset: Int
$limit: Int
$assigned_to: uuid!
$bodyshop: uuid!
$deleted: Boolean
$completed: Boolean
$order: [tasks_order_by!]!
) {
tasks(
offset: $offset
limit: $limit
order_by: $order
where: {
assigned_to: { _eq: $assigned_to }
bodyshopid: { _eq: $bodyshop }
deleted: { _eq: $deleted }
completed: { _eq: $completed }
}
) {
...TaskFields
}
tasks_aggregate(
where: {
assigned_to: { _eq: $assigned_to }
bodyshopid: { _eq: $bodyshop }
deleted: { _eq: $deleted }
completed: { _eq: $completed }
}
) {
aggregate {
count
}
}
}
`;
/**
* Toggle task completed mutation
* @type {DocumentNode}
*/
export const MUTATION_TOGGLE_TASK_COMPLETED = gql`
${PARTIAL_TASK_FIELDS}
mutation MUTATION_TOGGLE_TASK_COMPLETED($id: uuid!, $completed: Boolean!, $completed_at: timestamptz) {
update_tasks_by_pk(pk_columns: { id: $id }, _set: { completed: $completed, completed_at: $completed_at }) {
...TaskFields
}
}
`;
/**
* Toggle task deleted mutation
* @type {DocumentNode}
*/
export const MUTATION_TOGGLE_TASK_DELETED = gql`
${PARTIAL_TASK_FIELDS}
mutation MUTATION_TOGGLE_TASK_DELETED($id: uuid!, $deleted: Boolean!, $deleted_at: timestamptz) {
update_tasks_by_pk(pk_columns: { id: $id }, _set: { deleted: $deleted, deleted_at: $deleted_at }) {
...TaskFields
}
}
`;
/**
* Insert new task mutation
* @type {DocumentNode}
*/
export const MUTATION_INSERT_NEW_TASK = gql`
${PARTIAL_TASK_FIELDS}
mutation MUTATION_INSERT_NEW_TASK($taskInput: [tasks_insert_input!]!) {
insert_tasks(objects: $taskInput) {
returning {
...TaskFields
}
}
}
`;
/**
* Update task mutation
* @type {DocumentNode}
*/
export const MUTATION_UPDATE_TASK = gql`
${PARTIAL_TASK_FIELDS}
mutation MUTATION_UPDATE_TASK($taskId: uuid!, $task: tasks_set_input!) {
update_tasks(where: { id: { _eq: $taskId } }, _set: $task) {
returning {
...TaskFields
}
}
}
`;

View File

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

View File

@@ -51,6 +51,17 @@ export function JobsAvailablePageContainer({ partnerVersion, setBreadcrumbs, set
{!partnerVersion && (
<AlertComponent
type="warning"
action={
<a
href={InstanceRenderManager({
imex: "https://partner.imex.online/Setup.exe",
rome: "https://partner.romeonline.io/Setup.exe",
promanager: "https://dzaenazwrgg60.cloudfront.net/Setup.exe"
})}
>
<Button size="small">{t("general.actions.download")}</Button>
</a>
}
message={t("general.messages.partnernotrunning", {
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",

View File

@@ -41,7 +41,7 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import JobCloseRoGuardContainer from '../../components/job-close-ro-guard/job-close-ro-guard.container';
import JobCloseRoGuardContainer from "../../components/job-close-ro-guard/job-close-ro-guard.container";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -120,6 +120,13 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
operation: AuditTrailMapping.jobinvoiced(),
type: "jobinvoiced"
});
if (values.masterbypass) {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobclosedwithbypass(),
type: "jobclosedwithbypass"
});
}
// history(`/manage/jobs/${job.id}`);
} else {
setLoading(false);
@@ -184,7 +191,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail }) {
</Space>
}
/>
<JobCloseRoGuardContainer form={form} job={job} />
<JobCloseRoGuardContainer form={form} job={job} />
<Space wrap direction="vertical" style={{ width: "100%" }}>
<FormsFieldChanged form={form} />
{!job.actual_in && job.scheduled_in && <Alert type="warning" message={t("jobs.labels.actual_in_inferred")} />}

View File

@@ -20,18 +20,21 @@ export default function JobsCreateComponent({ form }) {
const steps = [
{
title: t("jobs.labels.create.vehicleinfo"),
id: "step-job-vehicleinfo",
content: <JobsCreateVehicleInfoContainer form={form} />,
validation: !!state.vehicle.new || !!state.vehicle.selectedid || !!state.vehicle.none,
error: t("vehicles.errors.selectexistingornew")
},
{
title: t("jobs.labels.create.ownerinfo"),
id: "step-job-ownerinfo",
content: <JobsCreateOwnerInfoContainer />,
validation: !!state.owner.new || !!state.owner.selectedid,
error: t("owners.errors.selectexistingornew")
},
{
title: t("jobs.labels.create.jobinfo"),
id: "step-job-jobinfo",
content: <JobsCreateJobsInfo form={form} selected={pageIndex === 2} />
}
];

View File

@@ -8,7 +8,7 @@ import Icon, {
SyncOutlined,
ToolFilled
} from "@ant-design/icons";
import { Button, Divider, Form, notification, Space, Tabs } from "antd";
import { Badge, Button, Divider, Form, notification, Space, Tabs } from "antd";
import { PageHeader } from "@ant-design/pro-layout";
import Axios from "axios";
@@ -16,7 +16,7 @@ import dayjs from "../../utils/day";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { FaHardHat, FaRegStickyNote, FaShieldAlt } from "react-icons/fa";
import { FaHardHat, FaRegStickyNote, FaShieldAlt, FaTasks } from "react-icons/fa";
import { connect } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";
@@ -53,14 +53,29 @@ import JobProfileDataWarning from "../../components/job-profile-data-warning/job
import { DateTimeFormat } from "../../utils/DateFormatter";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../../components/feature-wrapper/feature-wrapper.component";
import TaskListContainer from "../../components/task-list/task-list.container.jsx";
import { QUERY_JOB_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly
});
const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) => dispatch(setModalContext({ context: context, modal: "printCenter" })),
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
setPrintCenterContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "printCenter"
})
),
insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(
insertAuditTrail({
jobid,
operation,
type
})
)
});
export function JobsDetailPage({
@@ -69,7 +84,6 @@ export function JobsDetailPage({
jobRO,
job,
mutationUpdateJob,
handleSubmit,
insertAuditTrail,
refetch
}) {
@@ -277,6 +291,7 @@ export function JobsDetailPage({
{
key: "general",
icon: <Icon component={FaShieldAlt} />,
id: "job-details-general",
label: t("menus.jobsdetail.general"),
forceRender: true,
children: <JobsDetailGeneral job={job} form={form} />
@@ -284,6 +299,7 @@ export function JobsDetailPage({
{
key: "repairdata",
icon: <BarsOutlined />,
id: "job-details-repairdata",
label: t("menus.jobsdetail.repairdata"),
forceRender: true,
children: <JobsLinesContainer job={job} joblines={job.joblines} refetch={refetch} form={form} />
@@ -291,18 +307,21 @@ export function JobsDetailPage({
{
key: "rates",
icon: <DollarCircleOutlined />,
id: "job-details-rates",
label: t("menus.jobsdetail.rates"),
forceRender: true,
children: <JobsDetailRates job={job} form={form} />
},
{
key: "totals",
id: "job-details-totals",
icon: <DollarCircleOutlined />,
label: t("menus.jobsdetail.totals"),
children: <JobsDetailTotals job={job} refetch={refetch} />
},
{
key: "partssublet",
id: "job-details-partssublet",
icon: <ToolFilled />,
label: HasFeatureAccess({ featureName: "bills", bodyshop })
? t("menus.jobsdetail.partssublet")
@@ -317,6 +336,7 @@ export function JobsDetailPage({
? [
{
key: "labor",
id: "job-details-labor",
icon: <Icon component={FaHardHat} />,
label: t("menus.jobsdetail.labor"),
children: <JobsDetailLaborContainer job={job} jobId={job.id} />
@@ -326,11 +346,13 @@ export function JobsDetailPage({
{
key: "lifecycle",
icon: <BarsOutlined />,
id: "job-details-lifecycle",
label: t("menus.jobsdetail.lifecycle"),
children: <JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses} />
},
{
key: "dates",
id: "job-details-dates",
icon: <CalendarFilled />,
label: t("menus.jobsdetail.dates"),
forceRender: true,
@@ -344,6 +366,7 @@ export function JobsDetailPage({
? [
{
key: "documents",
id: "job-details-documents",
icon: <FileImageFilled />,
label: t("jobs.labels.documents"),
children: bodyshop.uselocalmediaserver ? (
@@ -356,6 +379,7 @@ export function JobsDetailPage({
: []),
{
key: "notes",
id: "job-details-notes",
icon: <Icon component={FaRegStickyNote} />,
label: t("jobs.labels.notes"),
children: <JobNotesContainer jobId={job.id} />
@@ -363,8 +387,29 @@ export function JobsDetailPage({
{
key: "audit",
icon: <HistoryOutlined />,
id: "job-details-audit",
label: t("jobs.labels.audit"),
children: <JobAuditTrail jobId={job.id} />
},
{
key: "tasks",
icon: <FaTasks />,
id: "job-details-tasks",
label: (
<Space direction="horizontal">
{t("jobs.labels.tasks")}
{job.tasks_aggregate.aggregate.count > 0 && <Badge count={job.tasks_aggregate.aggregate.count} />}
</Space>
),
children: (
<TaskListContainer
relationshipType={"jobid"}
relationshipId={job.id}
query={{ QUERY_JOB_TASKS_PAGINATED }}
titleTranslation="tasks.titles.job_tasks"
showRo={false}
/>
)
}
]}
/>

View File

@@ -1,4 +1,4 @@
import { FloatButton, Layout, Spin, Collapse, Button, Space, Tag } from "antd";
import { FloatButton, Layout, Spin } from "antd";
// import preval from "preval.macro";
import React, { lazy, Suspense, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -30,8 +30,8 @@ import "./manage.page.styles.scss";
const JobsPage = lazy(() => import("../jobs/jobs.page"));
const CardPaymentModalContainer = lazy(() =>
import("../../components/card-payment-modal/card-payment-modal.container.")
const CardPaymentModalContainer = lazy(
() => import("../../components/card-payment-modal/card-payment-modal.container.")
);
const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container"));
@@ -59,8 +59,8 @@ const JobCostingModal = lazy(() => import("../../components/job-costing-modal/jo
const ReportCenterModal = lazy(() => import("../../components/report-center-modal/report-center-modal.container"));
const BillEnterModalContainer = lazy(() => import("../../components/bill-enter-modal/bill-enter-modal.container"));
const TimeTicketModalContainer = lazy(() => import("../../components/time-ticket-modal/time-ticket-modal.container"));
const TimeTicketModalTask = lazy(() =>
import("../../components/time-ticket-task-modal/time-ticket-task-modal.container")
const TimeTicketModalTask = lazy(
() => import("../../components/time-ticket-task-modal/time-ticket-task-modal.container")
);
const PaymentModalContainer = lazy(() => import("../../components/payment-modal/payment-modal.container"));
const ProductionListPage = lazy(() => import("../production-list/production-list.container"));
@@ -97,7 +97,10 @@ const Dms = lazy(() => import("../dms/dms.container"));
const DmsPayables = lazy(() => import("../dms-payables/dms-payables.container"));
const ManageRootPage = lazy(() => import("../manage-root/manage-root.page.container"));
const TtApprovals = lazy(() => import("../tt-approvals/tt-approvals.page.container"));
const MyTasksPage = lazy(() => import("../tasks/myTasksPageContainer.jsx"));
const AllTasksPage = lazy(() => import("../tasks/allTasksPageContainer.jsx"));
const TaskUpsertModalContainer = lazy(() => import("../../components/task-upsert-modal/task-upsert-modal.container"));
const { Content, Footer } = Layout;
const mapStateToProps = createStructuredSelector({
@@ -114,7 +117,6 @@ const mapDispatchToProps = (dispatch) => ({
export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) {
const { t } = useTranslation();
const [chatVisible] = useState(false);
const [tours, setTours] = useState([]);
useEffect(() => {
const widgetId = InstanceRenderManager({
@@ -147,12 +149,10 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
})}
/>
}
This
>
<PaymentModalContainer />
<CardPaymentModalContainer />
<TaskUpsertModalContainer />
<BreadCrumbs />
<BillEnterModalContainer />
<JobCostingModal />
@@ -252,6 +252,22 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
</Suspense>
}
/>
<Route
path="/tasks/mytasks"
element={
<Suspense fallback={<Spin />}>
<MyTasksPage />
</Suspense>
}
/>
<Route
path="/tasks/alltasks"
element={
<Suspense fallback={<Spin />}>
<AllTasksPage />
</Suspense>
}
/>
<Route
path="/inventory/"
element={
@@ -613,28 +629,6 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
Disclaimer & Notices
</Link>
</div>
{InstanceRenderManager({
promanager: (
<Collapse>
<Collapse.Panel header="DEVELOPMENT ONLY - ProductFruits Tours">
<Space>
<Button
onClick={async () => {
setTours(await window.productFruits.api.tours.getTours());
}}
>
Get Tours
</Button>
{tours.map((tour) => (
<Tag key={tour.id} onClick={() => window.productFruits.api.tours.tryStartTour(tour.id)}>
{tour.name}
</Tag>
))}
</Space>
</Collapse.Panel>
</Collapse>
)
})}
</Footer>
</Layout>
</>

View File

@@ -72,10 +72,17 @@ export function ShopPage({ bodyshop, setSelectedHeader, setBreadcrumbs }) {
});
}
items.push({
key: "licensing",
label: t("bodyshop.labels.licensing"),
children: <ShopInfoUsersComponent />
InstanceRenderManager({
executeFunction: true,
args: [],
imex: () => {
items.push({
key: "licensing",
label: t("bodyshop.labels.licensing"),
children: <ShopInfoUsersComponent />
});
},
rome: "USE_IMEX"
});
if (HasFeatureAccess({ featureName: "csi", bodyshop })) {

View File

@@ -0,0 +1,60 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import TasksPageComponent from "./tasks.page.component";
import queryString from "query-string";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import TaskPageTypes from "./taskPageTypes.jsx";
import { setModalContext } from "../../redux/modals/modals.actions.js";
import { useLocation } from "react-router-dom";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
setTaskUpsertContext: (context) => dispatch(setModalContext({ context, modal: "taskUpsert" }))
});
export function AllTasksPageContainer({ setBreadcrumbs, setSelectedHeader, setTaskUpsertContext }) {
const { t } = useTranslation();
const searchParams = queryString.parse(useLocation().search);
useEffect(() => {
document.title = t("titles.all_tasks", {
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)",
promanager: "$t(titles.promanager)"
})
});
setSelectedHeader("all_tasks");
setBreadcrumbs([
{
link: "/manage/tasks/alltasks",
label: t("titles.bc.all_tasks")
}
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
// This takes care of the ability to deep link a task from the URL (Dispatches the modal)
useEffect(() => {
// Check for a query string in the URL
const urlParams = new URLSearchParams(searchParams);
const taskId = urlParams.get("taskid");
if (taskId) {
setTaskUpsertContext({
context: {
taskId
}
});
urlParams.delete("taskid");
}
}, [setTaskUpsertContext, searchParams]);
return <TasksPageComponent type={TaskPageTypes.ALL_TASKS} />;
}
export default connect(mapStateToProps, mapDispatchToProps)(AllTasksPageContainer);

View File

@@ -0,0 +1,40 @@
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import TasksPageComponent from "./tasks.page.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import TaskPageTypes from "./taskPageTypes.jsx";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key))
});
export function MyTasksPageContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
useEffect(() => {
document.title = t("titles.my_tasks", {
app: InstanceRenderManager({
imex: "$t(titles.imexonline)",
rome: "$t(titles.romeonline)",
promanager: "$t(titles.promanager)"
})
});
setSelectedHeader("my_tasks");
setBreadcrumbs([
{
link: "/manage/tasks/mytasks",
label: t("titles.bc.my_tasks")
}
]);
}, [t, setBreadcrumbs, setSelectedHeader]);
return <TasksPageComponent type={TaskPageTypes.MY_TASKS} />;
}
export default connect(mapStateToProps, mapDispatchToProps)(MyTasksPageContainer);

View File

@@ -0,0 +1,6 @@
export const TaskPageTypes = {
MY_TASKS: "myTasks",
ALL_TASKS: "allTasks"
};
export default TaskPageTypes;

View File

@@ -0,0 +1,41 @@
import React from "react";
import TaskListContainer from "../../components/task-list/task-list.container.jsx";
import { QUERY_ALL_TASKS_PAGINATED, QUERY_MY_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
import taskPageTypes from "./taskPageTypes.jsx";
import { createStructuredSelector } from "reselect";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors.js";
import { connect } from "react-redux";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(TasksPageComponent);
export function TasksPageComponent({ bodyshop, currentUser, type }) {
switch (type) {
case taskPageTypes.MY_TASKS:
return (
<TaskListContainer
onlyMine={true}
relationshipId={bodyshop?.employees?.find((e) => e.user_email === currentUser.email)?.id}
relationshipType={"assigned_to"}
query={{ QUERY_MY_TASKS_PAGINATED }}
titleTranslation={"tasks.titles.my_tasks"}
disableJobRefetch={true}
/>
);
case taskPageTypes.ALL_TASKS:
return (
<TaskListContainer
query={{ QUERY_ALL_TASKS_PAGINATED }}
titleTranslation={"tasks.titles.all_tasks"}
disableJobRefetch={true}
/>
);
default:
return <></>;
}
}

View File

@@ -111,7 +111,12 @@ export function* calculateScheduleLoad({ payload: end }) {
(load[itemDate].allHoursIn || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].allHoursInBody =
(load[itemDate].allHoursInBody || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].allHoursInRefinish =
(load[itemDate].allHoursInRefinish || 0) +
item.larhrs.aggregate.sum.mod_lb_hrs;
//If the job hasn't already arrived, add it to the jobs in list.
// Make sure it also hasn't already been completed, or isn't an in and out job.
//This prevents the duplicate counting.
@@ -119,7 +124,15 @@ export function* calculateScheduleLoad({ payload: end }) {
if (AddJobForSchedulingCalc) {
load[itemDate].jobsIn.push(item);
load[itemDate].hoursIn =
(load[itemDate].hoursIn || 0) + item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs;
(load[itemDate].hoursIn || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].hoursInBody =
(load[itemDate].hoursInBody || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].hoursInRefinish =
(load[itemDate].hoursInRefinish || 0) +
item.larhrs.aggregate.sum.mod_lb_hrs;
}
} else {
load[itemDate] = {
@@ -127,10 +140,21 @@ export function* calculateScheduleLoad({ payload: end }) {
jobsIn: AddJobForSchedulingCalc ? [item] : [], //Same as above, only add it if it isn't already in production.
jobsOut: [],
allJobsOut: [],
allHoursIn: item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs,
allHoursIn:
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs,
allHoursInBody: item.labhrs.aggregate.sum.mod_lb_hrs,
allHoursInRefinish: item.larhrs.aggregate.sum.mod_lb_hrs,
hoursIn: AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs
: 0
? item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs
: 0,
hoursInBody: AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs
: 0,
hoursInRefinish: AddJobForSchedulingCalc
? item.larhrs.aggregate.sum.mod_lb_hrs
: 0,
};
}
});
@@ -151,6 +175,12 @@ export function* calculateScheduleLoad({ payload: end }) {
(load[itemDate].allHoursOut || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].allHoursOutBody =
(load[itemDate].allHoursOutBody || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].allHoursOutRefinish =
(load[itemDate].allHoursOutRefinish || 0) +
item.larhrs.aggregate.sum.mod_lb_hrs;
//Add only the jobs that are still in production to get rid of.
//If it's not in production, we'd subtract unnecessarily.
load[itemDate].allJobsOut.push(item);
@@ -161,6 +191,12 @@ export function* calculateScheduleLoad({ payload: end }) {
(load[itemDate].hoursOut || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].hoursOutBody =
(load[itemDate].hoursOutBody || 0) +
item.labhrs.aggregate.sum.mod_lb_hrs;
load[itemDate].hoursOutRefinish =
(load[itemDate].hoursOutRefinish || 0) +
item.larhrs.aggregate.sum.mod_lb_hrs;
}
} else {
load[itemDate] = {
@@ -169,7 +205,11 @@ export function* calculateScheduleLoad({ payload: end }) {
hoursOut: AddJobForSchedulingCalc
? item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs
: 0,
allHoursOut: item.labhrs.aggregate.sum.mod_lb_hrs + item.larhrs.aggregate.sum.mod_lb_hrs
allHoursOut:
item.labhrs.aggregate.sum.mod_lb_hrs +
item.larhrs.aggregate.sum.mod_lb_hrs,
allHoursOutBody: item.labhrs.aggregate.sum.mod_lb_hrs,
allHoursOutRefinish: item.larhrs.aggregate.sum.mod_lb_hrs,
};
}
});

View File

@@ -8,7 +8,8 @@ const INITIAL_STATE = {
name: "ShopName",
address: InstanceRenderManager({
imex: "noreply@iemx.online",
rome: "noreply@romeonline.io"
rome: "noreply@romeonline.io",
promanager: "noreply@promanager.web-est.com"
})
},
to: null,

View File

@@ -13,6 +13,7 @@ const INITIAL_STATE = {
billEnter: { ...baseModal },
courtesyCarReturn: { ...baseModal },
noteUpsert: { ...baseModal },
taskUpsert: { ...baseModal },
schedule: { ...baseModal },
partsOrder: { ...baseModal },
timeTicket: { ...baseModal },

View File

@@ -10,6 +10,8 @@ export const selectCourtesyCarReturn = createSelector([selectModals], (modals) =
export const selectNoteUpsert = createSelector([selectModals], (modals) => modals.noteUpsert);
export const selectTaskUpsert = createSelector([selectModals], (modals) => modals.taskUpsert);
export const selectSchedule = createSelector([selectModals], (modals) => modals.schedule);
export const selectPartsOrder = createSelector([selectModals], (modals) => modals.partsOrder);

View File

@@ -15,6 +15,7 @@ import { getToken } from "firebase/messaging";
import i18next from "i18next";
import LogRocket from "logrocket";
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
import { Userpilot } from "userpilot";
import { factory } from "../../App/App.container";
import {
analytics,
@@ -25,6 +26,10 @@ import {
messaging,
updateCurrentUser
} from "../../firebase/firebase.utils";
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
import client from "../../utils/GraphQLClient";
import day from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import {
checkInstanceId,
sendPasswordResetFailure,
@@ -43,11 +48,6 @@ import {
validatePasswordResetSuccess
} from "./user.actions";
import UserActionTypes from "./user.types";
import client from "../../utils/GraphQLClient";
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
import day from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { Userpilot } from "userpilot";
const fpPromise = FingerprintJS.load();
@@ -310,10 +310,41 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
updateUserDetailsSuccess(authRecord[0] ? { validemail: authRecord[0].user.validemail } : { validemail: false })
);
const user = yield select((state) => state.user.currentUser);
if (payload.features.singleDeviceOnly) {
const user = yield select((state) => state.user.currentUser);
if (
!(
user.email.includes("@imex.") ||
user.email.includes("@rome.") ||
user.email.includes("@rometech.") ||
user.email.includes("@promanager.")
)
)
yield put(setInstanceId(user.uid));
}
if (!(user.email.includes("@imex.") || user.email.includes("@rome."))) yield put(setInstanceId(user.uid));
//For Rome, check to make sure it's not a PM shop.
try {
InstanceRenderManager({
executeFunction: true,
args: [],
rome: () => {
if (
payload.imexshopid.toLowerCase().startsWith("pm_") &&
!(
user.email.includes("@imex.") ||
user.email.includes("@rome.") ||
user.email.includes("@rometech.") ||
user.email.includes("@promanager.")
)
) {
throw new Error("You are not authorized to use this application.");
}
},
promanager: () => {}
});
} catch (error) {
yield put(setInstanceConflict());
}
try {

View File

@@ -116,6 +116,7 @@
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
"jobassignmentremoved": "Employee assignment removed for {{operation}}",
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.",
"jobclosedwithbypass": "Job was invoiced using the master bypass password. ",
"jobconverted": "Job converted and assigned number {{ro_number}}.",
"jobdelivery": "Job intake completed. Status set to {{status}}. Actual completion is {{actual_completion}}.",
"jobexported": "",
@@ -134,7 +135,13 @@
"jobstatuschange": "Job status changed to {{status}}.",
"jobsupplement": "Job supplement imported.",
"jobsuspend": "Suspend Toggle set to {{status}}",
"jobvoid": "Job has been voided."
"jobvoid": "Job has been voided.",
"tasks_completed": "Task '{{title}}' completed by {{completedBy}}",
"tasks_created": "Task '{{title}}' created by {{createdBy}}",
"tasks_deleted": "Task '{{title}}' deleted by {{deletedBy}}",
"tasks_uncompleted": "Task '{{title}}' uncompleted by {{uncompletedBy}}",
"tasks_undeleted": "Task '{{title}}' undeleted by {{undeletedBy}}",
"tasks_updated": "Task '{{title}}' updated by {{updatedBy}}"
}
},
"billlines": {
@@ -223,11 +230,12 @@
"markexported": "Mark Exported",
"markforreexport": "Mark for Re-export",
"new": "New Bill",
"nobilllines": "This part has not yet been recieved.",
"nobilllines": "",
"noneselected": "No bill selected.",
"onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.",
"printlabels": "Print Labels",
"retailtotal": "Bills Retail Total",
"returnfrombill": "Return From Bill",
"savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.",
"state_tax": "State Tax",
"subtotal": "Subtotal",
@@ -858,10 +866,11 @@
},
"status": {
"in": "Available",
"inservice": "In Service",
"inservice": "Service/Maintenance",
"leasereturn": "Lease Returned",
"out": "Rented",
"sold": "Sold"
"sold": "Sold",
"unavailable": "Unavailable"
},
"successes": {
"saved": "Courtesy Car saved successfully."
@@ -931,7 +940,8 @@
"scheduledindate": "Sheduled In Today: {{date}}",
"scheduledintoday": "Sheduled In Today",
"scheduledoutdate": "Sheduled Out Today: {{date}}",
"scheduledouttoday": "Sheduled Out Today"
"scheduledouttoday": "Sheduled Out Today",
"tasks": "Tasks"
}
},
"dms": {
@@ -1126,6 +1136,7 @@
"delete": "Delete",
"deleteall": "Delete All",
"deselectall": "Deselect All",
"download": "Download",
"edit": "Edit",
"login": "Login",
"print": "Print",
@@ -1351,6 +1362,7 @@
"amount": "Amount",
"dateOfPayment": "Date of Payment",
"descriptions": "Payment Details",
"hint": "Hint",
"payer": "Payer",
"payername": "Payer Name",
"paymentid": "Payment Reference ID",
@@ -1541,6 +1553,7 @@
"voiding": "Error voiding Job. {{error}}"
},
"fields": {
"active_tasks": "Active Tasks",
"actual_completion": "Actual Completion",
"actual_delivery": "Actual Delivery",
"actual_in": "Actual In",
@@ -1975,7 +1988,7 @@
"outstanding_ar": "A balance is outstanding on this RO. Payments can still be entered when the job is closed. ",
"outstanding_credit_memos": "Outstanding credit memos have not been entered against this job. Credit Memos may still be posted once the job is closed.",
"outstanding_ppd": "There are outstanding PPDs that may not have been synced back to the estimate.",
"outstanding_reconciliation_discrep": "At least one discrepancy is not 0. This may indicate that this job is not properly reconciled and should not be closed.",
"outstanding_reconciliation_discrep": "At least one discrepancy is not $0. This may indicate that this job is not properly reconciled and should not be closed.",
"outstanding_sublets": "There are sublet lines on the job which have not been marked as completed. ",
"outstandinghours": "There are outstanding hours on the job that have not been paid or have been overpaid.",
"override_header": "Override estimate header on import?",
@@ -2004,6 +2017,7 @@
"ppc": "This line contains a part price change.",
"ppdnotexported": "PPDs not Exported",
"profileadjustments": "Profile Disc./Mkup",
"profitbypassrequired": "Minimum gross profit requirements have not been met.",
"profits": "Job Profits",
"prt_dsmk_total": "Line Item Adjustment",
"rates": "Rates",
@@ -2025,12 +2039,12 @@
"remove_from_ar": "Remove from AR",
"returntotals": "Return Totals",
"ro_guard": {
"eforce_profit": "Profit margins enforced.",
"enforce_ar": "AR collection enforced.",
"enforce_bills": "Bill discrepancy enforced.",
"enforce_cm": "Credit memo entry enforced.",
"enforce_labor": "Labor allocations enforced.",
"enforce_ppd": "PPD sync enforced.",
"enforce_profit": "Profit marginsenforced.",
"enforce_sublet": "Sublet completion enforced.",
"enforce_validation": "Master Bypass Required: {{message}}",
"enforced": "This check has been enforced by your shop manager. Enter the master bypass password to close the Job."
@@ -2053,6 +2067,7 @@
"supplementnote": "The Job had a supplement imported.",
"suspended": "SUSPENDED",
"suspense": "Suspense",
"tasks": "Tasks",
"threshhold": "Max Threshold: ${{amount}}",
"total_cost": "Total Cost",
"total_cust_payable": "Total Customer Amount Payable",
@@ -2150,6 +2165,7 @@
"accounting-payments": "Payments",
"accounting-receivables": "Receivables",
"activejobs": "Active Jobs",
"all_tasks": "All Tasks",
"alljobs": "All Jobs",
"allpayments": "All Payments",
"availablejobs": "Available Jobs",
@@ -2158,6 +2174,7 @@
"courtesycars-all": "All Courtesy Cars",
"courtesycars-contracts": "Contracts",
"courtesycars-newcontract": "New Contract",
"create_task": "Create Task",
"customers": "Customers",
"dashboard": "Dashboard",
"enterbills": "Enter Bills",
@@ -2170,6 +2187,7 @@
"home": "Home",
"inventory": "Inventory",
"jobs": "Jobs",
"my_tasks": "My Tasks",
"newjob": "Create New Job",
"owners": "Owners",
"parts-queue": "Parts Queue",
@@ -2196,6 +2214,7 @@
"shop_csi": "CSI",
"shop_templates": "Templates",
"shop_vendors": "Vendors",
"tasks": "Tasks",
"temporarydocs": "Temporary Documents",
"timetickets": "Time Tickets",
"ttapprovals": "Time Ticket Approvals",
@@ -2389,6 +2408,7 @@
"percent_accepted": "% Accepted"
},
"labels": {
"notyetdispatched": "This part has not been dispatched.",
"parts_dispatch": "Parts Dispatch"
}
},
@@ -2505,6 +2525,7 @@
"markexported": "Payment(s) marked exported.",
"markreexported": "Payment marked for re-export successfully",
"payment": "Payment created successfully. ",
"paymentupdate": "Payment updated successfully. ",
"stripe": "Credit card transaction charged successfully."
}
},
@@ -2606,6 +2627,7 @@
"job_costing_ro": "Job Costing",
"job_lifecycle_ro": "Job Lifecycle",
"job_notes": "Job Notes",
"job_tasks": "Job Tasks",
"key_tag": "Key Tag",
"labels": {
"count": "Count",
@@ -2806,6 +2828,7 @@
"parts_orders": "Parts Orders",
"payments": "Payments",
"scoreboard": "Scoreboard",
"tasks": "Tasks",
"timetickets": "Timetickets"
},
"vendor": "Vendor"
@@ -2921,6 +2944,8 @@
"scoreboard_detail": "Scoreboard Detail",
"scoreboard_summary": "Scoreboard Summary",
"supplement_ratio_ins_co": "Supplement Ratio by Source",
"tasks_date": "Tasks by Date",
"tasks_date_employee": "Employee Tasks by Date",
"thank_you_date": "Thank You Letters",
"timetickets": "Time Tickets",
"timetickets_employee": "Employee Time Tickets",
@@ -3000,6 +3025,92 @@
"updated": "Scoreboard updated."
}
},
"tasks": {
"actions": {
"edit": "Edit Task",
"new": "New Task"
},
"buttons": {
"allTasks": "All",
"complete": "Toggle Complete",
"create": "Create Task",
"delete": "Toggle Delete",
"edit": "Edit",
"myTasks": "Mine",
"refresh": "Refresh"
},
"date_presets": {
"completion": "Completion",
"day": "Day",
"days": "Days",
"delivery": "Delivery",
"next_week": "Next Week",
"one_month": "One Month",
"three_months": "Three Months",
"three_weeks": "Three Weeks",
"today": "Today",
"tomorrow": "Tomorrow",
"two_weeks": "Two Weeks"
},
"failures": {
"completed": "Failed to toggle Task completion.",
"created": "Failed to create Task.",
"deleted": "Failed to toggle Task deletion.",
"updated": "Failed to update Task."
},
"fields": {
"actions": "Actions",
"assigned_to": "Assigned To",
"bill": "Bill",
"billid": "Bill",
"completed": "Completed",
"created_at": "Created At",
"description": "Description",
"due_date": "Due Date",
"job": {
"ro_number": "RO #"
},
"jobid": "Job",
"jobline": "Job Line",
"joblineid": "Job Line",
"parts_order": "Parts Order",
"partsorderid": "Parts Order",
"priorities": {
"high": "High",
"low": "Low",
"medium": "Medium"
},
"priority": "Priority",
"remind_at": "Remind At",
"title": "Title"
},
"placeholders": {
"assigned_to": "Select an Employee",
"billid": "Select a Bill",
"description": "Enter a description",
"jobid": "Select a Job",
"joblineid": "Select a Job Line",
"partsorderid": "Select a Parts Order"
},
"successes": {
"completed": "Toggled Task completion successfully.",
"created": "Task created successfully.",
"deleted": "Toggled Task deletion successfully.",
"updated": "Task updated successfully."
},
"titles": {
"all_tasks": "All Tasks",
"completed": "Completed Tasks",
"deleted": "Deleted Tasks",
"job_tasks": "Job Tasks",
"mine": "My Tasks",
"my_tasks": "My Tasks"
},
"validation": {
"due_at_error_message": "The due date must be in the future!",
"remind_at_error_message": "The reminder date and time must be at least 30 minutes in the future!"
}
},
"tech": {
"fields": {
"employeeid": "Employee ID",
@@ -3104,11 +3215,13 @@
"accounting-payables": "Payables | {{app}}",
"accounting-payments": "Payments | {{app}}",
"accounting-receivables": "Receivables | {{app}}",
"all_tasks": "All Tasks",
"app": "",
"bc": {
"accounting-payables": "Payables",
"accounting-payments": "Payments",
"accounting-receivables": "Receivables",
"all_tasks": "All Tasks",
"availablejobs": "Available Jobs",
"bills-list": "Bills",
"contracts": "Contracts",
@@ -3132,6 +3245,7 @@
"jobs-intake": "Intake",
"jobs-new": "Create a New Job",
"jobs-ready": "Ready Jobs",
"my_tasks": "My Tasks",
"owner-detail": "{{name}}",
"owners": "Owners",
"parts-queue": "Parts Queue",
@@ -3146,6 +3260,7 @@
"shop-csi": "CSI Responses",
"shop-templates": "Shop Templates",
"shop-vendors": "Vendors",
"tasks": "Tasks",
"temporarydocs": "Temporary Documents",
"timetickets": "Time Tickets",
"ttapprovals": "Time Ticket Approvals",
@@ -3176,6 +3291,7 @@
"jobsdetail": "Job {{ro_number}} | {{app}}",
"jobsdocuments": "Job Documents {{ro_number}} | {{app}}",
"manageroot": "Home | {{app}}",
"my_tasks": "My Tasks",
"owners": "All Owners | {{app}}",
"owners-detail": "{{name}} | {{app}}",
"parts-queue": "Parts Queue | {{app}}",
@@ -3195,6 +3311,7 @@
"shop-csi": "CSI Responses | {{app}}",
"shop-templates": "Shop Templates | {{app}}",
"shop_vendors": "Vendors | {{app}}",
"tasks": "Tasks",
"techconsole": "Technician Console | {{app}}",
"techjobclock": "Technician Job Clock | {{app}}",
"techjoblookup": "Technician Job Lookup | {{app}}",

View File

@@ -116,6 +116,7 @@
"jobassignmentchange": "",
"jobassignmentremoved": "",
"jobchecklist": "",
"jobclosedwithbypass": "",
"jobconverted": "",
"jobdelivery": "",
"jobexported": "",
@@ -134,7 +135,13 @@
"jobstatuschange": "",
"jobsupplement": "",
"jobsuspend": "",
"jobvoid": ""
"jobvoid": "",
"tasks_completed": "",
"tasks_created": "",
"tasks_deleted": "",
"tasks_uncompleted": "",
"tasks_undeleted": "",
"tasks_updated": ""
}
},
"billlines": {
@@ -223,10 +230,12 @@
"markexported": "",
"markforreexport": "",
"new": "",
"nobilllines": "",
"noneselected": "",
"onlycmforinvoiced": "",
"printlabels": "",
"retailtotal": "",
"returnfrombill": "",
"savewithdiscrepancy": "",
"state_tax": "",
"subtotal": "",
@@ -860,7 +869,8 @@
"inservice": "",
"leasereturn": "",
"out": "",
"sold": ""
"sold": "",
"unavailable": ""
},
"successes": {
"saved": ""
@@ -930,7 +940,8 @@
"scheduledindate": "",
"scheduledintoday": "",
"scheduledoutdate": "",
"scheduledouttoday": ""
"scheduledouttoday": "",
"tasks": ""
}
},
"dms": {
@@ -1125,6 +1136,7 @@
"delete": "Borrar",
"deleteall": "",
"deselectall": "",
"download": "",
"edit": "Editar",
"login": "",
"print": "",
@@ -1350,6 +1362,7 @@
"amount": "",
"dateOfPayment": "",
"descriptions": "",
"hint": "",
"payer": "",
"payername": "",
"paymentid": "",
@@ -1540,6 +1553,7 @@
"voiding": ""
},
"fields": {
"active_tasks": "",
"actual_completion": "Realización real",
"actual_delivery": "Entrega real",
"actual_in": "Real en",
@@ -2003,6 +2017,7 @@
"ppc": "",
"ppdnotexported": "",
"profileadjustments": "",
"profitbypassrequired": "",
"profits": "",
"prt_dsmk_total": "",
"rates": "Tarifas",
@@ -2024,12 +2039,12 @@
"remove_from_ar": "",
"returntotals": "",
"ro_guard": {
"eforce_profit": "",
"enforce_ar": "",
"enforce_bills": "",
"enforce_cm": "",
"enforce_labor": "",
"enforce_ppd": "",
"enforce_profit": "",
"enforce_sublet": "",
"enforce_validation": "",
"enforced": ""
@@ -2052,6 +2067,7 @@
"supplementnote": "",
"suspended": "",
"suspense": "",
"tasks": "",
"threshhold": "",
"total_cost": "",
"total_cust_payable": "",
@@ -2149,6 +2165,7 @@
"accounting-payments": "",
"accounting-receivables": "",
"activejobs": "Empleos activos",
"all_tasks": "",
"alljobs": "",
"allpayments": "",
"availablejobs": "Trabajos disponibles",
@@ -2157,6 +2174,7 @@
"courtesycars-all": "",
"courtesycars-contracts": "",
"courtesycars-newcontract": "",
"create_task": "",
"customers": "Clientes",
"dashboard": "",
"enterbills": "",
@@ -2169,6 +2187,7 @@
"home": "Casa",
"inventory": "",
"jobs": "Trabajos",
"my_tasks": "",
"newjob": "",
"owners": "propietarios",
"parts-queue": "",
@@ -2195,6 +2214,7 @@
"shop_csi": "",
"shop_templates": "",
"shop_vendors": "Vendedores",
"tasks": "",
"temporarydocs": "",
"timetickets": "",
"ttapprovals": "",
@@ -2388,6 +2408,7 @@
"percent_accepted": ""
},
"labels": {
"notyetdispatched": "",
"parts_dispatch": ""
}
},
@@ -2504,6 +2525,7 @@
"markexported": "",
"markreexported": "",
"payment": "",
"paymentupdate": "",
"stripe": ""
}
},
@@ -2605,6 +2627,7 @@
"job_costing_ro": "",
"job_lifecycle_ro": "",
"job_notes": "",
"job_tasks": "",
"key_tag": "",
"labels": {
"count": "",
@@ -2805,6 +2828,7 @@
"parts_orders": "",
"payments": "",
"scoreboard": "",
"tasks": "",
"timetickets": ""
},
"vendor": ""
@@ -2920,6 +2944,8 @@
"scoreboard_detail": "",
"scoreboard_summary": "",
"supplement_ratio_ins_co": "",
"tasks_date": "",
"tasks_date_employee": "",
"thank_you_date": "",
"timetickets": "",
"timetickets_employee": "",
@@ -2999,6 +3025,92 @@
"updated": ""
}
},
"tasks": {
"actions": {
"edit": "",
"new": ""
},
"buttons": {
"allTasks": "",
"complete": "",
"create": "",
"delete": "",
"edit": "",
"myTasks": "",
"refresh": ""
},
"date_presets": {
"completion": "",
"day": "",
"days": "",
"delivery": "",
"next_week": "",
"one_month": "",
"three_months": "",
"three_weeks": "",
"today": "",
"tomorrow": "",
"two_weeks": ""
},
"failures": {
"completed": "",
"created": "",
"deleted": "",
"updated": ""
},
"fields": {
"actions": "",
"assigned_to": "",
"bill": "",
"billid": "",
"completed": "",
"created_at": "",
"description": "",
"due_date": "",
"job": {
"ro_number": ""
},
"jobid": "",
"jobline": "",
"joblineid": "",
"parts_order": "",
"partsorderid": "",
"priorities": {
"high": "",
"low": "",
"medium": ""
},
"priority": "",
"remind_at": "",
"title": ""
},
"placeholders": {
"assigned_to": "",
"billid": "",
"description": "",
"jobid": "",
"joblineid": "",
"partsorderid": ""
},
"successes": {
"completed": "",
"created": "",
"deleted": "",
"updated": ""
},
"titles": {
"all_tasks": "",
"completed": "",
"deleted": "",
"job_tasks": "",
"mine": "",
"my_tasks": ""
},
"validation": {
"due_at_error_message": "",
"remind_at_error_message": ""
}
},
"tech": {
"fields": {
"employeeid": "",
@@ -3103,11 +3215,13 @@
"accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "",
"all_tasks": "",
"app": "",
"bc": {
"accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "",
"all_tasks": "",
"availablejobs": "",
"bills-list": "",
"contracts": "",
@@ -3131,6 +3245,7 @@
"jobs-intake": "",
"jobs-new": "",
"jobs-ready": "",
"my_tasks": "",
"owner-detail": "",
"owners": "",
"parts-queue": "",
@@ -3145,6 +3260,7 @@
"shop-csi": "",
"shop-templates": "",
"shop-vendors": "",
"tasks": "",
"temporarydocs": "",
"timetickets": "",
"ttapprovals": "",
@@ -3175,6 +3291,7 @@
"jobsdetail": "Trabajo {{ro_number}} | {{app}}",
"jobsdocuments": "Documentos de trabajo {{ro_number}} | {{app}}",
"manageroot": "Casa | {{app}}",
"my_tasks": "",
"owners": "Todos los propietarios | {{app}}",
"owners-detail": "",
"parts-queue": "",
@@ -3194,6 +3311,7 @@
"shop-csi": "",
"shop-templates": "",
"shop_vendors": "Vendedores | {{app}}",
"tasks": "",
"techconsole": "{{app}}",
"techjobclock": "{{app}}",
"techjoblookup": "{{app}}",

View File

@@ -116,6 +116,7 @@
"jobassignmentchange": "",
"jobassignmentremoved": "",
"jobchecklist": "",
"jobclosedwithbypass": "",
"jobconverted": "",
"jobdelivery": "",
"jobexported": "",
@@ -134,7 +135,13 @@
"jobstatuschange": "",
"jobsupplement": "",
"jobsuspend": "",
"jobvoid": ""
"jobvoid": "",
"tasks_completed": "",
"tasks_created": "",
"tasks_deleted": "",
"tasks_uncompleted": "",
"tasks_undeleted": "",
"tasks_updated": ""
}
},
"billlines": {
@@ -223,10 +230,12 @@
"markexported": "",
"markforreexport": "",
"new": "",
"nobilllines": "",
"noneselected": "",
"onlycmforinvoiced": "",
"printlabels": "",
"retailtotal": "",
"returnfrombill": "",
"savewithdiscrepancy": "",
"state_tax": "",
"subtotal": "",
@@ -860,7 +869,8 @@
"inservice": "",
"leasereturn": "",
"out": "",
"sold": ""
"sold": "",
"unavailable": ""
},
"successes": {
"saved": ""
@@ -930,7 +940,8 @@
"scheduledindate": "",
"scheduledintoday": "",
"scheduledoutdate": "",
"scheduledouttoday": ""
"scheduledouttoday": "",
"tasks": ""
}
},
"dms": {
@@ -1125,6 +1136,7 @@
"delete": "Effacer",
"deleteall": "",
"deselectall": "",
"download": "",
"edit": "modifier",
"login": "",
"print": "",
@@ -1350,6 +1362,7 @@
"amount": "",
"dateOfPayment": "",
"descriptions": "",
"hint": "",
"payer": "",
"payername": "",
"paymentid": "",
@@ -1540,6 +1553,7 @@
"voiding": ""
},
"fields": {
"active_tasks": "",
"actual_completion": "Achèvement réel",
"actual_delivery": "Livraison réelle",
"actual_in": "En réel",
@@ -2003,6 +2017,7 @@
"ppc": "",
"ppdnotexported": "",
"profileadjustments": "",
"profitbypassrequired": "",
"profits": "",
"prt_dsmk_total": "",
"rates": "Les taux",
@@ -2024,12 +2039,12 @@
"remove_from_ar": "",
"returntotals": "",
"ro_guard": {
"eforce_profit": "",
"enforce_ar": "",
"enforce_bills": "",
"enforce_cm": "",
"enforce_labor": "",
"enforce_ppd": "",
"enforce_profit": "",
"enforce_sublet": "",
"enforce_validation": "",
"enforced": ""
@@ -2052,6 +2067,7 @@
"supplementnote": "",
"suspended": "",
"suspense": "",
"tasks": "",
"threshhold": "",
"total_cost": "",
"total_cust_payable": "",
@@ -2149,6 +2165,7 @@
"accounting-payments": "",
"accounting-receivables": "",
"activejobs": "Emplois actifs",
"all_tasks": "",
"alljobs": "",
"allpayments": "",
"availablejobs": "Emplois disponibles",
@@ -2157,6 +2174,7 @@
"courtesycars-all": "",
"courtesycars-contracts": "",
"courtesycars-newcontract": "",
"create_task": "",
"customers": "Les clients",
"dashboard": "",
"enterbills": "",
@@ -2169,6 +2187,7 @@
"home": "Accueil",
"inventory": "",
"jobs": "Emplois",
"my_tasks": "",
"newjob": "",
"owners": "Propriétaires",
"parts-queue": "",
@@ -2195,6 +2214,7 @@
"shop_csi": "",
"shop_templates": "",
"shop_vendors": "Vendeurs",
"tasks": "",
"temporarydocs": "",
"timetickets": "",
"ttapprovals": "",
@@ -2388,6 +2408,7 @@
"percent_accepted": ""
},
"labels": {
"notyetdispatched": "",
"parts_dispatch": ""
}
},
@@ -2504,6 +2525,7 @@
"markexported": "",
"markreexported": "",
"payment": "",
"paymentupdate": "",
"stripe": ""
}
},
@@ -2605,6 +2627,7 @@
"job_costing_ro": "",
"job_lifecycle_ro": "",
"job_notes": "",
"job_tasks": "",
"key_tag": "",
"labels": {
"count": "",
@@ -2805,6 +2828,7 @@
"parts_orders": "",
"payments": "",
"scoreboard": "",
"tasks": "",
"timetickets": ""
},
"vendor": ""
@@ -2920,6 +2944,8 @@
"scoreboard_detail": "",
"scoreboard_summary": "",
"supplement_ratio_ins_co": "",
"tasks_date": "",
"tasks_date_employee": "",
"thank_you_date": "",
"timetickets": "",
"timetickets_employee": "",
@@ -2999,6 +3025,92 @@
"updated": ""
}
},
"tasks": {
"actions": {
"edit": "",
"new": ""
},
"buttons": {
"allTasks": "",
"complete": "",
"create": "",
"delete": "",
"edit": "",
"myTasks": "",
"refresh": ""
},
"date_presets": {
"completion": "",
"day": "",
"days": "",
"delivery": "",
"next_week": "",
"one_month": "",
"three_months": "",
"three_weeks": "",
"today": "",
"tomorrow": "",
"two_weeks": ""
},
"failures": {
"completed": "",
"created": "",
"deleted": "",
"updated": ""
},
"fields": {
"actions": "",
"assigned_to": "",
"bill": "",
"billid": "",
"completed": "",
"created_at": "",
"description": "",
"due_date": "",
"job": {
"ro_number": ""
},
"jobid": "",
"jobline": "",
"joblineid": "",
"parts_order": "",
"partsorderid": "",
"priorities": {
"high": "",
"low": "",
"medium": ""
},
"priority": "",
"remind_at": "",
"title": ""
},
"placeholders": {
"assigned_to": "",
"billid": "",
"description": "",
"jobid": "",
"joblineid": "",
"partsorderid": ""
},
"successes": {
"completed": "",
"created": "",
"deleted": "",
"updated": ""
},
"titles": {
"all_tasks": "",
"completed": "",
"deleted": "",
"job_tasks": "",
"mine": "",
"my_tasks": ""
},
"validation": {
"due_at_error_message": "",
"remind_at_error_message": ""
}
},
"tech": {
"fields": {
"employeeid": "",
@@ -3103,11 +3215,13 @@
"accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "",
"all_tasks": "",
"app": "",
"bc": {
"accounting-payables": "",
"accounting-payments": "",
"accounting-receivables": "",
"all_tasks": "",
"availablejobs": "",
"bills-list": "",
"contracts": "",
@@ -3131,6 +3245,7 @@
"jobs-intake": "",
"jobs-new": "",
"jobs-ready": "",
"my_tasks": "",
"owner-detail": "",
"owners": "",
"parts-queue": "",
@@ -3145,6 +3260,7 @@
"shop-csi": "",
"shop-templates": "",
"shop-vendors": "",
"tasks": "",
"temporarydocs": "",
"timetickets": "",
"ttapprovals": "",
@@ -3175,6 +3291,7 @@
"jobsdetail": "Travail {{ro_number}} | {{app}}",
"jobsdocuments": "Documents de travail {{ro_number}} | {{app}}",
"manageroot": "Accueil | {{app}}",
"my_tasks": "",
"owners": "Tous les propriétaires | {{app}}",
"owners-detail": "",
"parts-queue": "",
@@ -3194,6 +3311,7 @@
"shop-csi": "",
"shop-templates": "",
"shop_vendors": "Vendeurs | {{app}}",
"tasks": "",
"techconsole": "{{app}}",
"techjobclock": "{{app}}",
"techjoblookup": "{{app}}",

View File

@@ -24,6 +24,7 @@ const AuditTrailMapping = {
jobimported: () => i18n.t("audit_trail.messages.jobimported"),
jobinproductionchange: (inproduction) => i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }),
jobinvoiced: () => i18n.t("audit_trail.messages.jobinvoiced"),
jobclosedwithbypass: () => i18n.t("audit_trail.messages.jobclosedwithbypass"),
jobmodifylbradj: ({ mod_lbr_ty, hours }) => i18n.t("audit_trail.messages.jobmodifylbradj", { mod_lbr_ty, hours }),
jobnoteadded: () => i18n.t("audit_trail.messages.jobnoteadded"),
jobnoteupdated: () => i18n.t("audit_trail.messages.jobnoteupdated"),
@@ -39,7 +40,38 @@ const AuditTrailMapping = {
jobstatuschange: (status) => i18n.t("audit_trail.messages.jobstatuschange", { status }),
jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"),
jobsuspend: (status) => i18n.t("audit_trail.messages.jobsuspend", { status }),
jobvoid: () => i18n.t("audit_trail.messages.jobvoid")
jobvoid: () => i18n.t("audit_trail.messages.jobvoid"),
// Tasks Entries
tasksCreated: (title, createdBy) =>
i18n.t("audit_trail.messages.tasks_created", {
title,
createdBy
}),
tasksUpdated: (title, updatedBy) =>
i18n.t("audit_trail.messages.tasks_updated", {
title,
updatedBy
}),
tasksDeleted: (title, deletedBy) =>
i18n.t("audit_trail.messages.tasks_deleted", {
title,
deletedBy
}),
tasksUndeleted: (title, undeletedBy) =>
i18n.t("audit_trail.messages.tasks_undeleted", {
title,
undeletedBy
}),
tasksCompleted: (title, completedBy) =>
i18n.t("audit_trail.messages.tasks_completed", {
title,
completedBy
}),
tasksUncompleted: (title, uncompletedBy) =>
i18n.t("audit_trail.messages.tasks_uncompleted", {
title,
uncompletedBy
})
};
export default AuditTrailMapping;

View File

@@ -1,5 +1,6 @@
import axios from "axios";
import { auth } from "../firebase/firebase.utils";
import InstanceRenderManager from "./instanceRenderMgr";
axios.defaults.baseURL =
import.meta.env.VITE_APP_AXIOS_BASE_API_URL ||
@@ -12,6 +13,15 @@ export const axiosAuthInterceptorId = axios.interceptors.request.use(
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
InstanceRenderManager({
executeFunction: true,
args: [],
promanager: () => {
if (!config.url.startsWith("http://localhost:1337")) {
config.headers["Convenient-Company"] = "promanager";
}
}
});
}
return config;

View File

@@ -10,6 +10,7 @@ import client from "../utils/GraphQLClient";
import cleanAxios from "./CleanAxios";
import { TemplateList } from "./TemplateConstants";
import { generateTemplate } from "./graphQLmodifier";
import InstanceRenderManager from "./instanceRenderMgr";
const server = import.meta.env.VITE_APP_REPORTS_SERVER_URL;
jsreport.serverUrl = server;
@@ -71,8 +72,8 @@ export default async function RenderTemplate(
...contextData,
...templateObject.variables,
...templateObject.context,
headerpath: `/${bodyshop.imexshopid}/header.html`,
footerpath: `/${bodyshop.imexshopid}/footer.html`,
headerpath: `/${InstanceRenderManager({ imex: bodyshop.imexshopid, rome: bodyshop.imexshopid, promanager: "GENERIC" })}/header.html`,
footerpath: `/${InstanceRenderManager({ imex: bodyshop.imexshopid, rome: bodyshop.imexshopid, promanager: "GENERIC" })}/footer.html`,
bodyshop: bodyshop,
filters: templateObject?.filters,
sorters: templateObject?.sorters,

View File

@@ -587,6 +587,14 @@ export const TemplateList = (type, context) => {
key: "job_lifecycle_ro",
disabled: false,
group: "post"
},
job_tasks: {
title: i18n.t("printcenter.jobs.job_tasks"),
description: "",
subject: i18n.t("printcenter.jobs.job_tasks"),
key: "job_tasks",
disabled: false,
group: "ro"
}
}
: {}),
@@ -2089,6 +2097,30 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_invoiced")
},
group: "jobs"
},
tasks_date: {
title: i18n.t("reportcenter.templates.tasks_date"),
subject: i18n.t("reportcenter.templates.tasks_date"),
key: "tasks_date",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.tasks"),
field: i18n.t("tasks.fields.created_at")
},
group: "jobs"
},
tasks_date_employee: {
title: i18n.t("reportcenter.templates.tasks_date_employee"),
subject: i18n.t("reportcenter.templates.tasks_date_employee"),
key: "tasks_date_employee",
idtype: "employee",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.tasks"),
field: i18n.t("tasks.fields.created_at")
},
group: "jobs"
}
}
: {}),

View File

@@ -1,4 +1,5 @@
export default async function FcmHandler({ client, payload }) {
console.log("FCM", payload);
switch (payload.type) {
case "messaging-inbound":
client.cache.modify({

View File

@@ -1,3 +1,13 @@
/**
* Replaces undefined values with null in an object.
* Optionally, you can specify keys to replace.
* If keys are specified, only those keys will be replaced.
* If no keys are specified, all undefined values will be replaced.
* @param obj
* @param keys
* @returns {*}
* @constructor
*/
export default function UndefinedToNull(obj, keys) {
Object.keys(obj).forEach((key) => {
if (keys && keys.indexOf(key) >= 0) {
@@ -8,3 +18,21 @@ export default function UndefinedToNull(obj, keys) {
});
return obj;
}
/**
* Replaces undefined values with null in an object. Optionally, you can specify keys to replace. If keys are specified, only those keys will be replaced. If no keys are specified, all undefined values will be replaced.
* @param obj
* @param keys
* @returns {{[p: string]: unknown}}
*/
export function replaceUndefinedWithNull(obj, keys) {
return Object.fromEntries(
Object.entries(obj).map(([key, value]) => {
if (keys) {
return [key, keys.includes(key) && value === undefined ? null : value];
} else {
return [key, value === undefined ? null : value];
}
})
);
}

View File

@@ -5,6 +5,8 @@ import * as path from "path";
import * as url from "url";
import { defineConfig } from "vite";
import { ViteEjsPlugin } from "vite-plugin-ejs";
import eslint from 'vite-plugin-eslint';
//import CompressionPlugin from 'vite-plugin-compression';
import { VitePWA } from "vite-plugin-pwa";
import InstanceRenderManager from "./src/utils/instanceRenderMgr";
@@ -100,7 +102,8 @@ export default defineConfig({
}
}),
reactVirtualized(),
react()
react(),
eslint(),
// CompressionPlugin(), //Cloudfront already compresses assets, so not needed.
],
define: {

View File

@@ -1,6 +1,6 @@
actions: [ ]
actions: []
custom_types:
enums: [ ]
input_objects: [ ]
objects: [ ]
scalars: [ ]
enums: []
input_objects: []
objects: []
scalars: []

View File

@@ -1 +1 @@
[ ]
[]

View File

@@ -1 +1,8 @@
[ ]
- name: Task Reminders
webhook: '{{HASURA_API_URL}}/tasks-remind-handler'
schedule: '*/15 * * * *'
include_in_metadata: true
payload: {}
headers:
- name: event-secret
value_from_env: EVENT_SECRET

View File

@@ -1 +1 @@
{ }
{}

View File

@@ -1 +1 @@
[ ]
[]

View File

@@ -1 +1 @@
[ ]
[]

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