Compare commits

...

184 Commits

Author SHA1 Message Date
Allan Carr
4a16df36dd IO-2848 UPDATE_JOB query
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-22 17:35:05 -07:00
Dave Richer
7ba3ed2b89 Merged in release/2024-07-19 (pull request #1531)
Release/2024 07 19
2024-07-19 20:02:39 +00:00
Dave Richer
2cc0b7d741 Merged in bugfix/ProductFruitsWrapper (pull request #1530)
Bugfix/ProductFruitsWrapper
2024-07-19 20:02:11 +00:00
Dave Richer
97693fbcff - Add Prop Types to ProductFruitsWrapper.jsx
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-07-19 16:01:40 -04:00
Dave Richer
5e94b1a71e - Improve product fruits wrapper for extra checks
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-07-19 15:50:09 -04:00
Allan Carr
970275e62a Merged in release/2024-07-19 (pull request #1529)
Release/2024 07 19
2024-07-19 19:02:15 +00:00
Dave Richer
aa076da255 Merged in bugfix/ProductFruitsWrapper (pull request #1527)
- Improve product fruits wrapper for extra checks

Approved-by: Allan Carr
2024-07-19 17:24:22 +00:00
Dave Richer
3dd154de79 - Improve product fruits wrapper for extra checks
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-07-19 13:23:43 -04:00
Dave Richer
72a2366abe - Improve product fruits wrapper for extra checks
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-07-19 13:18:10 -04:00
Allan Carr
36f517e8e1 Merged in feature/IO-2845-Payment-by-Date-Grouped-by-Payment-Type (pull request #1525)
IO-2845 Payments Grouped by Payment Type

Approved-by: Dave Richer
2024-07-19 17:16:26 +00:00
Allan Carr
981fb57d36 Merged in feature/IO-2847-Employee-Rate-Filter (pull request #1526)
IO-2847 Employee Rate Filter

Approved-by: Dave Richer
2024-07-19 17:15:04 +00:00
Allan Carr
a059c2b5a8 IO-2847 Employee Rate Filter
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-19 08:41:12 -07:00
Allan Carr
207bb39672 IO-2845 Payments Grouped by Payment Type
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-17 14:15:38 -07:00
Dave Richer
16e34e4ed9 Merged in bugfix/productfruits (pull request #1523)
- misc updates / clear stage
2024-07-17 16:30:53 +00:00
Dave Richer
cd04f2b2b2 Merged in bugfix/productfruits (pull request #1524)
- misc updates / clear stage
2024-07-17 16:30:40 +00:00
Dave Richer
26e164b4d1 - misc updates / clear stage
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-07-17 12:22:31 -04:00
Allan Carr
ae6b2fe4f5 Merged in feature/IO-2843-QBO_USA-Switch-for-IO (pull request #1521)
IO-2843 State Tax for QBO_USA and Region CA_
2024-07-15 23:53:59 +00:00
Allan Carr
4ca686126a IO-2843 State Tax for QBO_USA and Region CA_
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-15 16:53:48 -07:00
Allan Carr
c254a8abfe Merged in release/2024-07-12 (pull request #1520)
Release/2024 07 12
2024-07-12 20:15:53 +00:00
Allan Carr
f864e40a90 Merged in feature/IO-2836-Charts-Route (pull request #1518)
IO-2836 Charts Route

Approved-by: Dave Richer
2024-07-11 22:17:01 +00:00
Allan Carr
802f70dde8 IO-2836 Charts Route
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-11 11:25:27 -07:00
Allan Carr
6cb4fd6b93 Merged in feature/IO-2839-DMS-Allocation-Labels (pull request #1514)
IO-2839 DMS Allocation Labels

Approved-by: Dave Richer
2024-07-11 17:10:49 +00:00
Allan Carr
70ca6edcb2 Merged in feature/IO-2840-Area-of-Damage-Correction (pull request #1515)
IO-2840 Correct for Area of Damage that is only 1 Charater intead of 2

Approved-by: Dave Richer
2024-07-11 17:09:41 +00:00
Allan Carr
9dcc861740 Merged in IO-2841-Non-Parts-in-jobline_status (pull request #1516)
IO-2841 Non-Parts listed in Jobline_Status

Approved-by: Dave Richer
2024-07-11 17:09:04 +00:00
Allan Carr
fecb7fb24b Merged in feature/IO-2837-Job-Card-Add-to-Scoreboard (pull request #1517)
IO-2837 Add to Scoreboard from Job Drawer

Approved-by: Dave Richer
2024-07-11 17:06:37 +00:00
Allan Carr
c5b94db8af Merged in feature/IO-2520-Kaizen-Data-Pump (pull request #1513)
IO-2520 Kaizen Data Pump

Approved-by: Dave Richer
2024-07-11 17:06:23 +00:00
Allan Carr
6690c9c692 IO-2841 Non-Parts listed in Jobline_Status
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-11 09:15:18 -07:00
Allan Carr
506edcb3c6 IO-2837 Add to Scoreboard from Job Drawer
Query restricted necessary lines for proper labour calculations

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-10 16:45:35 -07:00
Allan Carr
c45e53e38b IO-2840 Correct for Area of Damage that is only 1 Charater intead of 2
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-10 16:00:47 -07:00
Allan Carr
d8d8a4701e IO-2839 DMS Allocation Labels
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-10 14:54:04 -07:00
Allan Carr
970c4ee9e1 IO-2520 Kaizen Data Pump
Additional fields requested

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-10 10:33:53 -07:00
Allan Carr
c2386f43ef Merge branch 'master-AIO' into feature/IO-2520-Kaizen-Data-Pump
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-10 10:32:21 -07:00
Allan Carr
0fd3e75862 Merged in feature/IO-2835-CDK-Calculate-Allocations-Route (pull request #1511)
IO-2835 CDK Calculate Allocation Route

Approved-by: Dave Richer
2024-07-09 15:54:21 +00:00
Allan Carr
fcd3234fd9 Merged in feature/IO-2838-JobCloseRoGuardSublet (pull request #1512)
IO-2838 JobCloseRoGuardSublet

Approved-by: Dave Richer
2024-07-09 15:52:45 +00:00
Allan Carr
2afa810e6c IO-2838 JobCloseRoGuardSublet
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-08 18:15:47 -07:00
Allan Carr
ff33b924b2 IO-2835 CDK Calculate Allocation Route
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-07-08 18:07:33 -07:00
Dave Richer
84bb25985b Merged in release/2024-06-28 (pull request #1510)
Release - 2024/06/28

Approved-by: Allan Carr
2024-06-28 23:02:23 +00:00
Allan Carr
e38a58550f Merged in feature/IO-2832-Purchases-by-RO-Date (pull request #1508)
IO-2832 Purchases by RO - Invoice Date bound

Approved-by: Dave Richer
2024-06-28 15:33:02 +00:00
Allan Carr
25ef4c6228 IO-2832 Purchases by RO - Invoice Date bound
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-27 18:57:43 -07:00
Allan Carr
277fbeebc8 Merged in release/2024-06-28 (pull request #1506)
Release/2024 06 28

Approved-by: Dave Richer
2024-06-26 18:09:45 +00:00
Allan Carr
399df78957 Merged in feature/IO-2793-QBO-State-Tax-Null (pull request #1504)
IO-2793 Insure Part Tax Type Exists
2024-06-26 16:31:40 +00:00
Allan Carr
294325343b IO-2793 Insure Part Tax Type Exists
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-26 09:32:04 -07:00
Allan Carr
ed17eec948 Merged in feature/IO-2829-Multiple-Towing-Lines (pull request #1500)
IO-2829 Multiple Towing Lines

Approved-by: Dave Richer
2024-06-26 00:51:52 +00:00
Allan Carr
f87c95079c Merged in feature/IO-2830-Bill-Line-Select-Search-Component (pull request #1501)
IO-2830 Bill Line Select Search Component

Approved-by: Dave Richer
2024-06-26 00:50:58 +00:00
Allan Carr
327149ffc9 IO-2830 Bill Line Select Search Component
remove unused variable

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-25 13:32:01 -07:00
Allan Carr
f81b21b933 IO-2829 Multiple Towing Lines
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-25 13:26:56 -07:00
Allan Carr
a559b56983 Merged in feature/IO-2520-Kaizen-Data-Pump (pull request #1499)
IO-2520 Kaizen Data Pump

Approved-by: Dave Richer
2024-06-25 19:22:55 +00:00
Allan Carr
6a9030b653 IO-2520 Kaizen Data Pump
Add in Repair Line Details, Time Ticket Details, Void Date

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-25 11:57:38 -07:00
Dave Richer
fc7da187f4 Merged in release/2024-06-21 (pull request #1498)
Release/2024 06 21
2024-06-21 22:47:20 +00:00
Allan Carr
f593f83ec1 Merged in feature/IO-2820-Adjust-to-Bottom-Line-State-Tax (pull request #1496)
IO-2820 Adjustment to Bottom Line
2024-06-21 22:32:03 +00:00
Allan Carr
5752f123ac IO-2820 Adjustment to Bottom Line
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-21 15:32:55 -07:00
Allan Carr
6396d68584 Merged in feature/IO-2828-Deliver-Checklist-Delete (pull request #1494)
IO-2828 Add InstanceManager and correct delete button for delivery checklist
2024-06-21 18:14:38 +00:00
Allan Carr
bfd29f25dd IO-2828 Add InstanceManager and correct delete button for delivery checklist
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-21 11:12:10 -07:00
Allan Carr
e75e35e4ee Merged in feature/IO-2793-State-Tax-Null-QBO (pull request #1493)
Feature/IO-2793 State Tax Null QBO
2024-06-20 21:39:24 +00:00
Allan Carr
d7ddbf7e8d IO-2793 Change Additional Costs tax item
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-20 14:15:10 -07:00
Allan Carr
1b0198af63 Merged in feature/IO-2793-State-Tax-Null-QBO (pull request #1491)
IO-2793 Change Additional Costs tax item
2024-06-20 21:14:35 +00:00
Allan Carr
ace16ba873 IO-2793 Adjustment for parts
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-20 13:46:54 -07:00
Allan Carr
f57a4bd948 Merged in feature/IO-2793-State-Tax-Null-QBO (pull request #1489)
IO-2793 Adjustment for parts
2024-06-20 20:45:44 +00:00
Allan Carr
fef4393f9c Merged in feature/IO-2795-Delivery-Checklist-Option (pull request #1487)
IO-2795 Delivery Date required on Delivery Checklist

Approved-by: Dave Richer
2024-06-20 18:43:20 +00:00
Allan Carr
47adb6d40a IO-2795 Delivery Date required on Delivery Checklist
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-20 11:26:05 -07:00
Allan Carr
4940b10910 IO-2793 Adjustmnet for OP14
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-20 08:30:58 -07:00
Allan Carr
9c8e241ef7 Merged in feature/IO-2793-State-Tax-Null-QBO (pull request #1486)
IO-2793 Adjustmnet for OP14
2024-06-20 15:30:14 +00:00
Allan Carr
c3d6b98c89 Merged in feature/IO-2820-Adjust-to-Bottom-Line-State-Tax (pull request #1483)
IO-2820 State Tax & Adjustment to Bottom Line

Approved-by: Dave Richer
2024-06-18 20:39:40 +00:00
Allan Carr
4b49654083 Merged in feature/IO-2814-Job-Costing-Correction (pull request #1484)
IO-2814 Job Costing Correction for OP14 Duplication

Approved-by: Dave Richer
2024-06-18 20:39:16 +00:00
Allan Carr
78223078f4 Merged in feature/IO-2816-Unsaved-Changes-Manual-Creation-of-Job (pull request #1485)
IO-2816 Unsaved Changes on Manual Job Creation

Approved-by: Dave Richer
2024-06-18 20:39:03 +00:00
Allan Carr
ca4b78d44c IO-2816 Unsaved Changes on Manual Job Creation
Correct Hardcoded button labels

Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-18 13:16:58 -07:00
Allan Carr
d7bfc789e2 IO-2814 Job Costing Correction for OP14 Duplication
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-18 08:44:35 -07:00
Allan Carr
b54d0ed62a Merge branch 'master-AIO' into feature/IO-2814-Job-Costing-Correction
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-18 08:43:51 -07:00
Allan Carr
8386443cb0 IO-2820 State Tax & Adjustment to Bottom Line
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-06-17 10:13:36 -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
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
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
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
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
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
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
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
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
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
6513432993 Merge branch 'feature/IO-2677-Tasks' into test-AIO 2024-04-18 16:28:20 -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
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
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
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
77 changed files with 16951 additions and 13782 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
VITE_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql 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_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
VITE_APP_GA_CODE=231099835 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":"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_API=https://api.cloudinary.com/v1_1/io-test
VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test VITE_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
VITE_APP_CLOUDINARY_API_KEY=957865933348715 VITE_APP_CLOUDINARY_API_KEY=957865933348715

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

@@ -7,9 +7,7 @@ import { connect } from "react-redux";
import { Route, Routes } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import DocumentEditorContainer from "../components/document-editor/document-editor.container"; import DocumentEditorContainer from "../components/document-editor/document-editor.container";
import ErrorBoundary from "../components/error-boundary/error-boundary.component"; import ErrorBoundary from "../components/error-boundary/error-boundary.component"; // Component Imports
//Component Imports
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
import DisclaimerPage from "../pages/disclaimer/disclaimer.page"; import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
import LandingPage from "../pages/landing/landing.page"; import LandingPage from "../pages/landing/landing.page";
@@ -23,20 +21,21 @@ import "./App.styles.scss";
import handleBeta from "../utils/betaHandler"; import handleBeta from "../utils/betaHandler";
import Eula from "../components/eula/eula.component"; import Eula from "../components/eula/eula.component";
import InstanceRenderMgr from "../utils/instanceRenderMgr"; import InstanceRenderMgr from "../utils/instanceRenderMgr";
import { ProductFruits } from "react-product-fruits"; import ProductFruitsWrapper from "./ProductFruitsWrapper.jsx";
const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component")); const ResetPassword = lazy(() => import("../pages/reset-password/reset-password.component"));
const ManagePage = lazy(() => import("../pages/manage/manage.page.container")); const ManagePage = lazy(() => import("../pages/manage/manage.page.container"));
const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page")); const SignInPage = lazy(() => import("../pages/sign-in/sign-in.page"));
const CsiPage = lazy(() => import("../pages/csi/csi.container.page")); const CsiPage = lazy(() => import("../pages/csi/csi.container.page"));
const MobilePaymentContainer = lazy(() => import("../pages/mobile-payment/mobile-payment.container")); const MobilePaymentContainer = lazy(() => import("../pages/mobile-payment/mobile-payment.container"));
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
online: selectOnline, online: selectOnline,
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentEula: selectCurrentEula currentEula: selectCurrentEula
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
checkUserSession: () => dispatch(checkUserSession()), checkUserSession: () => dispatch(checkUserSession()),
setOnline: (isOnline) => dispatch(setOnline(isOnline)) setOnline: (isOnline) => dispatch(setOnline(isOnline))
@@ -60,11 +59,11 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
// Associate event listeners, memoize to prevent multiple listeners being added // Associate event listeners, memoize to prevent multiple listeners being added
useEffect(() => { useEffect(() => {
const offlineListener = (e) => { const offlineListener = () => {
setOnline(false); setOnline(false);
}; };
const onlineListener = (e) => { const onlineListener = () => {
setOnline(true); setOnline(true);
}; };
@@ -98,7 +97,7 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
InstanceRenderMgr({ InstanceRenderMgr({
imex: "gvfvfw/bodyshopapp", imex: "gvfvfw/bodyshopapp",
rome: "rome-online/rome-online", rome: "rome-online/rome-online",
promanager: "" //TODO:AIO Add in log rocket for promanager instances. promanager: "" // TODO: AIO Add in log rocket for promanager instances.
}) })
); );
} }
@@ -111,24 +110,20 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
handleBeta(); handleBeta();
if (!online) if (!online) {
return ( return (
<Result <Result
status="warning" status="warning"
title={t("general.labels.nointernet")} title={t("general.labels.nointernet")}
subTitle={t("general.labels.nointernet_sub")} subTitle={t("general.labels.nointernet_sub")}
extra={ extra={
<Button <Button type="primary" onClick={() => window.location.reload()}>
type="primary"
onClick={() => {
window.location.reload();
}}
>
{t("general.actions.refresh")} {t("general.actions.refresh")}
</Button> </Button>
} }
/> />
); );
}
if (currentEula && !currentUser.eulaIsAccepted) { if (currentEula && !currentUser.eulaIsAccepted) {
return <Eula />; return <Eula />;
@@ -147,18 +142,13 @@ export function App({ bodyshop, checkUserSession, currentUser, online, setOnline
/> />
} }
> >
<ProductFruits <ProductFruitsWrapper
currentUser={currentUser}
workspaceCode={InstanceRenderMgr({ workspaceCode={InstanceRenderMgr({
imex: null, imex: null,
rome: null, rome: "9BkbEseqNqxw8jUH",
promanager: "aoJoEifvezYI0Z0P" promanager: "aoJoEifvezYI0Z0P"
})} })}
debug
language="en"
user={{
email: currentUser.email,
username: currentUser.email
}}
/> />
<Routes> <Routes>

View File

@@ -0,0 +1,32 @@
import React from "react";
import { ProductFruits } from "react-product-fruits";
import PropTypes from "prop-types";
const ProductFruitsWrapper = React.memo(({ currentUser, workspaceCode }) => {
return (
workspaceCode &&
currentUser?.authorized === true &&
currentUser?.email && (
<ProductFruits
lifeCycle="unmount"
workspaceCode={workspaceCode}
debug
language="en"
user={{
email: currentUser.email,
username: currentUser.email
}}
/>
)
);
});
export default ProductFruitsWrapper;
ProductFruitsWrapper.propTypes = {
currentUser: PropTypes.shape({
authorized: PropTypes.bool,
email: PropTypes.string
}).isRequired,
workspaceCode: PropTypes.string.isRequired
};

View File

@@ -4,7 +4,6 @@ import { useTranslation } from "react-i18next";
import InstanceRenderMgr from "../../utils/instanceRenderMgr"; import InstanceRenderMgr from "../../utils/instanceRenderMgr";
//To be used as a form element only. //To be used as a form element only.
const { Option } = Select;
const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps }, ref) => { const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps }, ref) => {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -25,31 +24,27 @@ const BillLineSearchSelect = ({ options, disabled, allowRemoved, ...restProps },
); );
}} }}
notFoundContent={"Removed."} notFoundContent={"Removed."}
{...restProps} options={[
> { value: "noline", label: t("billlines.labels.other"), name: t("billlines.labels.other") },
<Select.Option key={null} value={"noline"} cost={0} line_desc={""}> ...options.map((item) => ({
{t("billlines.labels.other")} disabled: allowRemoved ? false : item.removed,
</Select.Option> key: item.id,
{options value: item.id,
? options.map((item) => ( cost: item.act_price ? item.act_price : 0,
<Option part_type: item.part_type,
disabled={allowRemoved ? false : item.removed} line_desc: item.line_desc,
key={item.id} part_qty: item.part_qty,
value={item.id} oem_partno: item.oem_partno,
cost={item.act_price ? item.act_price : 0} alt_partno: item.alt_partno,
part_type={item.part_type} act_price: item.act_price,
line_desc={item.line_desc} style: {
part_qty={item.part_qty} ...(item.removed ? { textDecoration: "line-through" } : {})
oem_partno={item.oem_partno} },
alt_partno={item.alt_partno} name: `${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
act_price={item.act_price} item.oem_partno ? ` - ${item.oem_partno}` : ""
style={{ }${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim(),
...(item.removed ? { textDecoration: "line-through" } : {}) label: (
}} <>
name={`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
>
<span> <span>
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${ {`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : "" item.oem_partno ? ` - ${item.oem_partno}` : ""
@@ -60,14 +55,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.mod_lb_hrs} units`}</span>
) )
})} })}
<span style={{ float: "right", paddingleft: "1rem" }}> <span style={{ float: "right", paddingleft: "1rem" }}>
{item.act_price ? `$${item.act_price && item.act_price.toFixed(2)}` : ``} {item.act_price ? `$${item.act_price && item.act_price.toFixed(2)}` : ``}
</span> </span>
</Option> </>
)) )
: null} }))
</Select> ]}
{...restProps}
></Select>
); );
}; };
export default forwardRef(BillLineSearchSelect); export default forwardRef(BillLineSearchSelect);

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,6 @@ import {
Typography Typography
} from "antd"; } from "antd";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import dayjs from "../../utils/day";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -22,6 +21,7 @@ import { createStructuredSelector } from "reselect";
import { determineDmsType } from "../../pages/dms/dms.container"; import { determineDmsType } from "../../pages/dms/dms.container";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import i18n from "../../translations/i18n"; import i18n from "../../translations/i18n";
import dayjs from "../../utils/day";
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component"; import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component"; import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
@@ -89,7 +89,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
job.area_of_damage && job.area_of_damage.impact1 job.area_of_damage && job.area_of_damage.impact1
? " " + ? " " +
t("jobs.labels.dms.damageto", { t("jobs.labels.dms.damageto", {
area_of_damage: (job.area_of_damage && job.area_of_damage.impact1) || "UNKNOWN" area_of_damage: (job.area_of_damage && job.area_of_damage.impact1.padStart(2, "0")) || "UNKNOWN"
}) })
: "" : ""
}`.slice(0, 239), }`.slice(0, 239),

View File

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

View File

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

View File

@@ -32,6 +32,7 @@ import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { BsKanban } from "react-icons/bs"; import { BsKanban } from "react-icons/bs";
import { FaCalendarAlt, FaCarCrash, FaCreditCard, FaFileInvoiceDollar, FaTasks } 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 { GiPayMoney, GiPlayerTime, GiSettingsKnobs } from "react-icons/gi";
import { IoBusinessOutline } from "react-icons/io5"; import { IoBusinessOutline } from "react-icons/io5";
import { RiSurveyLine } from "react-icons/ri"; import { RiSurveyLine } from "react-icons/ri";
@@ -42,7 +43,6 @@ import { selectRecentItems, selectSelectedHeader } from "../../redux/application
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { signOutStart } from "../../redux/user/user.actions"; import { signOutStart } from "../../redux/user/user.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import { FiLogOut } from "react-icons/fi";
import { checkBeta, handleBeta, setBeta } from "../../utils/betaHandler"; import { checkBeta, handleBeta, setBeta } from "../../utils/betaHandler";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component"; import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
@@ -141,11 +141,13 @@ function Header({
accountingChildren.push( accountingChildren.push(
{ {
key: "bills", key: "bills",
id: "header-accounting-bills",
icon: <Icon component={FaFileInvoiceDollar} />, icon: <Icon component={FaFileInvoiceDollar} />,
label: <Link to="/manage/bills">{t("menus.header.bills")}</Link> label: <Link to="/manage/bills">{t("menus.header.bills")}</Link>
}, },
{ {
key: "enterbills", key: "enterbills",
id: "header-accounting-enterbills",
icon: <Icon component={GiPayMoney} />, icon: <Icon component={GiPayMoney} />,
label: t("menus.header.enterbills"), label: t("menus.header.enterbills"),
onClick: () => { onClick: () => {
@@ -165,6 +167,7 @@ function Header({
}, },
{ {
key: "inventory", key: "inventory",
id: "header-accounting-inventory",
icon: <Icon component={FaFileInvoiceDollar} />, icon: <Icon component={FaFileInvoiceDollar} />,
label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link> label: <Link to="/manage/inventory">{t("menus.header.inventory")}</Link>
} }
@@ -183,11 +186,13 @@ function Header({
}, },
{ {
key: "allpayments", key: "allpayments",
id: "header-accounting-allpayments",
icon: <BankFilled />, icon: <BankFilled />,
label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link> label: <Link to="/manage/payments">{t("menus.header.allpayments")}</Link>
}, },
{ {
key: "enterpayments", key: "enterpayments",
id: "header-accounting-enterpayments",
icon: <Icon component={FaCreditCard} />, icon: <Icon component={FaCreditCard} />,
label: t("menus.header.enterpayment"), label: t("menus.header.enterpayment"),
onClick: () => { onClick: () => {
@@ -203,6 +208,7 @@ function Header({
if (ImEXPay.treatment === "on") { if (ImEXPay.treatment === "on") {
accountingChildren.push({ accountingChildren.push({
key: "entercardpayments", key: "entercardpayments",
id: "header-accounting-entercardpayments",
icon: <Icon component={FaCreditCard} />, icon: <Icon component={FaCreditCard} />,
label: t("menus.header.entercardpayment"), label: t("menus.header.entercardpayment"),
onClick: () => { onClick: () => {
@@ -227,6 +233,7 @@ function Header({
}, },
{ {
key: "timetickets", key: "timetickets",
id: "header-accounting-timetickets",
icon: <FieldTimeOutlined />, icon: <FieldTimeOutlined />,
label: <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link> label: <Link to="/manage/timetickets">{t("menus.header.timetickets")}</Link>
} }
@@ -235,6 +242,7 @@ function Header({
if (bodyshop?.md_tasks_presets?.use_approvals) { if (bodyshop?.md_tasks_presets?.use_approvals) {
accountingChildren.push({ accountingChildren.push({
key: "ttapprovals", key: "ttapprovals",
id: "header-accounting-ttapprovals",
icon: <FieldTimeOutlined />, icon: <FieldTimeOutlined />,
label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link> label: <Link to="/manage/ttapprovals">{t("menus.header.ttapprovals")}</Link>
}); });
@@ -244,6 +252,7 @@ function Header({
key: "entertimetickets", key: "entertimetickets",
icon: <Icon component={GiPlayerTime} />, icon: <Icon component={GiPlayerTime} />,
label: t("menus.header.entertimeticket"), label: t("menus.header.entertimeticket"),
id: "header-accounting-entertimetickets",
onClick: () => { onClick: () => {
setTimeTicketContext({ setTimeTicketContext({
actions: {}, actions: {},
@@ -264,6 +273,7 @@ function Header({
const accountingExportChildren = [ const accountingExportChildren = [
{ {
key: "receivables", key: "receivables",
id: "header-accounting-receivables",
label: <Link to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link> label: <Link to="/manage/accounting/receivables">{t("menus.header.accounting-receivables")}</Link>
} }
]; ];
@@ -271,6 +281,7 @@ function Header({
if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) || DmsAp.treatment === "on") { if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber)) || DmsAp.treatment === "on") {
accountingExportChildren.push({ accountingExportChildren.push({
key: "payables", key: "payables",
id: "header-accounting-payables",
label: <Link to="/manage/accounting/payables">{t("menus.header.accounting-payables")}</Link> label: <Link to="/manage/accounting/payables">{t("menus.header.accounting-payables")}</Link>
}); });
} }
@@ -278,6 +289,7 @@ function Header({
if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber))) { if (!((bodyshop && bodyshop.cdk_dealerid) || (bodyshop && bodyshop.pbs_serialnumber))) {
accountingExportChildren.push({ accountingExportChildren.push({
key: "payments", key: "payments",
id: "header-accounting-payments",
label: <Link to="/manage/accounting/payments">{t("menus.header.accounting-payments")}</Link> label: <Link to="/manage/accounting/payments">{t("menus.header.accounting-payments")}</Link>
}); });
} }
@@ -288,6 +300,7 @@ function Header({
}, },
{ {
key: "exportlogs", key: "exportlogs",
id: "header-accounting-exportlogs",
label: <Link to="/manage/accounting/exportlogs">{t("menus.header.export-logs")}</Link> label: <Link to="/manage/accounting/exportlogs">{t("menus.header.export-logs")}</Link>
} }
); );
@@ -301,6 +314,7 @@ function Header({
) { ) {
accountingChildren.push({ accountingChildren.push({
key: "accountingexport", key: "accountingexport",
id: "header-accounting-export",
icon: <ExportOutlined />, icon: <ExportOutlined />,
label: t("menus.header.export"), label: t("menus.header.export"),
children: accountingExportChildren children: accountingExportChildren
@@ -311,10 +325,12 @@ function Header({
{ {
key: "home", key: "home",
icon: <HomeFilled />, icon: <HomeFilled />,
id: "header-home",
label: <Link to="/manage/">{t("menus.header.home")}</Link> label: <Link to="/manage/">{t("menus.header.home")}</Link>
}, },
{ {
key: "schedule", key: "schedule",
id: "header-schedule",
icon: <Icon component={FaCalendarAlt} />, icon: <Icon component={FaCalendarAlt} />,
label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link> label: <Link to="/manage/schedule">{t("menus.header.schedule")}</Link>
}, },
@@ -326,16 +342,19 @@ function Header({
children: [ children: [
{ {
key: "activejobs", key: "activejobs",
id: "header-active-jobs",
icon: <FileFilled />, icon: <FileFilled />,
label: <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link> label: <Link to="/manage/jobs">{t("menus.header.activejobs")}</Link>
}, },
{ {
key: "readyjobs", key: "readyjobs",
id: "header-ready-jobs",
icon: <CheckCircleOutlined />, icon: <CheckCircleOutlined />,
label: <Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link> label: <Link to="/manage/jobs/ready">{t("menus.header.readyjobs")}</Link>
}, },
{ {
key: "parts-queue", key: "parts-queue",
id: "header-parts-queue",
icon: <ToolFilled />, icon: <ToolFilled />,
label: <Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link> label: <Link to="/manage/partsqueue">{t("menus.header.parts-queue")}</Link>
}, },
@@ -347,6 +366,7 @@ function Header({
}, },
{ {
key: "newjob", key: "newjob",
id: "header-new-job",
icon: <FileAddOutlined />, icon: <FileAddOutlined />,
label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link> label: <Link to="/manage/jobs/new">{t("menus.header.newjob")}</Link>
}, },
@@ -355,6 +375,7 @@ function Header({
}, },
{ {
key: "alljobs", key: "alljobs",
id: "header-all-jobs",
icon: <UnorderedListOutlined />, icon: <UnorderedListOutlined />,
label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link> label: <Link to="/manage/jobs/all">{t("menus.header.alljobs")}</Link>
}, },
@@ -363,6 +384,7 @@ function Header({
}, },
{ {
key: "productionlist", key: "productionlist",
id: "header-production-list",
icon: <ScheduleOutlined />, icon: <ScheduleOutlined />,
label: <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link> label: <Link to="/manage/production/list">{t("menus.header.productionlist")}</Link>
}, },
@@ -374,6 +396,7 @@ function Header({
? [ ? [
{ {
key: "productionboard", key: "productionboard",
id: "header-production-board",
icon: <Icon component={BsKanban} />, icon: <Icon component={BsKanban} />,
label: <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link> label: <Link to="/manage/production/board">{t("menus.header.productionboard")}</Link>
} }
@@ -391,6 +414,7 @@ function Header({
}, },
{ {
key: "scoreboard", key: "scoreboard",
id: "header-scoreboard",
icon: <LineChartOutlined />, icon: <LineChartOutlined />,
label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link> label: <Link to="/manage/scoreboard">{t("menus.header.scoreboard")}</Link>
} }
@@ -401,15 +425,18 @@ function Header({
{ {
key: "customers", key: "customers",
icon: <UserOutlined />, icon: <UserOutlined />,
id: "header-customers",
label: t("menus.header.customers"), label: t("menus.header.customers"),
children: [ children: [
{ {
key: "owners", key: "owners",
id: "header-owners",
icon: <TeamOutlined />, icon: <TeamOutlined />,
label: <Link to="/manage/owners">{t("menus.header.owners")}</Link> label: <Link to="/manage/owners">{t("menus.header.owners")}</Link>
}, },
{ {
key: "vehicles", key: "vehicles",
id: "header-vehicles",
icon: <CarFilled />, icon: <CarFilled />,
label: <Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link> label: <Link to="/manage/vehicles">{t("menus.header.vehicles")}</Link>
} }
@@ -423,21 +450,25 @@ function Header({
? [ ? [
{ {
key: "ccs", key: "ccs",
id: "header-css",
icon: <CarFilled />, icon: <CarFilled />,
label: t("menus.header.courtesycars"), label: t("menus.header.courtesycars"),
children: [ children: [
{ {
key: "courtesycarsall", key: "courtesycarsall",
id: "header-courtesycars-all",
icon: <CarFilled />, icon: <CarFilled />,
label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link> label: <Link to="/manage/courtesycars">{t("menus.header.courtesycars-all")}</Link>
}, },
{ {
key: "contracts", key: "contracts",
id: "header-contracts",
icon: <FileFilled />, icon: <FileFilled />,
label: <Link to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link> label: <Link to="/manage/courtesycars/contracts">{t("menus.header.courtesycars-contracts")}</Link>
}, },
{ {
key: "newcontract", key: "newcontract",
id: "header-newcontract",
icon: <FileAddFilled />, icon: <FileAddFilled />,
label: <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link> label: <Link to="/manage/courtesycars/contracts/new">{t("menus.header.courtesycars-newcontract")}</Link>
} }
@@ -450,6 +481,7 @@ function Header({
? [ ? [
{ {
key: "accounting", key: "accounting",
id: "header-accounting",
icon: <DollarCircleFilled />, icon: <DollarCircleFilled />,
label: t("menus.header.accounting"), label: t("menus.header.accounting"),
children: accountingChildren children: accountingChildren
@@ -458,6 +490,7 @@ function Header({
: []), : []),
{ {
key: "phonebook", key: "phonebook",
id: "header-phonebook",
icon: <PhoneOutlined />, icon: <PhoneOutlined />,
label: <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link> label: <Link to="/manage/phonebook">{t("menus.header.phonebook")}</Link>
}, },
@@ -469,6 +502,7 @@ function Header({
? [ ? [
{ {
key: "temporarydocs", key: "temporarydocs",
id: "header-temporarydocs",
icon: <PaperClipOutlined />, icon: <PaperClipOutlined />,
label: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link> label: <Link to="/manage/temporarydocs">{t("menus.header.temporarydocs")}</Link>
} }
@@ -505,21 +539,25 @@ function Header({
}, },
{ {
key: "shopsubmenu", key: "shopsubmenu",
id: "header-shopsubmenu",
icon: <SettingOutlined />, icon: <SettingOutlined />,
label: t("menus.header.shop"), label: t("menus.header.shop"),
children: [ children: [
{ {
key: "shop", key: "shop",
id: "header-shop",
icon: <Icon component={GiSettingsKnobs} />, icon: <Icon component={GiSettingsKnobs} />,
label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link> label: <Link to="/manage/shop?tab=info">{t("menus.header.shop_config")}</Link>
}, },
{ {
key: "dashboard", key: "dashboard",
id: "header-dashboard",
icon: <DashboardFilled />, icon: <DashboardFilled />,
label: <Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link> label: <Link to="/manage/dashboard">{t("menus.header.dashboard")}</Link>
}, },
{ {
key: "reportcenter", key: "reportcenter",
id: "header-reportcenter",
icon: <BarChartOutlined />, icon: <BarChartOutlined />,
label: t("menus.header.reportcenter"), label: t("menus.header.reportcenter"),
onClick: () => { onClick: () => {
@@ -531,6 +569,7 @@ function Header({
}, },
{ {
key: "shop-vendors", key: "shop-vendors",
id: "header-shop-vendors",
icon: <Icon component={IoBusinessOutline} />, icon: <Icon component={IoBusinessOutline} />,
label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link> label: <Link to="/manage/shop/vendors">{t("menus.header.shop_vendors")}</Link>
}, },
@@ -542,6 +581,7 @@ function Header({
? [ ? [
{ {
key: "shop-csi", key: "shop-csi",
id: "header-shop-csi",
icon: <Icon component={RiSurveyLine} />, icon: <Icon component={RiSurveyLine} />,
label: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link> label: <Link to="/manage/shop/csi">{t("menus.header.shop_csi")}</Link>
} }
@@ -555,6 +595,7 @@ function Header({
children: [ children: [
{ {
key: "signout", key: "signout",
id: "header-signout",
icon: <Icon component={FiLogOut} />, icon: <Icon component={FiLogOut} />,
danger: true, danger: true,
label: t("user.actions.signout"), label: t("user.actions.signout"),
@@ -562,6 +603,7 @@ function Header({
}, },
{ {
key: "help", key: "help",
id: "header-help",
icon: <Icon component={QuestionCircleFilled} />, icon: <Icon component={QuestionCircleFilled} />,
label: t("menus.header.help"), label: t("menus.header.help"),
onClick: () => { onClick: () => {
@@ -576,14 +618,22 @@ function Header({
); );
} }
}, },
// { ...(InstanceRenderManager({
// key: 'rescue', imex: true,
// icon: <Icon component={CarFilled}/>, rome: false,
// label: t("menus.header.rescueme"), promanager: false
// onClick: () => { })
// window.open("https://imexrescue.com/", "_blank"); ? [
// } {
// }, key: "rescue",
icon: <Icon component={CarFilled} />,
label: t("menus.header.rescueme"),
onClick: () => {
window.open("https://imexrescue.com/", "_blank");
}
}
]
: []),
...(InstanceRenderManager({ ...(InstanceRenderManager({
imex: true, imex: true,
@@ -593,6 +643,7 @@ function Header({
? [ ? [
{ {
key: "shiftclock", key: "shiftclock",
id: "header-shiftclock",
icon: <Icon component={GiPlayerTime} />, icon: <Icon component={GiPlayerTime} />,
label: <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link> label: <Link to="/manage/shiftclock">{t("menus.header.shiftclock")}</Link>
} }
@@ -600,6 +651,7 @@ function Header({
: []), : []),
{ {
key: "profile", key: "profile",
id: "header-profile",
icon: <UserOutlined />, icon: <UserOutlined />,
label: <Link to="/manage/profile">{t("menus.currentuser.profile")}</Link> label: <Link to="/manage/profile">{t("menus.currentuser.profile")}</Link>
} }
@@ -635,6 +687,7 @@ function Header({
{ {
key: "recent", key: "recent",
icon: <ClockCircleFilled />, icon: <ClockCircleFilled />,
id: "header-recent",
children: recentItems.map((i, idx) => ({ children: recentItems.map((i, idx) => ({
key: idx, key: idx,
label: <Link to={i.url}>{i.label}</Link> label: <Link to={i.url}>{i.label}</Link>
@@ -648,6 +701,7 @@ function Header({
imex: () => { imex: () => {
menuItems.push({ menuItems.push({
key: "beta-switch", key: "beta-switch",
id: "header-beta-switch",
style: { marginLeft: "auto" }, style: { marginLeft: "auto" },
label: ( label: (
<Tooltip <Tooltip

View File

@@ -1,6 +1,5 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Card, Form, Input, notification, Switch } from "antd"; import { Button, Card, Form, Input, notification, Switch } from "antd";
import dayjs from "../../../../utils/day";
import queryString from "query-string"; import queryString from "query-string";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -14,6 +13,7 @@ import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
import { insertAuditTrail } from "../../../../redux/application/application.actions"; import { insertAuditTrail } from "../../../../redux/application/application.actions";
import { selectBodyshop, selectCurrentUser } from "../../../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../../../redux/user/user.selectors";
import AuditTrailMapping from "../../../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
import dayjs from "../../../../utils/day";
import ConfigFormComponents from "../../../config-form-components/config-form-components.component"; import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component"; import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
@@ -275,7 +275,19 @@ export function JobChecklistForm({ insertAuditTrail, formItems, bodyshop, curren
> >
<DateTimePicker disabled={readOnly} /> <DateTimePicker disabled={readOnly} />
</Form.Item> </Form.Item>
<Form.Item name="actual_delivery" label={t("jobs.fields.actual_delivery")} disabled={readOnly}> <Form.Item
name="actual_delivery"
label={t("jobs.fields.actual_delivery")}
rules={[
{
required: bodyshop.deliverchecklist.actual_delivery
? bodyshop.deliverchecklist.actual_delivery
: false
//message: t("general.validation.required"),
}
]}
disabled={readOnly}
>
<DateTimePicker disabled={readOnly} /> <DateTimePicker disabled={readOnly} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item

View File

@@ -1,6 +1,6 @@
import React, { useCallback, useState } from "react";
import { LockOutlined } from "@ant-design/icons"; import { LockOutlined } from "@ant-design/icons";
import { Badge, Card, Col, Collapse, Form, Input, Row, Space, Tooltip } from "antd"; import { Badge, Card, Col, Collapse, Form, Input, Row, Space, Tooltip } from "antd";
import React, { useCallback, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -12,9 +12,9 @@ import JobCloseRoGuardBills from "./job-close-ro-guard.bills";
import JobCloseRoGuardPpd from "./job-close-ro-guard.ppd"; import JobCloseRoGuardPpd from "./job-close-ro-guard.ppd";
import JobCloseRoGuardProfit from "./job-close-ro-guard.profit"; import JobCloseRoGuardProfit from "./job-close-ro-guard.profit";
import "./job-close-ro-guard.styles.scss"; import "./job-close-ro-guard.styles.scss";
import JobCloseRoGuardSublet from "./job-close-ro-guard.sublet"; // import JobCloseRoGuardSublet from "./job-close-ro-guard.sublet";
import JobCloseRoGuardTtLifecycle from "./job-close-ro-guard.tt-lifecycle";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import JobCloseRoGuardTtLifecycle from "./job-close-ro-guard.tt-lifecycle";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser

View File

@@ -1,14 +1,21 @@
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import CardTemplate from "./job-detail-cards.template.component";
import Car from "../job-damage-visual/job-damage-visual.component"; import Car from "../job-damage-visual/job-damage-visual.component";
import CardTemplate from "./job-detail-cards.template.component";
export default function JobDetailCardsDamageComponent({ loading, data }) { export default function JobDetailCardsDamageComponent({ loading, data }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { area_of_damage } = data; const { area_of_damage } = data;
return ( return (
<CardTemplate loading={loading} title={t("jobs.labels.cards.damage")}> <CardTemplate loading={loading} title={t("jobs.labels.cards.damage")}>
{area_of_damage ? <Car dmg1={area_of_damage.impact1} dmg2={area_of_damage.impact2} /> : t("jobs.errors.nodamage")} {area_of_damage ? (
<Car
dmg1={area_of_damage.impact1 && area_of_damage.impact1.padStart(2, "0")}
dmg2={area_of_damage.impact2 && area_of_damage.impact2.padStart(2, "0")}
/>
) : (
t("jobs.errors.nodamage")
)}
</CardTemplate> </CardTemplate>
); );
} }

View File

@@ -26,6 +26,16 @@ export function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
const { t } = useTranslation(); const { t } = useTranslation();
const { joblines_status } = data; const { joblines_status } = data;
const filteredJobLines = data.joblines.filter(
(j) =>
j.part_type !== null &&
j.part_type !== "PAE" &&
j.part_type !== "PAS" &&
j.part_type !== "PASL" &&
j.part_qty !== 0 &&
j.act_price !== 0
);
//TODO: Correct jobline_statuses view by including the part_qty !== 0 and act_price !== 0
const columns = [ const columns = [
{ {
title: t("joblines.fields.line_desc"), title: t("joblines.fields.line_desc"),
@@ -95,7 +105,7 @@ export function JobDetailCardsPartsComponent({ loading, data, jobRO }) {
<div> <div>
<CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}> <CardTemplate loading={loading} title={t("jobs.labels.cards.parts")}>
<PartsStatusPie joblines_status={joblines_status} /> <PartsStatusPie joblines_status={joblines_status} />
<Table key="id" columns={columns} dataSource={data ? data.joblines : []} /> <Table key="id" columns={columns} dataSource={filteredJobLines ? filteredJobLines : []} />
</CardTemplate> </CardTemplate>
</div> </div>
); );

View File

@@ -12,6 +12,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { QUERY_JOBLINE_TASKS_PAGINATED } from "../../graphql/tasks.queries.js"; import { QUERY_JOBLINE_TASKS_PAGINATED } from "../../graphql/tasks.queries.js";
import TaskListContainer from "../task-list/task-list.container.jsx"; import TaskListContainer from "../task-list/task-list.container.jsx";
import FeatureWrapper from "../feature-wrapper/feature-wrapper.component.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -98,55 +99,59 @@ export function JobLinesExpander({ jobline, jobid, bodyshop }) {
</Row> </Row>
) )
})) }))
: {
key: "dispatch-lines",
children: t("parts_orders.labels.notyetordered")
}
}
/>
</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", key: "dispatch-lines",
children: t("bills.labels.nobilllines") children: t("parts_dispatch.labels.notyetdispatched")
} }
] ]
} }
/> />
</Col> </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}> <Col md={24} lg={24}>
<TaskListContainer <TaskListContainer
parentJobId={jobid} parentJobId={jobid}

View File

@@ -43,6 +43,7 @@ import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.con
import JobLinesExpander from "./job-lines-expander.component"; import JobLinesExpander from "./job-lines-expander.component";
import JobLinesPartPriceChange from "./job-lines-part-price-change.component"; import JobLinesPartPriceChange from "./job-lines-part-price-change.component";
import { FaTasks } from "react-icons/fa"; import { FaTasks } from "react-icons/fa";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -552,7 +553,7 @@ export function JobLinesComponent({
> >
{t("joblines.actions.new")} {t("joblines.actions.new")}
</Button> </Button>
{bodyshop.region_config.toLowerCase().startsWith("us") && <JobSendPartPriceChangeComponent job={job} />} {InstanceRenderManager({ rome: <JobSendPartPriceChangeComponent job={job} /> })}
<JobCreateIOU job={job} selectedJobLines={selectedLines} /> <JobCreateIOU job={job} selectedJobLines={selectedLines} />
<Input.Search <Input.Search
placeholder={t("general.labels.search")} placeholder={t("general.labels.search")}

View File

@@ -3,6 +3,7 @@ import { Button, Space, notification } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { DELETE_DELIVERY_CHECKLIST, DELETE_INTAKE_CHECKLIST } from "../../graphql/jobs.queries"; import { DELETE_DELIVERY_CHECKLIST, DELETE_INTAKE_CHECKLIST } from "../../graphql/jobs.queries";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
export default function JobAdminDeleteIntake({ job }) { export default function JobAdminDeleteIntake({ job }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -47,16 +48,22 @@ export default function JobAdminDeleteIntake({ job }) {
setLoading(false); setLoading(false);
}; };
return ( const InstanceRender = InstanceRenderManager({
imex: true,
rome: "USE_IMEX",
promanager: false
});
return InstanceRender ? (
<> <>
<Space wrap> <Space wrap>
<Button loading={loading} onClick={handleDelete} disabled={!job.intakechecklist}> <Button loading={loading} onClick={handleDelete} disabled={!job.intakechecklist}>
{t("jobs.labels.deleteintake")} {t("jobs.labels.deleteintake")}
</Button> </Button>
<Button loading={loading} onClick={handleDeleteDelivery} disabled={!job.deliverychecklist}> <Button loading={loading} onClick={handleDeleteDelivery} disabled={!job.deliverchecklist}>
{t("jobs.labels.deletedelivery")} {t("jobs.labels.deletedelivery")}
</Button> </Button>
</Space> </Space>
</> </>
); ) : null;
} }

View File

@@ -189,7 +189,10 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
</Col> </Col>
<Col {...lossColDamage}> <Col {...lossColDamage}>
{job.area_of_damage ? ( {job.area_of_damage ? (
<Car dmg1={job.area_of_damage.impact1} dmg2={job.area_of_damage.impact2} /> <Car
dmg1={job.area_of_damage.impact1 && job.area_of_damage.impact1.padStart(2, "0")}
dmg2={job.area_of_damage.impact2 && job.area_of_damage.impact2.padStart(2, "0")}
/>
) : ( ) : (
t("jobs.errors.nodamage") t("jobs.errors.nodamage")
)} )}

View File

@@ -1,6 +1,9 @@
import { DownCircleFilled } from "@ant-design/icons"; import { DownCircleFilled } from "@ant-design/icons";
import { useApolloClient, useMutation } from "@apollo/client"; 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 { 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 React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -8,27 +11,24 @@ import { Link, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { auth, logImEXEvent } from "../../firebase/firebase.utils"; import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { CANCEL_APPOINTMENTS_BY_JOB_ID, INSERT_MANUAL_APPT } from "../../graphql/appointments.queries"; 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 { DELETE_JOB, UPDATE_JOB, VOID_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setEmailOptions } from "../../redux/email/email.actions";
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; 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 RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util"; import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.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"; import JobsDetailHeaderActionsToggleProduction from "./jobs-detail-header-actions.toggle-production";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -83,6 +83,13 @@ const mapDispatchToProps = (dispatch) => ({
modal: "timeTicketTask" modal: "timeTicketTask"
}) })
), ),
setTaskUpsertContext: (context) =>
dispatch(
setModalContext({
context: context,
modal: "taskUpsert"
})
),
setEmailOptions: (e) => dispatch(setEmailOptions(e)), setEmailOptions: (e) => dispatch(setEmailOptions(e)),
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)), openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text)) setMessage: (text) => dispatch(setMessage(text))
@@ -104,7 +111,8 @@ export function JobsDetailHeaderActions({
setEmailOptions, setEmailOptions,
openChatByPhone, openChatByPhone,
setMessage, setMessage,
setTimeTicketTaskContext setTimeTicketTaskContext,
setTaskUpsertContext
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const client = useApolloClient(); const client = useApolloClient();
@@ -633,6 +641,7 @@ export function JobsDetailHeaderActions({
const menuItems = [ const menuItems = [
{ {
key: "schedule", key: "schedule",
id: "job-actions-schedule",
disabled: !jobInPreProduction || !job.converted || jobRO, disabled: !jobInPreProduction || !job.converted || jobRO,
label: t("jobs.actions.schedule"), label: t("jobs.actions.schedule"),
onClick: () => { onClick: () => {
@@ -649,6 +658,7 @@ export function JobsDetailHeaderActions({
}, },
{ {
key: "cancelallappointments", key: "cancelallappointments",
id: "job-actions-cancelallappointments",
onClick: () => { onClick: () => {
if (job.status !== bodyshop.md_ro_statuses.default_scheduled) { if (job.status !== bodyshop.md_ro_statuses.default_scheduled) {
return; return;
@@ -662,6 +672,7 @@ export function JobsDetailHeaderActions({
imex: [ imex: [
{ {
key: "intake", key: "intake",
id: "job-actions-intake",
disabled: !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO, disabled: !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO,
label: label:
!!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO ? ( !!job.intakechecklist || !jobInPreProduction || !job.converted || jobRO ? (
@@ -672,6 +683,7 @@ export function JobsDetailHeaderActions({
}, },
{ {
key: "deliver", key: "deliver",
id: "job-actions-deliver",
disabled: !jobInProduction || jobRO, disabled: !jobInProduction || jobRO,
label: !jobInProduction ? ( label: !jobInProduction ? (
t("jobs.actions.deliver") t("jobs.actions.deliver")
@@ -681,6 +693,7 @@ export function JobsDetailHeaderActions({
}, },
{ {
key: "checklist", key: "checklist",
id: "job-actions-checklist",
disabled: !job.converted, disabled: !job.converted,
label: <Link to={`/manage/jobs/${job.id}/checklist`}>{t("jobs.actions.viewchecklist")}</Link> label: <Link to={`/manage/jobs/${job.id}/checklist`}>{t("jobs.actions.viewchecklist")}</Link>
} }
@@ -689,6 +702,7 @@ export function JobsDetailHeaderActions({
promanager: [ promanager: [
{ {
key: "toggleproduction", key: "toggleproduction",
id: "job-actions-toggleproduction",
disabled: !job.converted || jobRO, disabled: !job.converted || jobRO,
label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch} /> label: <JobsDetailHeaderActionsToggleProduction job={job} refetch={refetch} />
} }
@@ -702,6 +716,7 @@ export function JobsDetailHeaderActions({
? [ ? [
{ {
key: "entertimetickets", key: "entertimetickets",
id: "job-actions-entertimetickets",
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced), disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
label: t("timetickets.actions.enter"), label: t("timetickets.actions.enter"),
onClick: () => { onClick: () => {
@@ -725,6 +740,7 @@ export function JobsDetailHeaderActions({
if (bodyshop.md_tasks_presets.enable_tasks) { if (bodyshop.md_tasks_presets.enable_tasks) {
menuItems.push({ menuItems.push({
key: "claimtimetickettasks", key: "claimtimetickettasks",
id: "job-actions-claimtimetickettasks",
disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced), disabled: !job.converted || (!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced),
onClick: () => { onClick: () => {
setTimeTicketTaskContext({ setTimeTicketTaskContext({
@@ -738,6 +754,7 @@ export function JobsDetailHeaderActions({
menuItems.push({ menuItems.push({
key: "enterpayments", key: "enterpayments",
id: "job-actions-enterpayments",
disabled: !job.converted, disabled: !job.converted,
label: t("menus.header.enterpayment"), label: t("menus.header.enterpayment"),
onClick: () => { onClick: () => {
@@ -753,22 +770,24 @@ export function JobsDetailHeaderActions({
if (ImEXPay.treatment === "on") { if (ImEXPay.treatment === "on") {
menuItems.push({ menuItems.push({
key: "entercardpayments", key: "entercardpayments",
id: "job-actions-entercardpayments",
disabled: !job.converted, disabled: !job.converted,
label: t("menus.header.entercardpayment"), label: t("menus.header.entercardpayment"),
onClick: () => { onClick: () => {
logImEXEvent("job_header_enter_card_payment"); logImEXEvent("job_header_enter_card_payment");
setCardPaymentContext({ setCardPaymentContext({
actions: {}, actions: { refetch },
context: { jobid: job.id } context: { jobid: job.id }
}); });
} }
}); });
} }
if (HasFeatureAccess({ featureName: "courtesycars" })) { if (HasFeatureAccess({ featureName: "courtesycars", bodyshop })) {
menuItems.push({ menuItems.push({
key: "cccontract", key: "cccontract",
id: "job-actions-cccontract",
disabled: jobRO || !job.converted, disabled: jobRO || !job.converted,
label: ( label: (
<Link state={{ jobId: job.id }} to="/manage/courtesycars/contracts/new"> <Link state={{ jobId: job.id }} to="/manage/courtesycars/contracts/new">
@@ -778,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( menuItems.push(
job.inproduction job.inproduction
? { ? {
key: "removefromproduction", key: "removefromproduction",
id: "job-actions-removefromproduction",
disabled: !job.converted, disabled: !job.converted,
label: t("jobs.actions.removefromproduction"), label: t("jobs.actions.removefromproduction"),
onClick: () => AddToProduction(client, job.id, refetch, true) onClick: () => AddToProduction(client, job.id, refetch, true)
} }
: { : {
key: "addtoproduction", key: "addtoproduction",
id: "job-actions-addtoproduction",
disabled: !job.converted, disabled: !job.converted,
label: t("jobs.actions.addtoproduction"), label: t("jobs.actions.addtoproduction"),
onClick: () => AddToProduction(client, job.id, refetch) onClick: () => AddToProduction(client, job.id, refetch)
@@ -797,12 +829,14 @@ export function JobsDetailHeaderActions({
menuItems.push( menuItems.push(
{ {
key: "togglesuspend", key: "togglesuspend",
id: "job-actions-togglesuspend",
onClick: handleSuspend, onClick: handleSuspend,
label: job.suspended ? t("production.actions.unsuspend") : t("production.actions.suspend") label: job.suspended ? t("production.actions.unsuspend") : t("production.actions.suspend")
}, },
{ {
key: "toggleAlert", key: "toggleAlert",
onClick: handleAlertToggle, onClick: handleAlertToggle,
id: "job-actions-togglealert",
label: label:
job.production_vars && job.production_vars.alert job.production_vars && job.production_vars.alert
? t("production.labels.alertoff") ? t("production.labels.alertoff")
@@ -814,6 +848,7 @@ export function JobsDetailHeaderActions({
children: [ children: [
{ {
key: "duplicate", key: "duplicate",
id: "job-actions-duplicate",
label: ( label: (
<Popconfirm <Popconfirm
title={t("jobs.labels.duplicateconfirm")} title={t("jobs.labels.duplicateconfirm")}
@@ -829,6 +864,7 @@ export function JobsDetailHeaderActions({
}, },
{ {
key: "duplicatenolines", key: "duplicatenolines",
id: "job-actions-duplicatenolines",
label: ( label: (
<Popconfirm <Popconfirm
title={t("jobs.labels.duplicateconfirm")} title={t("jobs.labels.duplicateconfirm")}
@@ -852,6 +888,7 @@ export function JobsDetailHeaderActions({
? [ ? [
{ {
key: "postbills", key: "postbills",
id: "job-actions-postbills",
disabled: !job.converted, disabled: !job.converted,
label: t("jobs.actions.postbills"), label: t("jobs.actions.postbills"),
onClick: () => { onClick: () => {
@@ -870,6 +907,7 @@ export function JobsDetailHeaderActions({
{ {
key: "addtopartsqueue", key: "addtopartsqueue",
id: "job-actions-addtopartsqueue",
disabled: !job.converted || !jobInProduction || jobRO, disabled: !job.converted || !jobInProduction || jobRO,
label: t("jobs.actions.addtopartsqueue"), label: t("jobs.actions.addtopartsqueue"),
onClick: async () => { onClick: async () => {
@@ -895,6 +933,7 @@ export function JobsDetailHeaderActions({
}, },
{ {
key: "closejob", key: "closejob",
id: "job-actions-closejob",
disabled: !jobInPostProduction, disabled: !jobInPostProduction,
label: !jobInPostProduction ? ( label: !jobInPostProduction ? (
t("menus.jobsactions.closejob") t("menus.jobsactions.closejob")
@@ -910,6 +949,7 @@ export function JobsDetailHeaderActions({
}, },
{ {
key: "admin", key: "admin",
id: "job-actions-admin",
label: ( label: (
<Link <Link
to={{ to={{
@@ -931,6 +971,7 @@ export function JobsDetailHeaderActions({
) { ) {
menuItems.push({ menuItems.push({
key: "exportcustdata", key: "exportcustdata",
id: "job-actions-exportcustdata",
disabled: !job.converted, disabled: !job.converted,
label: t("jobs.actions.exportcustdata"), label: t("jobs.actions.exportcustdata"),
onClick: handleExportCustData onClick: handleExportCustData
@@ -941,18 +982,21 @@ export function JobsDetailHeaderActions({
const children = [ const children = [
{ {
key: "email", key: "email",
id: "job-actions-email",
disabled: !!!job.ownr_ea, disabled: !!!job.ownr_ea,
label: t("general.labels.email"), label: t("general.labels.email"),
onClick: handleCreateCsi onClick: handleCreateCsi
}, },
{ {
key: "text", key: "text",
id: "job-actions-text",
disabled: !!!job.ownr_ph1, disabled: !!!job.ownr_ph1,
label: t("general.labels.text"), label: t("general.labels.text"),
onClick: handleCreateCsi onClick: handleCreateCsi
}, },
{ {
key: "generate", key: "generate",
id: "job-actions-generate",
disabled: job.csiinvites && job.csiinvites.length > 0, disabled: job.csiinvites && job.csiinvites.length > 0,
label: t("jobs.actions.generatecsi"), label: t("jobs.actions.generatecsi"),
onClick: handleCreateCsi onClick: handleCreateCsi
@@ -986,6 +1030,7 @@ export function JobsDetailHeaderActions({
} }
menuItems.push({ menuItems.push({
key: "sendcsi", key: "sendcsi",
id: "job-actions-sendcsi",
label: t("jobs.actions.sendcsi"), label: t("jobs.actions.sendcsi"),
disabled: !job.converted, disabled: !job.converted,
children children
@@ -994,6 +1039,7 @@ export function JobsDetailHeaderActions({
menuItems.push({ menuItems.push({
key: "jobcosting", key: "jobcosting",
id: "job-actions-jobcosting",
disabled: !job.converted, disabled: !job.converted,
label: t("jobs.labels.jobcosting"), label: t("jobs.labels.jobcosting"),
onClick: () => { onClick: () => {
@@ -1011,6 +1057,7 @@ export function JobsDetailHeaderActions({
if (job && !job.converted) { if (job && !job.converted) {
menuItems.push({ menuItems.push({
key: "deletejob", key: "deletejob",
id: "job-actions-deletejob",
label: ( label: (
<Popconfirm <Popconfirm
title={t("jobs.labels.deleteconfirm")} title={t("jobs.labels.deleteconfirm")}
@@ -1027,6 +1074,7 @@ export function JobsDetailHeaderActions({
menuItems.push({ menuItems.push({
key: "manualevent", key: "manualevent",
id: "job-actions-manualevent",
onClick: (e) => { onClick: (e) => {
setVisibility(true); setVisibility(true);
}, },
@@ -1036,6 +1084,7 @@ export function JobsDetailHeaderActions({
if (!jobRO && job.converted) { if (!jobRO && job.converted) {
menuItems.push({ menuItems.push({
key: "voidjob", key: "voidjob",
id: "job-actions-voidjob",
label: ( label: (
<RbacWrapper action="jobs:void" noauth> <RbacWrapper action="jobs:void" noauth>
<Popconfirm <Popconfirm

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 OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { setJoyRideSteps } from "../../redux/application/application.actions"; import { setJoyRideSteps } from "../../redux/application/application.actions";
import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component"; import { OwnerNameDisplayFunction } from "./../owner-name-display/owner-name-display.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
}); });
@@ -315,34 +313,6 @@ export function JobsList({ bodyshop, setJoyRideSteps }) {
title={t("titles.bc.jobs-active")} title={t("titles.bc.jobs-active")}
extra={ extra={
<Space wrap> <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()}> <Button onClick={() => refetch()}>
<SyncOutlined /> <SyncOutlined />
</Button> </Button>

View File

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

View File

@@ -1,23 +1,24 @@
import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons"; import { DeleteFilled, EyeFilled, SyncOutlined } from "@ant-design/icons";
import { useMutation } from "@apollo/client"; import { useLazyQuery, useMutation } from "@apollo/client";
import { Button, Card, Checkbox, Drawer, Grid, Input, Popconfirm, Space, Table } from "antd"; import { Button, Card, Checkbox, Drawer, Grid, Input, Popconfirm, Space, Table } from "antd";
import { PageHeader } from "@ant-design/pro-layout"; import { PageHeader } from "@ant-design/pro-layout";
import queryString from "query-string"; import queryString from "query-string";
import React, { useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { QUERY_BILL_BY_PK } from "../../graphql/bills.queries";
import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries"; import { DELETE_PARTS_ORDER } from "../../graphql/parts-orders.queries";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort } from "../../utils/sorters";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component"; import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component"; import PartsOrderCmReceived from "../parts-order-cm-received/parts-order-cm-received.component";
@@ -81,19 +82,46 @@ export function PartsOrderListTableComponent({
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {} sortedInfo: {}
}); });
const [returnfrombill, setReturnFromBill] = useState();
const [billData, setBillData] = useState();
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const selectedpartsorder = search.partsorderid; const selectedpartsorder = search.partsorderid;
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const [billQuery] = useLazyQuery(QUERY_BILL_BY_PK);
const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER); const [deletePartsOrder] = useMutation(DELETE_PARTS_ORDER);
const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : []; const parts_orders = billsQuery.data ? billsQuery.data.parts_orders : [];
const { refetch } = billsQuery; const { refetch } = billsQuery;
useEffect(() => {
if (returnfrombill === null) {
setBillData(null);
} else {
const fetchData = async () => {
const result = await billQuery({
variables: { billid: returnfrombill }
});
setBillData(result.data);
};
fetchData();
}
}, [returnfrombill, billQuery]);
const recordActions = (record, showView = false) => ( const recordActions = (record, showView = false) => (
<Space direction="horizontal" wrap> <Space direction="horizontal" wrap>
{showView && ( {showView && (
<Button onClick={() => handleOnRowClick(record)}> <Button
onClick={() => {
if (record.returnfrombill) {
setReturnFromBill(record.returnfrombill);
} else {
setReturnFromBill(null);
}
handleOnRowClick(record);
}}
>
<EyeFilled /> <EyeFilled />
</Button> </Button>
)} )}
@@ -175,7 +203,7 @@ export function PartsOrderListTableComponent({
is_credit_memo: record.return, is_credit_memo: record.return,
billlines: record.parts_order_lines.map((pol) => { billlines: record.parts_order_lines.map((pol) => {
return { return {
joblineid: pol.job_line_id, joblineid: pol.job_line_id || "noline",
line_desc: pol.line_desc, line_desc: pol.line_desc,
quantity: pol.quantity, quantity: pol.quantity,
@@ -395,7 +423,14 @@ export function PartsOrderListTableComponent({
return ( return (
<div> <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 <Table
scroll={{ scroll={{
x: true //y: "50rem" x: true //y: "50rem"

View File

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

View File

@@ -63,7 +63,7 @@ export default function ShopEmployeesListComponent({ loading, employees }) {
value: false value: false
} }
], ],
onFilter: (value, record) => value === record.flate_rate, onFilter: (value, record) => value === record.flat_rate,
render: (text, record) => render: (text, record) =>
record.flat_rate ? t("employees.labels.flat_rate") : t("employees.labels.straight_time") record.flat_rate ? t("employees.labels.flat_rate") : t("employees.labels.straight_time")
}, },

View File

@@ -1,25 +1,25 @@
import { useSplitTreatments } from '@splitsoftware/splitio-react'; import { useSplitTreatments } from "@splitsoftware/splitio-react";
import { Button, Card, Tabs } from 'antd'; import { Button, Card, Tabs } from "antd";
import React from 'react'; import React from "react";
import { useTranslation } from 'react-i18next'; import { useTranslation } from "react-i18next";
import { connect } from 'react-redux'; import { connect } from "react-redux";
import { createStructuredSelector } from 'reselect'; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from '../../redux/user/user.selectors'; import { selectBodyshop } from "../../redux/user/user.selectors";
import ShopInfoGeneral from './shop-info.general.component'; import ShopInfoGeneral from "./shop-info.general.component";
import ShopInfoIntakeChecklistComponent from './shop-info.intake.component'; import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component";
import ShopInfoLaborRates from './shop-info.laborrates.component'; import ShopInfoLaborRates from "./shop-info.laborrates.component";
import ShopInfoOrderStatusComponent from './shop-info.orderstatus.component'; import ShopInfoOrderStatusComponent from "./shop-info.orderstatus.component";
import ShopInfoPartsScan from './shop-info.parts-scan'; import ShopInfoPartsScan from "./shop-info.parts-scan";
import ShopInfoRbacComponent from './shop-info.rbac.component'; import ShopInfoRbacComponent from "./shop-info.rbac.component";
import ShopInfoResponsibilityCenterComponent from './shop-info.responsibilitycenters.component'; import ShopInfoResponsibilityCenterComponent from "./shop-info.responsibilitycenters.component";
import ShopInfoROStatusComponent from './shop-info.rostatus.component'; import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
import ShopInfoSchedulingComponent from './shop-info.scheduling.component'; import ShopInfoSchedulingComponent from "./shop-info.scheduling.component";
import ShopInfoSpeedPrint from './shop-info.speedprint.component'; import ShopInfoSpeedPrint from "./shop-info.speedprint.component";
import { useLocation, useNavigate } from 'react-router-dom'; import { useLocation, useNavigate } from "react-router-dom";
import ShopInfoTaskPresets from './shop-info.task-presets.component'; import ShopInfoTaskPresets from "./shop-info.task-presets.component";
import queryString from 'query-string'; import queryString from "query-string";
import InstanceRenderManager from '../../utils/instanceRenderMgr'; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import ShopInfoRoGuard from './shop-info.roguard.component'; import ShopInfoRoGuard from "./shop-info.roguard.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -47,44 +47,52 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
{ {
key: "general", key: "general",
label: t("bodyshop.labels.shopinfo"), label: t("bodyshop.labels.shopinfo"),
children: <ShopInfoGeneral form={form} /> children: <ShopInfoGeneral form={form} />,
id: "tab-shop-general"
}, },
{ {
key: "speedprint", key: "speedprint",
label: t("bodyshop.labels.speedprint"), label: t("bodyshop.labels.speedprint"),
children: <ShopInfoSpeedPrint form={form} /> children: <ShopInfoSpeedPrint form={form} />,
id: "tab-shop-speedprint"
}, },
{ {
key: "rbac", key: "rbac",
label: t("bodyshop.labels.rbac"), label: t("bodyshop.labels.rbac"),
children: <ShopInfoRbacComponent form={form} /> children: <ShopInfoRbacComponent form={form} />,
id: "tab-shop-rbac"
}, },
{ {
key: "roStatus", key: "roStatus",
label: t("bodyshop.labels.jobstatuses"), label: t("bodyshop.labels.jobstatuses"),
children: <ShopInfoROStatusComponent form={form} /> children: <ShopInfoROStatusComponent form={form} />,
id: "tab-shop-rostatus"
}, },
{ {
key: "scheduling", key: "scheduling",
label: t("bodyshop.labels.scheduling"), label: t("bodyshop.labels.scheduling"),
children: <ShopInfoSchedulingComponent form={form} /> children: <ShopInfoSchedulingComponent form={form} />,
id: "tab-shop-scheduling"
}, },
{ {
key: "orderStatus", key: "orderStatus",
label: t("bodyshop.labels.orderstatuses"), label: t("bodyshop.labels.orderstatuses"),
children: <ShopInfoOrderStatusComponent form={form} /> children: <ShopInfoOrderStatusComponent form={form} />,
id: "tab-shop-orderstatus"
}, },
{ {
key: "responsibilityCenters", key: "responsibilityCenters",
label: t("bodyshop.labels.responsibilitycenters.title"), label: t("bodyshop.labels.responsibilitycenters.title"),
children: <ShopInfoResponsibilityCenterComponent form={form} /> children: <ShopInfoResponsibilityCenterComponent form={form} />,
id: "tab-shop-responsibilitycenters"
}, },
...InstanceRenderManager({ ...InstanceRenderManager({
imex: [ imex: [
{ {
key: "checklists", key: "checklists",
label: t("bodyshop.labels.checklists"), label: t("bodyshop.labels.checklists"),
children: <ShopInfoIntakeChecklistComponent form={form} /> children: <ShopInfoIntakeChecklistComponent form={form} />,
id: "tab-shop-checklists"
} }
], ],
rome: "USE_IMEX", rome: "USE_IMEX",
@@ -93,14 +101,16 @@ export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
{ {
key: "laborrates", key: "laborrates",
label: t("bodyshop.labels.laborrates"), label: t("bodyshop.labels.laborrates"),
children: <ShopInfoLaborRates form={form} /> children: <ShopInfoLaborRates form={form} />,
id: "tab-shop-laborrates"
}, },
...(CriticalPartsScanning.treatment === "on" ...(CriticalPartsScanning.treatment === "on"
? [ ? [
{ {
key: "partsscan", key: "partsscan",
label: t("bodyshop.labels.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", key: "task-presets",
label: t("bodyshop.labels.task-presets"), label: t("bodyshop.labels.task-presets"),
children: <ShopInfoTaskPresets form={form} /> children: <ShopInfoTaskPresets form={form} />,
id: "tab-shop-task-presets"
} }
] ]
: []), : []),
...InstanceRenderManager({ ...InstanceRenderManager({
imex: [ imex: [
{ {
key: 'roguard', key: "roguard",
label: t('bodyshop.labels.roguard.title'), label: t("bodyshop.labels.roguard.title"),
children: <ShopInfoRoGuard form={form} />, children: <ShopInfoRoGuard form={form} />,
}, id: "tab-shop-roguard"
], }
rome: 'USE_IMEX', ],
promanager: [], rome: "USE_IMEX",
}), promanager: []
})
]; ];
return ( return (
<Card <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 FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
import FeatureWrapper, { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
// TODO: Client Update, this might break // TODO: Client Update, this might break
const timeZonesList = Intl.supportedValuesOf("timeZone"); const timeZonesList = Intl.supportedValuesOf("timeZone");
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -144,285 +145,289 @@ export function ShopInfoGeneral({ form, bodyshop }) {
<InputNumber min={0} /> <InputNumber min={0} />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")} id="accountingsetup"> <FeatureWrapper featureName="export" noauth={() => null}>
<Form.Item label={t("bodyshop.labels.qbo")} valuePropName="checked" name={["accountingconfig", "qbo"]}> <LayoutFormRow header={t("bodyshop.labels.accountingsetup")} id="accountingsetup">
<Switch /> <Form.Item label={t("bodyshop.labels.qbo")} valuePropName="checked" name={["accountingconfig", "qbo"]}>
</Form.Item> <Switch />
{InstanceRenderManager({ </Form.Item>
imex: ( {InstanceRenderManager({
<Form.Item shouldUpdate noStyle> 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 <Form.Item
label={t("bodyshop.labels.qbo_usa")} label={t("bodyshop.labels.2tiersetup")}
shouldUpdate shouldUpdate
valuePropName="checked" rules={[
name={["accountingconfig", "qbo_usa"]} {
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> }}
) </Form.Item>
})} <Form.Item
<Form.Item label={t("bodyshop.labels.qbo_departmentid")} name={["accountingconfig", "qbo_departmentid"]}> label={t("bodyshop.labels.printlater")}
<Input /> valuePropName="checked"
</Form.Item> name={["accountingconfig", "printlater"]}
<Form.Item >
label={t("bodyshop.labels.accountingtiers")} <Switch />
rules={[ </Form.Item>
{ <Form.Item
required: true label={t("bodyshop.labels.emaillater")}
//message: t("general.validation.required"), valuePropName="checked"
} name={["accountingconfig", "emaillater"]}
]} >
name={["accountingconfig", "tiers"]} <Switch />
> </Form.Item>
<Radio.Group> <Form.Item
<Radio value={2}>2</Radio> label={t("bodyshop.fields.inhousevendorid")}
<Radio value={3}>3</Radio> name={"inhousevendorid"}
</Radio.Group> rules={[
</Form.Item> {
<Form.Item shouldUpdate> required: true
{() => { //message: t("general.validation.required"),
return ( }
]}
>
<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 <Form.Item
label={t("bodyshop.labels.2tiersetup")} label={t("bodyshop.fields.invoice_federal_tax_rate")}
shouldUpdate name={["bill_tax_rates", "federal_tax_rate"]}
rules={[ rules={[
{ {
required: form.getFieldValue(["accountingconfig", "tiers"]) === 2 required: true
//message: t("general.validation.required"), //message: t("general.validation.required"),
} }
]} ]}
name={["accountingconfig", "twotierpref"]}
> >
<Radio.Group disabled={form.getFieldValue(["accountingconfig", "tiers"]) === 3}> <InputNumber />
<Radio value="name">{t("bodyshop.labels.2tiername")}</Radio>
<Radio value="source">{t("bodyshop.labels.2tiersource")}</Radio>
</Radio.Group>
</Form.Item> </Form.Item>
); )
}} })}
</Form.Item> <Form.Item
<Form.Item label={t("bodyshop.fields.invoice_state_tax_rate")}
label={t("bodyshop.labels.printlater")} name={["bill_tax_rates", "state_tax_rate"]}
valuePropName="checked" rules={[
name={["accountingconfig", "printlater"]} {
> required: true
<Switch /> //message: t("general.validation.required"),
</Form.Item> }
<Form.Item ]}
label={t("bodyshop.labels.emaillater")} >
valuePropName="checked" <InputNumber />
name={["accountingconfig", "emaillater"]} </Form.Item>
> <Form.Item
<Switch /> label={t("bodyshop.fields.invoice_local_tax_rate")}
</Form.Item> name={["bill_tax_rates", "local_tax_rate"]}
<Form.Item rules={[
label={t("bodyshop.fields.inhousevendorid")} {
name={"inhousevendorid"} required: true
rules={[ //message: t("general.validation.required"),
{ }
required: true ]}
//message: t("general.validation.required"), >
} <InputNumber />
]} </Form.Item>
> <Form.Item
<Input /> name={["md_payment_types"]}
</Form.Item> label={t("bodyshop.fields.md_payment_types")}
<Form.Item rules={[
label={t("bodyshop.fields.default_adjustment_rate")} {
name={"default_adjustment_rate"} required: true,
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"),
//message: t("general.validation.required"), //message: t("general.validation.required"),
type: "array" type: "array"
}; }
} ]}
]} >
> <Select mode="tags" />
<Select mode="tags" /> </Form.Item>
</Form.Item> <Form.Item
{ClosingPeriod.treatment === "on" && ( name={["md_categories"]}
<> label={t("bodyshop.fields.md_categories")}
<Form.Item rules={[
name={["accountingconfig", "ClosingPeriod"]} {
label={t("bodyshop.fields.closingperiod")} //{t("reportcenter.labels.dates")} //message: t("general.validation.required"),
> type: "array"
<DatePicker.RangePicker format="MM/DD/YYYY" presets={DatePickerRanges} /> }
</Form.Item> ]}
</> >
)} <Select mode="tags" />
</LayoutFormRow> </Form.Item>
<LayoutFormRow header={t("bodyshop.labels.scoreboardsetup")} id="scoreboardsetup"> <Form.Item name={["enforce_class"]} label={t("bodyshop.fields.enforce_class")} valuePropName="checked">
<Form.Item <Switch />
label={t("bodyshop.fields.dailypainttarget")} </Form.Item>
name={["scoreboard_target", "dailyPaintTarget"]} <Form.Item
rules={[ name={["accountingconfig", "ReceivableCustomField1"]}
{ label={t("bodyshop.fields.ReceivableCustomField", { number: 1 })}
required: true >
//message: t("general.validation.required"), {ReceivableCustomFieldSelect}
} </Form.Item>
]} <Form.Item
> name={["accountingconfig", "ReceivableCustomField2"]}
<InputNumber min={0} precision={0} /> label={t("bodyshop.fields.ReceivableCustomField", { number: 2 })}
</Form.Item> >
{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 <Form.Item
label={t("bodyshop.fields.dailybodytarget")} label={t("bodyshop.fields.dailybodytarget")}
name={["scoreboard_target", "dailyBodyTarget"]} name={["scoreboard_target", "dailyBodyTarget"]}
rules={[ rules={[
{ {
required: true required: true
//message: t("general.validation.required"), //message: t("general.validation.required"),
} }
]} ]}
> >
<InputNumber min={0} precision={0} /> <InputNumber min={0} precision={0} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.lastnumberworkingdays")} label={t("bodyshop.fields.lastnumberworkingdays")}
name={["scoreboard_target", "lastNumberWorkingDays"]} name={["scoreboard_target", "lastNumberWorkingDays"]}
rules={[ rules={[
{ {
required: true required: true
//message: t("general.validation.required"), //message: t("general.validation.required"),
} }
]} ]}
> >
<InputNumber min={0} max={12} precision={0} /> <InputNumber min={0} max={12} precision={0} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.ignoreblockeddays")} label={t("bodyshop.fields.ignoreblockeddays")}
name={["scoreboard_target", "ignoreblockeddays"]} name={["scoreboard_target", "ignoreblockeddays"]}
valuePropName="checked" valuePropName="checked"
> >
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.prodtargethrs")} label={t("bodyshop.fields.prodtargethrs")}
name={["prodtargethrs"]} name={["prodtargethrs"]}
rules={[ rules={[
{ {
required: true required: true
//message: t("general.validation.required"), //message: t("general.validation.required"),
} }
]} ]}
> >
<InputNumber min={1} precision={1} /> <InputNumber min={1} precision={1} />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
</FeatureWrapper>
<LayoutFormRow header={t("bodyshop.labels.systemsettings")} id="systemsettings"> <LayoutFormRow header={t("bodyshop.labels.systemsettings")} id="systemsettings">
<Form.Item <Form.Item
name={["md_referral_sources"]} name={["md_referral_sources"]}
@@ -567,27 +572,32 @@ export function ShopInfoGeneral({ form, bodyshop }) {
> >
<Select mode="tags" /> <Select mode="tags" />
</Form.Item> </Form.Item>
<Form.Item
name={["tt_allow_post_to_invoiced"]} {HasFeatureAccess({ featureName: "timetickets", bodyshop }) && (
label={t("bodyshop.fields.tt_allow_post_to_invoiced")} <>
valuePropName="checked" <Form.Item
> name={["tt_allow_post_to_invoiced"]}
<Switch /> label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
</Form.Item> valuePropName="checked"
<Form.Item >
name={["tt_enforce_hours_for_tech_console"]} <Switch />
label={t("bodyshop.fields.tt_enforce_hours_for_tech_console")} </Form.Item>
valuePropName="checked" <Form.Item
> name={["tt_enforce_hours_for_tech_console"]}
<Switch /> label={t("bodyshop.fields.tt_enforce_hours_for_tech_console")}
</Form.Item> valuePropName="checked"
<Form.Item >
name={["bill_allow_post_to_closed"]} <Switch />
label={t("bodyshop.fields.bill_allow_post_to_closed")} </Form.Item>
valuePropName="checked" <Form.Item
> name={["bill_allow_post_to_closed"]}
<Switch /> label={t("bodyshop.fields.bill_allow_post_to_closed")}
</Form.Item> valuePropName="checked"
>
<Switch />
</Form.Item>
</>
)}
<Form.Item <Form.Item
name={["md_ded_notes"]} name={["md_ded_notes"]}
label={t("bodyshop.fields.md_ded_notes")} label={t("bodyshop.fields.md_ded_notes")}
@@ -1042,111 +1052,118 @@ export function ShopInfoGeneral({ form, bodyshop }) {
}} }}
</Form.List> </Form.List>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")} id="md_ccc_rates"> <FeatureWrapper featureName="courtesycars" noauth={() => null}>
<Form.List name={["md_ccc_rates"]}> <LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")} id="md_ccc_rates">
{(fields, { add, remove, move }) => { <Form.List name={["md_ccc_rates"]}>
return ( {(fields, { add, remove, move }) => {
<div> return (
{fields.map((field, index) => ( <div>
<Form.Item key={field.key}> {fields.map((field, index) => (
<LayoutFormRow noDivider> <Form.Item key={field.key}>
<Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}> <LayoutFormRow noDivider>
<Input /> <Form.Item label={t("general.labels.label")} key={`${index}label`} name={[field.name, "label"]}>
</Form.Item> <Input />
<Form.Item label={t("contracts.fields.actax")} key={`${index}actax`} name={[field.name, "actax"]}> </Form.Item>
<InputNumber precision={2} /> <Form.Item
</Form.Item> label={t("contracts.fields.actax")}
<Form.Item key={`${index}actax`}
label={t("contracts.fields.dailyfreekm")} name={[field.name, "actax"]}
key={`${index}dailyfreekm`} >
name={[field.name, "dailyfreekm"]} <InputNumber precision={2} />
> </Form.Item>
<InputNumber precision={2} /> <Form.Item
</Form.Item> label={t("contracts.fields.dailyfreekm")}
<Form.Item key={`${index}dailyfreekm`}
label={t("contracts.fields.refuelcharge")} name={[field.name, "dailyfreekm"]}
key={`${index}refuelcharge`} >
name={[field.name, "refuelcharge"]} <InputNumber precision={2} />
> </Form.Item>
<InputNumber precision={2} /> <Form.Item
</Form.Item> label={t("contracts.fields.refuelcharge")}
<Form.Item key={`${index}refuelcharge`}
label={t("contracts.fields.excesskmrate")} name={[field.name, "refuelcharge"]}
key={`${index}excesskmrate`} >
name={[field.name, "excesskmrate"]} <InputNumber precision={2} />
> </Form.Item>
<InputNumber precision={2} /> <Form.Item
</Form.Item> label={t("contracts.fields.excesskmrate")}
<Form.Item key={`${index}excesskmrate`}
label={t("contracts.fields.cleanupcharge")} name={[field.name, "excesskmrate"]}
key={`${index}cleanupcharge`} >
name={[field.name, "cleanupcharge"]} <InputNumber precision={2} />
> </Form.Item>
<InputNumber precision={2} /> <Form.Item
</Form.Item> label={t("contracts.fields.cleanupcharge")}
<Form.Item key={`${index}cleanupcharge`}
label={t("contracts.fields.damagewaiver")} name={[field.name, "cleanupcharge"]}
key={`${index}damagewaiver`} >
name={[field.name, "damagewaiver"]} <InputNumber precision={2} />
> </Form.Item>
<InputNumber precision={2} /> <Form.Item
</Form.Item> label={t("contracts.fields.damagewaiver")}
<Form.Item key={`${index}damagewaiver`}
label={t("contracts.fields.federaltax")} name={[field.name, "damagewaiver"]}
key={`${index}federaltax`} >
name={[field.name, "federaltax"]} <InputNumber precision={2} />
> </Form.Item>
<InputNumber precision={2} /> <Form.Item
</Form.Item> label={t("contracts.fields.federaltax")}
<Form.Item key={`${index}federaltax`}
label={t("contracts.fields.statetax")} name={[field.name, "federaltax"]}
key={`${index}statetax`} >
name={[field.name, "statetax"]} <InputNumber precision={2} />
> </Form.Item>
<InputNumber precision={2} /> <Form.Item
</Form.Item> label={t("contracts.fields.statetax")}
<Form.Item key={`${index}statetax`}
label={t("contracts.fields.localtax")} name={[field.name, "statetax"]}
key={`${index}localtax`} >
name={[field.name, "localtax"]} <InputNumber precision={2} />
> </Form.Item>
<InputNumber precision={2} /> <Form.Item
</Form.Item> label={t("contracts.fields.localtax")}
<Form.Item key={`${index}localtax`}
label={t("contracts.fields.coverage")} name={[field.name, "localtax"]}
key={`${index}coverage`} >
name={[field.name, "coverage"]} <InputNumber precision={2} />
> </Form.Item>
<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> <Space wrap>
<DeleteFilled <DeleteFilled
onClick={() => { onClick={() => {
remove(field.name); remove(field.name);
}} }}
/> />
<FormListMoveArrows move={move} index={index} total={fields.length} /> <FormListMoveArrows move={move} index={index} total={fields.length} />
</Space> </Space>
</LayoutFormRow> </LayoutFormRow>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item> </Form.Item>
))} </div>
<Form.Item> );
<Button }}
type="dashed" </Form.List>
onClick={() => { </LayoutFormRow>
add(); </FeatureWrapper>
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.fields.md_jobline_presets")} id="md_jobline_presets"> <LayoutFormRow grow header={t("bodyshop.fields.md_jobline_presets")} id="md_jobline_presets">
<Form.List name={["md_jobline_presets"]}> <Form.List name={["md_jobline_presets"]}>
{(fields, { add, remove, move }) => { {(fields, { add, remove, move }) => {

View File

@@ -319,6 +319,18 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
))} ))}
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item
name={["deliverchecklist", "actual_delivery"]}
label={t("bodyshop.fields.deliver.require_actual_delivery_date")}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Switch />
</Form.Item>
</SelectorDiv> </SelectorDiv>
</div> </div>
); );

View File

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

View File

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

View File

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

View File

@@ -6,7 +6,8 @@ import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort, statusSort } from "../../utils/sorters"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component"; import VehicleDetailUpdateJobsComponent from "../vehicle-detail-update-jobs/vehicle-detail-update-jobs.component";
@@ -67,9 +68,20 @@ export function VehicleDetailJobsComponent({ vehicle, bodyshop }) {
text: status, text: status,
value: 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"), title: t("jobs.fields.clm_total"),
dataIndex: "clm_total", dataIndex: "clm_total",

View File

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

View File

@@ -905,7 +905,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
} }
joblines( joblines(
where: { removed: { _eq: false }, part_type: { _is_null: false, _nin: ["PAE", "PAS", "PASL"] } } where: { removed: { _eq: false } }
order_by: { line_no: asc } order_by: { line_no: asc }
) { ) {
id id
@@ -1149,6 +1149,7 @@ export const UPDATE_JOB = gql`
suspended suspended
queued_for_parts queued_for_parts
scheduled_completion scheduled_completion
scheduled_delivery
actual_in actual_in
date_repairstarted date_repairstarted
date_void date_void

View File

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

View File

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

View File

@@ -51,6 +51,17 @@ export function JobsAvailablePageContainer({ partnerVersion, setBreadcrumbs, set
{!partnerVersion && ( {!partnerVersion && (
<AlertComponent <AlertComponent
type="warning" 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", { message={t("general.messages.partnernotrunning", {
app: InstanceRenderManager({ app: InstanceRenderManager({
imex: "$t(titles.imexonline)", imex: "$t(titles.imexonline)",

View File

@@ -1,5 +1,5 @@
import { Button, Result, Space, Steps } from "antd";
import { PageHeader } from "@ant-design/pro-layout"; import { PageHeader } from "@ant-design/pro-layout";
import { Button, Result, Space, Steps } from "antd";
import React, { useContext, useState } from "react"; import React, { useContext, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -9,7 +9,6 @@ import JobsCreateJobsInfo from "../../components/jobs-create-jobs-info/jobs-crea
import JobsCreateOwnerInfoContainer from "../../components/jobs-create-owner-info/jobs-create-owner-info.container"; import JobsCreateOwnerInfoContainer from "../../components/jobs-create-owner-info/jobs-create-owner-info.container";
import JobsCreateVehicleInfoContainer from "../../components/jobs-create-vehicle-info/jobs-create-vehicle-info.container"; import JobsCreateVehicleInfoContainer from "../../components/jobs-create-vehicle-info/jobs-create-vehicle-info.container";
import JobCreateContext from "../../pages/jobs-create/jobs-create.context"; import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
import FormsFieldChanged from "../../components/form-fields-changed-alert/form-fields-changed-alert.component";
export default function JobsCreateComponent({ form }) { export default function JobsCreateComponent({ form }) {
const [pageIndex, setPageIndex] = useState(0); const [pageIndex, setPageIndex] = useState(0);
@@ -20,28 +19,32 @@ export default function JobsCreateComponent({ form }) {
const steps = [ const steps = [
{ {
title: t("jobs.labels.create.vehicleinfo"), title: t("jobs.labels.create.vehicleinfo"),
id: "step-job-vehicleinfo",
content: <JobsCreateVehicleInfoContainer form={form} />, content: <JobsCreateVehicleInfoContainer form={form} />,
validation: !!state.vehicle.new || !!state.vehicle.selectedid || !!state.vehicle.none, validation: !!state.vehicle.new || !!state.vehicle.selectedid || !!state.vehicle.none,
error: t("vehicles.errors.selectexistingornew") error: t("vehicles.errors.selectexistingornew")
}, },
{ {
title: t("jobs.labels.create.ownerinfo"), title: t("jobs.labels.create.ownerinfo"),
id: "step-job-ownerinfo",
content: <JobsCreateOwnerInfoContainer />, content: <JobsCreateOwnerInfoContainer />,
validation: !!state.owner.new || !!state.owner.selectedid, validation: !!state.owner.new || !!state.owner.selectedid,
error: t("owners.errors.selectexistingornew") error: t("owners.errors.selectexistingornew")
}, },
{ {
title: t("jobs.labels.create.jobinfo"), title: t("jobs.labels.create.jobinfo"),
id: "step-job-jobinfo",
content: <JobsCreateJobsInfo form={form} selected={pageIndex === 2} /> content: <JobsCreateJobsInfo form={form} selected={pageIndex === 2} />
} }
]; ];
const next = () => { const next = () => {
setPageIndex(pageIndex + 1); setPageIndex(pageIndex + 1);
console.log("NExt"); console.log("Next");
}; };
const prev = () => { const prev = () => {
setPageIndex(pageIndex - 1); setPageIndex(pageIndex - 1);
console.log("Previous");
}; };
const { Step } = Steps; const { Step } = Steps;
@@ -50,26 +53,26 @@ export default function JobsCreateComponent({ form }) {
<PageHeader <PageHeader
extra={ extra={
<Space wrap> <Space wrap>
{pageIndex > 0 && <Button onClick={() => prev()}>Previous</Button>} {pageIndex > 0 && <Button onClick={() => prev()}>{t("general.actions.previous")}</Button>}
{pageIndex < steps.length - 1 && ( {pageIndex < steps.length - 1 && (
<Button <Button
type="primary" type="primary"
onClick={() => { onClick={() => {
next(); next();
// form form
// .validateFields() .validateFields()
// .then((r) => { .then((r) => {
// if (steps[pageIndex].validation) { if (steps[pageIndex].validation) {
// setErrorMessage(null); setErrorMessage(null);
// next(); next();
// } else { } else {
// setErrorMessage(steps[pageIndex].error); setErrorMessage(steps[pageIndex].error);
// } }
// }) })
// .catch((error) => console.log("error", error)); .catch((error) => console.log("error", error));
}} }}
> >
Next {t("general.actions.next")}
</Button> </Button>
)} )}
{pageIndex === steps.length - 1 && ( {pageIndex === steps.length - 1 && (
@@ -101,17 +104,17 @@ export default function JobsCreateComponent({ form }) {
}} }}
onClick={() => { onClick={() => {
setPageIndex(idx); setPageIndex(idx);
// form form
// .validateFields() .validateFields()
// .then((r) => { .then((r) => {
// if (steps[pageIndex].validation) { if (steps[pageIndex].validation) {
// setErrorMessage(null); setErrorMessage(null);
// setPageIndex(idx); setPageIndex(idx);
// } else { } else {
// setErrorMessage(steps[pageIndex].error); setErrorMessage(steps[pageIndex].error);
// } }
// }) })
// .catch((error) => console.log("error", error)); .catch((error) => console.log("error", error));
}} }}
/> />
))} ))}
@@ -141,7 +144,7 @@ export default function JobsCreateComponent({ form }) {
) : ( ) : (
<div> <div>
<ProgressButtons top /> <ProgressButtons top />
<FormsFieldChanged form={form} />
{errorMessage ? ( {errorMessage ? (
<div> <div>
<AlertComponent message={errorMessage} type="error" /> <AlertComponent message={errorMessage} type="error" />

View File

@@ -291,6 +291,7 @@ export function JobsDetailPage({
{ {
key: "general", key: "general",
icon: <Icon component={FaShieldAlt} />, icon: <Icon component={FaShieldAlt} />,
id: "job-details-general",
label: t("menus.jobsdetail.general"), label: t("menus.jobsdetail.general"),
forceRender: true, forceRender: true,
children: <JobsDetailGeneral job={job} form={form} /> children: <JobsDetailGeneral job={job} form={form} />
@@ -298,6 +299,7 @@ export function JobsDetailPage({
{ {
key: "repairdata", key: "repairdata",
icon: <BarsOutlined />, icon: <BarsOutlined />,
id: "job-details-repairdata",
label: t("menus.jobsdetail.repairdata"), label: t("menus.jobsdetail.repairdata"),
forceRender: true, forceRender: true,
children: <JobsLinesContainer job={job} joblines={job.joblines} refetch={refetch} form={form} /> children: <JobsLinesContainer job={job} joblines={job.joblines} refetch={refetch} form={form} />
@@ -305,18 +307,21 @@ export function JobsDetailPage({
{ {
key: "rates", key: "rates",
icon: <DollarCircleOutlined />, icon: <DollarCircleOutlined />,
id: "job-details-rates",
label: t("menus.jobsdetail.rates"), label: t("menus.jobsdetail.rates"),
forceRender: true, forceRender: true,
children: <JobsDetailRates job={job} form={form} /> children: <JobsDetailRates job={job} form={form} />
}, },
{ {
key: "totals", key: "totals",
id: "job-details-totals",
icon: <DollarCircleOutlined />, icon: <DollarCircleOutlined />,
label: t("menus.jobsdetail.totals"), label: t("menus.jobsdetail.totals"),
children: <JobsDetailTotals job={job} refetch={refetch} /> children: <JobsDetailTotals job={job} refetch={refetch} />
}, },
{ {
key: "partssublet", key: "partssublet",
id: "job-details-partssublet",
icon: <ToolFilled />, icon: <ToolFilled />,
label: HasFeatureAccess({ featureName: "bills", bodyshop }) label: HasFeatureAccess({ featureName: "bills", bodyshop })
? t("menus.jobsdetail.partssublet") ? t("menus.jobsdetail.partssublet")
@@ -331,6 +336,7 @@ export function JobsDetailPage({
? [ ? [
{ {
key: "labor", key: "labor",
id: "job-details-labor",
icon: <Icon component={FaHardHat} />, icon: <Icon component={FaHardHat} />,
label: t("menus.jobsdetail.labor"), label: t("menus.jobsdetail.labor"),
children: <JobsDetailLaborContainer job={job} jobId={job.id} /> children: <JobsDetailLaborContainer job={job} jobId={job.id} />
@@ -340,11 +346,13 @@ export function JobsDetailPage({
{ {
key: "lifecycle", key: "lifecycle",
icon: <BarsOutlined />, icon: <BarsOutlined />,
id: "job-details-lifecycle",
label: t("menus.jobsdetail.lifecycle"), label: t("menus.jobsdetail.lifecycle"),
children: <JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses} /> children: <JobLifecycleComponent job={job} statuses={bodyshop.md_ro_statuses} />
}, },
{ {
key: "dates", key: "dates",
id: "job-details-dates",
icon: <CalendarFilled />, icon: <CalendarFilled />,
label: t("menus.jobsdetail.dates"), label: t("menus.jobsdetail.dates"),
forceRender: true, forceRender: true,
@@ -358,6 +366,7 @@ export function JobsDetailPage({
? [ ? [
{ {
key: "documents", key: "documents",
id: "job-details-documents",
icon: <FileImageFilled />, icon: <FileImageFilled />,
label: t("jobs.labels.documents"), label: t("jobs.labels.documents"),
children: bodyshop.uselocalmediaserver ? ( children: bodyshop.uselocalmediaserver ? (
@@ -370,6 +379,7 @@ export function JobsDetailPage({
: []), : []),
{ {
key: "notes", key: "notes",
id: "job-details-notes",
icon: <Icon component={FaRegStickyNote} />, icon: <Icon component={FaRegStickyNote} />,
label: t("jobs.labels.notes"), label: t("jobs.labels.notes"),
children: <JobNotesContainer jobId={job.id} /> children: <JobNotesContainer jobId={job.id} />
@@ -377,12 +387,14 @@ export function JobsDetailPage({
{ {
key: "audit", key: "audit",
icon: <HistoryOutlined />, icon: <HistoryOutlined />,
id: "job-details-audit",
label: t("jobs.labels.audit"), label: t("jobs.labels.audit"),
children: <JobAuditTrail jobId={job.id} /> children: <JobAuditTrail jobId={job.id} />
}, },
{ {
key: "tasks", key: "tasks",
icon: <FaTasks />, icon: <FaTasks />,
id: "job-details-tasks",
label: ( label: (
<Space direction="horizontal"> <Space direction="horizontal">
{t("jobs.labels.tasks")} {t("jobs.labels.tasks")}

View File

@@ -1,4 +1,4 @@
import { Button, Collapse, FloatButton, Layout, Space, Spin, Tag } from "antd"; import { FloatButton, Layout, Spin } from "antd";
// import preval from "preval.macro"; // import preval from "preval.macro";
import React, { lazy, Suspense, useEffect, useState } from "react"; import React, { lazy, Suspense, useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -117,7 +117,6 @@ const mapDispatchToProps = (dispatch) => ({
export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) { export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoyRideFinished }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [chatVisible] = useState(false); const [chatVisible] = useState(false);
const [tours, setTours] = useState([]);
useEffect(() => { useEffect(() => {
const widgetId = InstanceRenderManager({ const widgetId = InstanceRenderManager({
@@ -630,28 +629,6 @@ export function Manage({ conflict, bodyshop, enableJoyRide, joyRideSteps, setJoy
Disclaimer & Notices Disclaimer & Notices
</Link> </Link>
</div> </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> </Footer>
</Layout> </Layout>
</> </>

View File

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

View File

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

View File

@@ -15,6 +15,7 @@ import { getToken } from "firebase/messaging";
import i18next from "i18next"; import i18next from "i18next";
import LogRocket from "logrocket"; import LogRocket from "logrocket";
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects"; import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
import { Userpilot } from "userpilot";
import { factory } from "../../App/App.container"; import { factory } from "../../App/App.container";
import { import {
analytics, analytics,
@@ -25,6 +26,10 @@ import {
messaging, messaging,
updateCurrentUser updateCurrentUser
} from "../../firebase/firebase.utils"; } 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 { import {
checkInstanceId, checkInstanceId,
sendPasswordResetFailure, sendPasswordResetFailure,
@@ -43,11 +48,6 @@ import {
validatePasswordResetSuccess validatePasswordResetSuccess
} from "./user.actions"; } from "./user.actions";
import UserActionTypes from "./user.types"; 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(); const fpPromise = FingerprintJS.load();
@@ -310,10 +310,41 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
updateUserDetailsSuccess(authRecord[0] ? { validemail: authRecord[0].user.validemail } : { validemail: false }) updateUserDetailsSuccess(authRecord[0] ? { validemail: authRecord[0].user.validemail } : { validemail: false })
); );
const user = yield select((state) => state.user.currentUser);
if (payload.features.singleDeviceOnly) { 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 { try {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,6 @@
import axios from "axios"; import axios from "axios";
import { auth } from "../firebase/firebase.utils"; import { auth } from "../firebase/firebase.utils";
import InstanceRenderManager from "./instanceRenderMgr";
axios.defaults.baseURL = axios.defaults.baseURL =
import.meta.env.VITE_APP_AXIOS_BASE_API_URL || import.meta.env.VITE_APP_AXIOS_BASE_API_URL ||
@@ -12,6 +13,15 @@ export const axiosAuthInterceptorId = axios.interceptors.request.use(
if (token) { if (token) {
config.headers.Authorization = `Bearer ${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; return config;

View File

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

View File

@@ -1081,6 +1081,32 @@ export const TemplateList = (type, context) => {
}, },
group: "purchases" group: "purchases"
}, },
purchases_by_ro_detail_date: {
title: i18n.t("reportcenter.templates.purchases_by_ro_detail_date"),
description: "",
subject: i18n.t("reportcenter.templates.purchases_by_ro_detail_date"),
key: "purchases_by_ro_detail_date",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced")
},
group: "purchases"
},
purchases_by_ro_summary_date: {
title: i18n.t("reportcenter.templates.purchases_by_ro_summary_date"),
description: "",
subject: i18n.t("reportcenter.templates.purchases_by_ro_summary_date"),
key: "purchases_by_ro_summary_date",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced")
},
group: "purchases"
},
job_costing_ro_date_summary: { job_costing_ro_date_summary: {
title: i18n.t("reportcenter.templates.job_costing_ro_date_summary"), title: i18n.t("reportcenter.templates.job_costing_ro_date_summary"),
description: "", description: "",
@@ -1168,6 +1194,17 @@ export const TemplateList = (type, context) => {
}, },
group: "customers" group: "customers"
}, },
payments_by_date_payment: {
title: i18n.t("reportcenter.templates.payments_by_date_payment"),
subject: i18n.t("reportcenter.templates.payments_by_date_payment"),
key: "payments_by_date_payment",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.payments"),
field: i18n.t("payments.fields.date")
},
group: "customers"
},
schedule: { schedule: {
title: i18n.t("reportcenter.templates.schedule"), title: i18n.t("reportcenter.templates.schedule"),
subject: i18n.t("reportcenter.templates.schedule"), subject: i18n.t("reportcenter.templates.schedule"),

View File

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

View File

@@ -920,6 +920,7 @@
- cdk_dealerid - cdk_dealerid
- city - city
- claimscorpid - claimscorpid
- convenient_company
- country - country
- created_at - created_at
- default_adjustment_rate - default_adjustment_rate

View File

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

View File

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

View File

@@ -0,0 +1,10 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE OR REPLACE VIEW "public"."joblines_status" AS
-- SELECT j.jobid,
-- j.status,
-- count(1) AS count,
-- j.part_type
-- FROM joblines j
-- WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND (j.part_qty <> 0) AND (j.act_price <> 0) AND (j.removed IS FALSE))
-- GROUP BY j.jobid, j.status, j.part_type;

View File

@@ -0,0 +1,8 @@
CREATE OR REPLACE VIEW "public"."joblines_status" AS
SELECT j.jobid,
j.status,
count(1) AS count,
j.part_type
FROM joblines j
WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND (j.part_qty <> 0) AND (j.act_price <> 0) AND (j.removed IS FALSE))
GROUP BY j.jobid, j.status, j.part_type;

View File

@@ -0,0 +1,10 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE OR REPLACE VIEW "public"."joblines_status" AS
-- SELECT j.jobid,
-- j.status,
-- count(1) AS count,
-- j.part_type
-- FROM joblines j
-- WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND (j.part_qty <> (0)::numeric) AND (j.act_price <> (0)::numeric) AND (j.removed IS FALSE))
-- GROUP BY j.jobid, j.status, j.part_type;

View File

@@ -0,0 +1,8 @@
CREATE OR REPLACE VIEW "public"."joblines_status" AS
SELECT j.jobid,
j.status,
count(1) AS count,
j.part_type
FROM joblines j
WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND (j.part_qty <> (0)::numeric) AND (j.act_price <> (0)::numeric) AND (j.removed IS FALSE))
GROUP BY j.jobid, j.status, j.part_type;

View File

@@ -174,10 +174,7 @@ async function OpenSearchUpdateHandler(req, res) {
const bulkOperation = []; const bulkOperation = [];
slicedArray.forEach((bill) => { slicedArray.forEach((bill) => {
bulkOperation.push({ index: { _index: "bills", _id: bill.id } }); bulkOperation.push({ index: { _index: "bills", _id: bill.id } });
bulkOperation.push({ bulkOperation.push({ ...bill, bodyshopid: bill.job.bodyshopid });
...omit(bill, ["job"]),
bodyshopid: bill.job.bodyshopid
});
}); });
promiseQueue.push(bulkOperation); promiseQueue.push(bulkOperation);
} }

480
package-lock.json generated
View File

@@ -19,6 +19,8 @@
"better-queue": "^3.8.12", "better-queue": "^3.8.12",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"canvas": "^2.11.2",
"chart.js": "^4.4.3",
"cloudinary": "^2.0.2", "cloudinary": "^2.0.2",
"compression": "^1.7.4", "compression": "^1.7.4",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",
@@ -1687,6 +1689,45 @@
"@jridgewell/sourcemap-codec": "^1.4.14" "@jridgewell/sourcemap-codec": "^1.4.14"
} }
}, },
"node_modules/@kurkle/color": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.2.tgz",
"integrity": "sha512-fuscdXJ9G1qb7W8VdHi+IwRqij3lBkosAm4ydQtEmbY58OzHXqQhvlxqEkoz0yssNVn38bcpRWgA9PP+OGoisw=="
},
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
"dependencies": {
"detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
"node-fetch": "^2.6.7",
"nopt": "^5.0.0",
"npmlog": "^5.0.1",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"tar": "^6.1.11"
},
"bin": {
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@mapbox/node-pre-gyp/node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"deprecated": "Rimraf versions prior to v4 are no longer supported",
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/@oozcitak/dom": { "node_modules/@oozcitak/dom": {
"version": "1.15.10", "version": "1.15.10",
"resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz", "resolved": "https://registry.npmjs.org/@oozcitak/dom/-/dom-1.15.10.tgz",
@@ -2609,6 +2650,11 @@
"node": ">=10.0.0" "node": ">=10.0.0"
} }
}, },
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
},
"node_modules/abort-controller": { "node_modules/abort-controller": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
@@ -2705,6 +2751,24 @@
"resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz",
"integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==" "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw=="
}, },
"node_modules/aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ=="
},
"node_modules/are-we-there-yet": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
"deprecated": "This package is no longer supported.",
"dependencies": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/array-flatten": { "node_modules/array-flatten": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@@ -3006,6 +3070,20 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/canvas": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz",
"integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==",
"hasInstallScript": true,
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.0",
"nan": "^2.17.0",
"simple-get": "^3.0.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/caseless": { "node_modules/caseless": {
"version": "0.12.0", "version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
@@ -3039,6 +3117,17 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/chart.js": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.3.tgz",
"integrity": "sha512-qK1gkGSRYcJzqrrzdR6a+I0vQ4/R+SoODXyAjscQ/4mzuNzySaMCd+hyVxitSY1+L2fjPD1Gbn+ibNqRmwQeLw==",
"dependencies": {
"@kurkle/color": "^0.3.0"
},
"engines": {
"pnpm": ">=8"
}
},
"node_modules/cheerio": { "node_modules/cheerio": {
"version": "1.0.0-rc.12", "version": "1.0.0-rc.12",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz", "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0-rc.12.tgz",
@@ -3075,6 +3164,14 @@
"url": "https://github.com/sponsors/fb55" "url": "https://github.com/sponsors/fb55"
} }
}, },
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"engines": {
"node": ">=10"
}
},
"node_modules/cliui": { "node_modules/cliui": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -3135,6 +3232,14 @@
"simple-swizzle": "^0.2.2" "simple-swizzle": "^0.2.2"
} }
}, },
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"bin": {
"color-support": "bin.js"
}
},
"node_modules/color/node_modules/color-convert": { "node_modules/color/node_modules/color-convert": {
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
@@ -3228,8 +3333,7 @@
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
"dev": true
}, },
"node_modules/concat-stream": { "node_modules/concat-stream": {
"version": "1.6.2", "version": "1.6.2",
@@ -3294,6 +3398,11 @@
"url": "https://github.com/open-cli-tools/concurrently?sponsor=1" "url": "https://github.com/open-cli-tools/concurrently?sponsor=1"
} }
}, },
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ=="
},
"node_modules/content-disposition": { "node_modules/content-disposition": {
"version": "0.5.4", "version": "0.5.4",
"resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
@@ -3540,6 +3649,17 @@
"node": ">=0.10" "node": ">=0.10"
} }
}, },
"node_modules/decompress-response": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
"dependencies": {
"mimic-response": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/deeks": { "node_modules/deeks": {
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/deeks/-/deeks-3.1.0.tgz", "resolved": "https://registry.npmjs.org/deeks/-/deeks-3.1.0.tgz",
@@ -3588,6 +3708,11 @@
"node": ">=0.4.0" "node": ">=0.4.0"
} }
}, },
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ=="
},
"node_modules/depd": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
@@ -3614,6 +3739,14 @@
"npm": "1.2.8000 || >= 1.4.16" "npm": "1.2.8000 || >= 1.4.16"
} }
}, },
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"engines": {
"node": ">=8"
}
},
"node_modules/dev-null": { "node_modules/dev-null": {
"version": "0.1.1", "version": "0.1.1",
"resolved": "https://registry.npmjs.org/dev-null/-/dev-null-0.1.1.tgz", "resolved": "https://registry.npmjs.org/dev-null/-/dev-null-0.1.1.tgz",
@@ -4286,11 +4419,32 @@
"node": ">=6 <7 || >=8" "node": ">=6 <7 || >=8"
} }
}, },
"node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/fs-minipass/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fs.realpath": { "node_modules/fs.realpath": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
"dev": true
}, },
"node_modules/ftp": { "node_modules/ftp": {
"version": "0.3.10", "version": "0.3.10",
@@ -4339,6 +4493,31 @@
"integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==", "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
"optional": true "optional": true
}, },
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"deprecated": "This package is no longer supported.",
"dependencies": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.1",
"object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/gauge/node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
"node_modules/gaxios": { "node_modules/gaxios": {
"version": "6.1.1", "version": "6.1.1",
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.1.1.tgz", "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.1.1.tgz",
@@ -4450,6 +4629,46 @@
"assert-plus": "^1.0.0" "assert-plus": "^1.0.0"
} }
}, },
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"deprecated": "Glob versions prior to v9 are no longer supported",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/globals": { "node_modules/globals": {
"version": "11.12.0", "version": "11.12.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
@@ -4634,6 +4853,11 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ=="
},
"node_modules/hasown": { "node_modules/hasown": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz",
@@ -4761,7 +4985,6 @@
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dev": true,
"dependencies": { "dependencies": {
"once": "^1.3.0", "once": "^1.3.0",
"wrappy": "1" "wrappy": "1"
@@ -5329,6 +5552,28 @@
"resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
"integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A==" "integrity": "sha512-ncTzHV7NvsQZkYe1DW7cbDLm0YpzHmZF5r/iyP3ZnQtMiJ+pjzisCiMNI+Sj+xQF5pXhSHxSB3uDbsBTzY/c2A=="
}, },
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"dependencies": {
"semver": "^6.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"bin": {
"semver": "bin/semver.js"
}
},
"node_modules/make-error": { "node_modules/make-error": {
"version": "1.3.6", "version": "1.3.6",
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
@@ -5402,6 +5647,17 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/mimic-response": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimalistic-assert": { "node_modules/minimalistic-assert": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz",
@@ -5435,6 +5691,29 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/minizlib/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/mkdirp": { "node_modules/mkdirp": {
"version": "0.5.6", "version": "0.5.6",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz",
@@ -5490,8 +5769,7 @@
"node_modules/nan": { "node_modules/nan": {
"version": "2.18.0", "version": "2.18.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz", "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz",
"integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==", "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w=="
"optional": true
}, },
"node_modules/negotiator": { "node_modules/negotiator": {
"version": "0.6.3", "version": "0.6.3",
@@ -5631,6 +5909,32 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
"deprecated": "This package is no longer supported.",
"dependencies": {
"are-we-there-yet": "^2.0.0",
"console-control-strings": "^1.1.0",
"gauge": "^3.0.0",
"set-blocking": "^2.0.0"
}
},
"node_modules/nth-check": { "node_modules/nth-check": {
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
@@ -5824,7 +6128,6 @@
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"dev": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -6491,6 +6794,11 @@
"node": ">= 0.8.0" "node": ">= 0.8.0"
} }
}, },
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw=="
},
"node_modules/set-function-length": { "node_modules/set-function-length": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.1.1.tgz",
@@ -6562,6 +6870,35 @@
"url": "https://github.com/sponsors/isaacs" "url": "https://github.com/sponsors/isaacs"
} }
}, },
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/simple-get": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
"dependencies": {
"decompress-response": "^4.2.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/simple-swizzle": { "node_modules/simple-swizzle": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
@@ -6722,16 +7059,6 @@
"node": ">=12" "node": ">=12"
} }
}, },
"node_modules/source-map-explorer/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/source-map-explorer/node_modules/cliui": { "node_modules/source-map-explorer/node_modules/cliui": {
"version": "7.0.4", "version": "7.0.4",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
@@ -6743,38 +7070,6 @@
"wrap-ansi": "^7.0.0" "wrap-ansi": "^7.0.0"
} }
}, },
"node_modules/source-map-explorer/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/source-map-explorer/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/source-map-explorer/node_modules/source-map": { "node_modules/source-map-explorer/node_modules/source-map": {
"version": "0.7.4", "version": "0.7.4",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
@@ -7158,6 +7453,41 @@
"url": "https://github.com/chalk/supports-color?sponsor=1" "url": "https://github.com/chalk/supports-color?sponsor=1"
} }
}, },
"node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/tar/node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"engines": {
"node": ">=8"
}
},
"node_modules/tar/node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/teeny-request": { "node_modules/teeny-request": {
"version": "9.0.0", "version": "9.0.0",
"resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz", "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-9.0.0.tgz",
@@ -7210,48 +7540,6 @@
"node": ">=6.0.0" "node": ">=6.0.0"
} }
}, },
"node_modules/temp/node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/temp/node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/temp/node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/temp/node_modules/rimraf": { "node_modules/temp/node_modules/rimraf": {
"version": "2.6.3", "version": "2.6.3",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
@@ -7599,6 +7887,14 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/winston": { "node_modules/winston": {
"version": "3.11.0", "version": "3.11.0",
"resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz", "resolved": "https://registry.npmjs.org/winston/-/winston-3.11.0.tgz",

View File

@@ -29,6 +29,8 @@
"better-queue": "^3.8.12", "better-queue": "^3.8.12",
"bluebird": "^3.7.2", "bluebird": "^3.7.2",
"body-parser": "^1.20.2", "body-parser": "^1.20.2",
"canvas": "^2.11.2",
"chart.js": "^4.4.3",
"cloudinary": "^2.0.2", "cloudinary": "^2.0.2",
"compression": "^1.7.4", "compression": "^1.7.4",
"cookie-parser": "^1.4.6", "cookie-parser": "^1.4.6",

View File

@@ -1,9 +1,13 @@
const DineroQbFormat = require("./accounting-constants").DineroQbFormat; const DineroQbFormat = require("./accounting-constants").DineroQbFormat;
const Dinero = require("dinero.js"); const Dinero = require("dinero.js");
const { DiscountNotAlreadyCounted } = require("../job/job-totals");
const logger = require("../utils/logger");
const InstanceManager = require("../utils/instanceMgr").default; const InstanceManager = require("../utils/instanceMgr").default;
const { DiscountNotAlreadyCounted } = InstanceManager({
imex: require("../job/job-totals"),
rome: require("../job/job-totals-USA"),
promanager: "USE_ROME"
});
const logger = require("../utils/logger");
exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes, classes }) { exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes, classes }) {
const InvoiceLineAdd = []; const InvoiceLineAdd = [];
@@ -67,16 +71,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
{ {
local: false, local: false,
federal: InstanceManager({ imex: true, rome: false }), federal: InstanceManager({ imex: true, rome: false }),
state: state: checkStateTax(jobline, jobs_by_pk)
jobs_by_pk.state_tax_rate === 0
? false
: jobline.db_ref === "900511" ||
jobline.db_ref === "900510" ||
(jobline.mod_lb_hrs === 0 && //Extending IO-1375 as a part of IO-2023
jobline.act_price > 0 &&
jobline.lbr_op === "OP14")
? true
: jobline.tax_part
}, },
bodyshop.md_responsibility_centers.sales_tax_codes bodyshop.md_responsibility_centers.sales_tax_codes
); );
@@ -152,7 +147,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
{ {
local: false, local: false,
federal: InstanceManager({ imex: true, rome: false }), federal: InstanceManager({ imex: true, rome: false }),
state: jobs_by_pk.state_tax_rate === 0 ? false : true state: jobs_by_pk.tax_lbr_rt === 0 ? false : true
}, },
bodyshop.md_responsibility_centers.sales_tax_codes bodyshop.md_responsibility_centers.sales_tax_codes
); );
@@ -220,7 +215,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
{ {
local: false, local: false,
federal: InstanceManager({ imex: true, rome: false }), federal: InstanceManager({ imex: true, rome: false }),
state: jobs_by_pk.state_tax_rate === 0 ? false : true state: jobs_by_pk.tax_paint_mat_rt === 0 ? false : true
}, },
bodyshop.md_responsibility_centers.sales_tax_codes bodyshop.md_responsibility_centers.sales_tax_codes
); );
@@ -289,7 +284,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
{ {
local: false, local: false,
federal: InstanceManager({ imex: true, rome: false }), federal: InstanceManager({ imex: true, rome: false }),
state: jobs_by_pk.state_tax_rate === 0 ? false : true state: jobs_by_pk.tax_shop_mat_rt === 0 ? false : true
}, },
bodyshop.md_responsibility_centers.sales_tax_codes bodyshop.md_responsibility_centers.sales_tax_codes
); );
@@ -375,7 +370,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
{ {
local: false, local: false,
federal: InstanceManager({ imex: true, rome: false }), federal: InstanceManager({ imex: true, rome: false }),
state: jobs_by_pk.state_tax_rate === 0 ? false : true state: jobs_by_pk.tax_tow_rt === 0 ? false : true
}, },
bodyshop.md_responsibility_centers.sales_tax_codes bodyshop.md_responsibility_centers.sales_tax_codes
); );
@@ -435,7 +430,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
{ {
local: false, local: false,
federal: InstanceManager({ imex: true, rome: false }), federal: InstanceManager({ imex: true, rome: false }),
state: jobs_by_pk.state_tax_rate === 0 ? false : true state: jobs_by_pk.tax_str_rt === 0 ? false : true
}, },
bodyshop.md_responsibility_centers.sales_tax_codes bodyshop.md_responsibility_centers.sales_tax_codes
); );
@@ -495,7 +490,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
{ {
local: false, local: false,
federal: InstanceManager({ imex: true, rome: false }), federal: InstanceManager({ imex: true, rome: false }),
state: jobs_by_pk.state_tax_rate === 0 ? false : true state: jobs_by_pk.tax_lbr_rt === 0 ? false : true
}, },
bodyshop.md_responsibility_centers.sales_tax_codes bodyshop.md_responsibility_centers.sales_tax_codes
); );
@@ -637,7 +632,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
} }
} }
//QB USA with GST //QB USA with GST and PST
//This was required for the No. 1 Collision Group. //This was required for the No. 1 Collision Group.
if ( if (
bodyshop.accountingconfig && bodyshop.accountingconfig &&
@@ -656,6 +651,17 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
Qty: 1 Qty: 1
} }
}); });
InvoiceLineAdd.push({
DetailType: "SalesItemLineDetail",
Amount: Dinero(jobs_by_pk.job_totals.totals.state_tax).toFormat(DineroQbFormat),
SalesItemLineDetail: {
...(jobs_by_pk.class ? { ClassRef: { value: classes[jobs_by_pk.class] } } : {}),
ItemRef: {
value: items[bodyshop.md_responsibility_centers.taxes.state.accountitem]
},
Qty: 1
}
});
} }
} else { } else {
//Handle insurance profile adjustments //Handle insurance profile adjustments
@@ -875,6 +881,64 @@ exports.createMultiQbPayerLines = function ({ bodyshop, jobs_by_pk, qbo = false,
return InvoiceLineAdd; return InvoiceLineAdd;
}; };
function checkStateTax(jobline, jobs_by_pk) {
const isPaintOrShopMat = jobline.db_ref === "936008" || jobline.db_ref === "936007";
if (isPaintOrShopMat) {
if (jobline.db_ref === "936008") {
if (jobs_by_pk.tax_paint_mat_rt === 0) {
return false;
} else {
return true;
}
}
if (jobline.db_ref === "936007") {
if (jobs_by_pk.tax_shop_mat_rt === 0) {
return false;
} else {
return true;
}
}
}
const isAdditionalCost =
(jobline.lbr_op === "OP13" ||
(jobline.lbr_op === "OP14" && jobline.act_price > 0 && jobline.mod_lb_hrs === 0) ||
(jobline.db_ref && jobline.db_ref.startsWith("9360")) ||
(jobline.db_ref && jobline.db_ref.startsWith("90051"))) &&
!isPaintOrShopMat;
if (!jobline.part_type && isAdditionalCost) {
if (jobs_by_pk.tax_lbr_rt === 0) {
return false;
} else {
return true;
}
}
if (jobline.tax_part === false) {
return false;
} else {
if (jobline.part_type) {
if (
!jobs_by_pk.parts_tax_rates[`${jobline.part_type.toUpperCase()}`] ||
jobs_by_pk.parts_tax_rates[`${jobline.part_type.toUpperCase()}`].prt_tax_in === false ||
jobs_by_pk.parts_tax_rates[`${jobline.part_type.toUpperCase()}`].prt_tax_rt === 0
) {
return false;
} else {
return true;
}
} else {
if (jobs_by_pk.tax_lbr_rt === 0) {
return false;
} else {
return true;
}
}
}
}
function CheckQBOUSATaxID({ jobline, job, type }) { function CheckQBOUSATaxID({ jobline, job, type }) {
//Replacing this to be all non-taxable items with the refactor of parts tax rates. //Replacing this to be all non-taxable items with the refactor of parts tax rates.
return "NON"; return "NON";

View File

@@ -9,380 +9,409 @@ const CdkBase = require("../web-sockets/web-socket");
const Dinero = require("dinero.js"); const Dinero = require("dinero.js");
const _ = require("lodash"); const _ = require("lodash");
const { DiscountNotAlreadyCounted } = require("../job/job-totals");
const InstanceManager = require("../utils/instanceMgr").default; const InstanceManager = require("../utils/instanceMgr").default;
const { DiscountNotAlreadyCounted } = InstanceManager({
imex: require("../job/job-totals"),
rome: require("../job/job-totals-USA"),
promanager: "USE_ROME"
});
exports.defaultRoute = async function (req, res) {
try {
CdkBase.createLogEvent(req, "DEBUG", `Received request to calculate allocations for ${req.body.jobid}`);
const jobData = await QueryJobData(req, req.BearerToken, req.body.jobid);
return res.status(200).json({ data: calculateAllocations(req, jobData) });
} catch (error) {
console.log(error);
CdkBase.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
res.status(500).json({ error: `Error encountered in CdkCalculateAllocations. ${error}` });
}
};
exports.default = async function (socket, jobid) { exports.default = async function (socket, jobid) {
try { try {
CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${jobid}`); const jobData = await QueryJobData(socket, "Bearer " + socket.handshake.auth.token, jobid);
const job = await QueryJobData(socket, jobid); return calculateAllocations(socket, jobData);
const { bodyshop } = job;
const taxAllocations = InstanceManager({
executeFunction: true,
deubg: true,
args: [],
imex: () => ({
local: {
center: bodyshop.md_responsibility_centers.taxes.local.name,
sale: Dinero(job.job_totals.totals.local_tax),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes.local,
costCenter: bodyshop.md_responsibility_centers.taxes.local
},
state: {
center: bodyshop.md_responsibility_centers.taxes.state.name,
sale: Dinero(job.job_totals.totals.state_tax),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes.state,
costCenter: bodyshop.md_responsibility_centers.taxes.state
},
federal: {
center: bodyshop.md_responsibility_centers.taxes.federal.name,
sale: Dinero(job.job_totals.totals.federal_tax),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes.federal,
costCenter: bodyshop.md_responsibility_centers.taxes.federal
}
}),
rome: () => ({
tax_ty1: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty1`].name,
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty1Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`]
},
tax_ty2: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty2`].name,
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty2Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`]
},
tax_ty3: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty3`].name,
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty3Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`]
},
tax_ty4: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty4`].name,
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty4Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`]
},
tax_ty5: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty5`].name,
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty5Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`]
}
})
});
//Determine if there are MAPA and MASH lines already on the estimate.
//If there are, don't do anything extra (mitchell estimate)
//Otherwise, calculate them and add them to the default MAPA and MASH centers.
let hasMapaLine = false;
let hasMashLine = false;
const profitCenterHash = job.joblines.reduce((acc, val) => {
//Check the Parts Assignment
if (val.db_ref === "936008") {
//If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
hasMapaLine = true;
}
if (val.db_ref === "936007") {
hasMashLine = true;
}
if (val.profitcenter_part) {
if (!acc[val.profitcenter_part]) acc[val.profitcenter_part] = Dinero();
let DineroAmount = Dinero({
amount: Math.round(val.act_price * 100)
}).multiply(val.part_qty || 1);
DineroAmount = DineroAmount.add(
((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
DiscountNotAlreadyCounted(val, job.joblines)
? val.prt_dsmk_m
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
: Dinero({
amount: Math.round(val.act_price * 100)
})
.multiply(val.part_qty || 0)
.percentage(Math.abs(val.prt_dsmk_p || 0))
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
: Dinero()
);
acc[val.profitcenter_part] = acc[val.profitcenter_part].add(DineroAmount);
}
if (val.profitcenter_labor && val.mod_lbr_ty) {
//Check the Labor Assignment.
if (!acc[val.profitcenter_labor]) acc[val.profitcenter_labor] = Dinero();
acc[val.profitcenter_labor] = acc[val.profitcenter_labor].add(
Dinero({
amount: Math.round(job[`rate_${val.mod_lbr_ty.toLowerCase()}`] * 100)
}).multiply(val.mod_lb_hrs)
);
}
return acc;
}, {});
const selectedDmsAllocationConfig = bodyshop.md_responsibility_centers.dms_defaults.find(
(d) => d.name === job.dms_allocation
);
CdkBase.createLogEvent(
socket,
"DEBUG",
`Using DMS Allocation ${selectedDmsAllocationConfig && selectedDmsAllocationConfig.name} for cost export.`
);
let costCenterHash = {};
//Check whether to skip this if PBS and using AP module.
const disablebillwip = !!bodyshop?.pbs_configuration?.disablebillwip;
if (!disablebillwip) {
costCenterHash = job.bills.reduce((bill_acc, bill_val) => {
bill_val.billlines.map((line_val) => {
if (!bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]])
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = Dinero();
let lineDinero = Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100)
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1);
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] =
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]].add(lineDinero);
return null;
});
return bill_acc;
}, {});
}
job.timetickets.forEach((ticket) => {
//Get the total amount of the ticket.
let TicketTotal = Dinero({
amount: Math.round(
ticket.rate *
(ticket.employee && ticket.employee.flat_rate ? ticket.productivehrs || 0 : ticket.actualhrs || 0) *
100
)
});
//Add it to the right cost center.
if (!costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]])
costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]] = Dinero();
costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]] =
costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]].add(TicketTotal);
});
if (!hasMapaLine && job.job_totals.rates.mapa.total.amount > 0) {
// console.log("Adding MAPA Line Manually.");
const mapaAccountName = selectedDmsAllocationConfig.profits.MAPA;
const mapaAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === mapaAccountName);
if (mapaAccount) {
if (!profitCenterHash[mapaAccountName]) profitCenterHash[mapaAccountName] = Dinero();
profitCenterHash[mapaAccountName] = profitCenterHash[mapaAccountName].add(
Dinero(job.job_totals.rates.mapa.total)
);
} else {
//console.log("NO MAPA ACCOUNT FOUND!!");
}
}
if (!hasMashLine && job.job_totals.rates.mash.total.amount > 0) {
// console.log("Adding MASH Line Manually.");
const mashAccountName = selectedDmsAllocationConfig.profits.MASH;
const mashAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === mashAccountName);
if (mashAccount) {
if (!profitCenterHash[mashAccountName]) profitCenterHash[mashAccountName] = Dinero();
profitCenterHash[mashAccountName] = profitCenterHash[mashAccountName].add(
Dinero(job.job_totals.rates.mash.total)
);
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
}
}
console.log(
Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting),
typeof Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting)
);
if (!!bodyshop?.cdk_configuration?.sendmaterialscosting) {
//Manually send the percentage of the costing.
//Paint Mat
const mapaAccountName = selectedDmsAllocationConfig.costs.MAPA;
const mapaAccount = bodyshop.md_responsibility_centers.costs.find((c) => c.name === mapaAccountName);
if (mapaAccount) {
if (!costCenterHash[mapaAccountName]) costCenterHash[mapaAccountName] = Dinero();
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
Dinero(job.job_totals.rates.mapa.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting)
);
} else {
//console.log("NO MAPA ACCOUNT FOUND!!");
}
//Shop Mat
const mashAccountName = selectedDmsAllocationConfig.costs.MASH;
const mashAccount = bodyshop.md_responsibility_centers.costs.find((c) => c.name === mashAccountName);
if (mashAccount) {
if (!costCenterHash[mashAccountName]) costCenterHash[mashAccountName] = Dinero();
costCenterHash[mashAccountName] = costCenterHash[mashAccountName].add(
Dinero(job.job_totals.rates.mash.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting)
);
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
}
}
const { ca_bc_pvrt } = job;
if (ca_bc_pvrt) {
// const pvrtAccount = bodyshop.md_responsibility_centers.profits.find(
// (c) => c.name === mashAccountName
// );
taxAllocations.state.sale = taxAllocations.state.sale.add(
Dinero({ amount: Math.round((ca_bc_pvrt || 0) * 100) })
);
}
if (job.towing_payable && job.towing_payable !== 0) {
const towAccountName = selectedDmsAllocationConfig.profits.TOW;
const towAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === towAccountName);
if (towAccount) {
if (!profitCenterHash[towAccountName]) profitCenterHash[towAccountName] = Dinero();
profitCenterHash[towAccountName] = profitCenterHash[towAccountName].add(
Dinero({
amount: Math.round((job.towing_payable || 0) * 100)
})
);
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
}
}
if (job.storage_payable && job.storage_payable !== 0) {
const storageAccountName = selectedDmsAllocationConfig.profits.TOW;
const towAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === storageAccountName);
if (towAccount) {
if (!profitCenterHash[storageAccountName]) profitCenterHash[storageAccountName] = Dinero();
profitCenterHash[storageAccountName] = profitCenterHash[storageAccountName].add(
Dinero({
amount: Math.round((job.storage_payable || 0) * 100)
})
);
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
}
}
if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) {
const otherAccountName = selectedDmsAllocationConfig.profits.PAO;
const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === otherAccountName);
if (otherAccount) {
if (!profitCenterHash[otherAccountName]) profitCenterHash[otherAccountName] = Dinero();
profitCenterHash[otherAccountName] = profitCenterHash[otherAccountName].add(
Dinero({
amount: Math.round((job.adjustment_bottom_line || 0) * 100)
})
);
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
}
}
if (InstanceManager({ rome: true })) {
//profile level adjustments
Object.keys(job.job_totals.parts.adjustments).forEach((key) => {
const accountName = selectedDmsAllocationConfig.profits[key];
const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName);
if (otherAccount) {
if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero();
profitCenterHash[accountName] = profitCenterHash[accountName].add(
Dinero(job.job_totals.parts.adjustments[key])
);
} else {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}`
);
}
});
}
const jobAllocations = _.union(Object.keys(profitCenterHash), Object.keys(costCenterHash)).map((key) => {
const profitCenter = bodyshop.md_responsibility_centers.profits.find((c) => c.name === key);
const costCenter = bodyshop.md_responsibility_centers.costs.find((c) => c.name === key);
return {
center: key,
sale: profitCenterHash[key] ? profitCenterHash[key] : Dinero(),
cost: costCenterHash[key] ? costCenterHash[key] : Dinero(),
profitCenter,
costCenter
};
});
return [
...jobAllocations,
...Object.keys(taxAllocations)
.filter((key) => taxAllocations[key].sale.getAmount() > 0 || taxAllocations[key].cost.getAmount() > 0)
.map((key) => {
if (
key === "federal" &&
selectedDmsAllocationConfig.gst_override &&
selectedDmsAllocationConfig.gst_override !== ""
) {
const ret = { ...taxAllocations[key], tax: key };
ret.costCenter.dms_acctnumber = selectedDmsAllocationConfig.gst_override;
ret.profitCenter.dms_acctnumber = selectedDmsAllocationConfig.gst_override;
return ret;
} else {
return { ...taxAllocations[key], tax: key };
}
})
];
} catch (error) { } catch (error) {
console.log(error); console.log(error);
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`); CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
} }
}; };
async function QueryJobData(socket, jobid) { async function QueryJobData(connectionData, token, jobid) {
CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`); CdkBase.createLogEvent(connectionData, "DEBUG", `Querying job data for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {}); const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client const result = await client.setHeaders({ Authorization: token }).request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` }) CdkBase.createLogEvent(connectionData, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
.request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
return result.jobs_by_pk; return result.jobs_by_pk;
} }
function calculateAllocations(connectionData, job) {
const { bodyshop } = job;
const taxAllocations = InstanceManager({
executeFunction: true,
deubg: true,
args: [],
imex: () => ({
local: {
center: bodyshop.md_responsibility_centers.taxes.local.name,
sale: Dinero(job.job_totals.totals.local_tax),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes.local,
costCenter: bodyshop.md_responsibility_centers.taxes.local
},
state: {
center: bodyshop.md_responsibility_centers.taxes.state.name,
sale: Dinero(job.job_totals.totals.state_tax),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes.state,
costCenter: bodyshop.md_responsibility_centers.taxes.state
},
federal: {
center: bodyshop.md_responsibility_centers.taxes.federal.name,
sale: Dinero(job.job_totals.totals.federal_tax),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes.federal,
costCenter: bodyshop.md_responsibility_centers.taxes.federal
}
}),
rome: () => ({
tax_ty1: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty1`].name,
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty1Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty1`]
},
tax_ty2: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty2`].name,
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty2Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty2`]
},
tax_ty3: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty3`].name,
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty3Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty3`]
},
tax_ty4: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty4`].name,
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty4Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty4`]
},
tax_ty5: {
center: bodyshop.md_responsibility_centers.taxes[`tax_ty5`].name,
sale: Dinero(job.job_totals.totals.us_sales_tax_breakdown[`ty5Tax`]),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`],
costCenter: bodyshop.md_responsibility_centers.taxes[`tax_ty5`]
}
})
});
//Determine if there are MAPA and MASH lines already on the estimate.
//If there are, don't do anything extra (mitchell estimate)
//Otherwise, calculate them and add them to the default MAPA and MASH centers.
let hasMapaLine = false;
let hasMashLine = false;
const profitCenterHash = job.joblines.reduce((acc, val) => {
//Check the Parts Assignment
if (val.db_ref === "936008") {
//If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
hasMapaLine = true;
}
if (val.db_ref === "936007") {
hasMashLine = true;
}
if (val.profitcenter_part) {
if (!acc[val.profitcenter_part]) acc[val.profitcenter_part] = Dinero();
let DineroAmount = Dinero({
amount: Math.round(val.act_price * 100)
}).multiply(val.part_qty || 1);
DineroAmount = DineroAmount.add(
((val.prt_dsmk_m && val.prt_dsmk_m !== 0) || (val.prt_dsmk_p && val.prt_dsmk_p !== 0)) &&
DiscountNotAlreadyCounted(val, job.joblines)
? val.prt_dsmk_m
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
: Dinero({
amount: Math.round(val.act_price * 100)
})
.multiply(val.part_qty || 0)
.percentage(Math.abs(val.prt_dsmk_p || 0))
.multiply(val.prt_dsmk_p > 0 ? 1 : -1)
: Dinero()
);
acc[val.profitcenter_part] = acc[val.profitcenter_part].add(DineroAmount);
}
if (val.profitcenter_labor && val.mod_lbr_ty) {
//Check the Labor Assignment.
if (!acc[val.profitcenter_labor]) acc[val.profitcenter_labor] = Dinero();
acc[val.profitcenter_labor] = acc[val.profitcenter_labor].add(
Dinero({
amount: Math.round(job[`rate_${val.mod_lbr_ty.toLowerCase()}`] * 100)
}).multiply(val.mod_lb_hrs)
);
}
return acc;
}, {});
const selectedDmsAllocationConfig = bodyshop.md_responsibility_centers.dms_defaults.find(
(d) => d.name === job.dms_allocation
);
CdkBase.createLogEvent(
connectionData,
"DEBUG",
`Using DMS Allocation ${selectedDmsAllocationConfig && selectedDmsAllocationConfig.name} for cost export.`
);
let costCenterHash = {};
//Check whether to skip this if PBS and using AP module.
const disablebillwip = !!bodyshop?.pbs_configuration?.disablebillwip;
if (!disablebillwip) {
costCenterHash = job.bills.reduce((bill_acc, bill_val) => {
bill_val.billlines.map((line_val) => {
if (!bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]])
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] = Dinero();
let lineDinero = Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100)
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1);
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]] =
bill_acc[selectedDmsAllocationConfig.costs[line_val.cost_center]].add(lineDinero);
return null;
});
return bill_acc;
}, {});
}
job.timetickets.forEach((ticket) => {
//Get the total amount of the ticket.
let TicketTotal = Dinero({
amount: Math.round(
ticket.rate *
(ticket.employee && ticket.employee.flat_rate ? ticket.productivehrs || 0 : ticket.actualhrs || 0) *
100
)
});
//Add it to the right cost center.
if (!costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]])
costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]] = Dinero();
costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]] =
costCenterHash[selectedDmsAllocationConfig.costs[ticket.ciecacode]].add(TicketTotal);
});
if (!hasMapaLine && job.job_totals.rates.mapa.total.amount > 0) {
// console.log("Adding MAPA Line Manually.");
const mapaAccountName = selectedDmsAllocationConfig.profits.MAPA;
const mapaAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === mapaAccountName);
if (mapaAccount) {
if (!profitCenterHash[mapaAccountName]) profitCenterHash[mapaAccountName] = Dinero();
profitCenterHash[mapaAccountName] = profitCenterHash[mapaAccountName].add(
Dinero(job.job_totals.rates.mapa.total)
);
} else {
//console.log("NO MAPA ACCOUNT FOUND!!");
}
}
if (!hasMashLine && job.job_totals.rates.mash.total.amount > 0) {
// console.log("Adding MASH Line Manually.");
const mashAccountName = selectedDmsAllocationConfig.profits.MASH;
const mashAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === mashAccountName);
if (mashAccount) {
if (!profitCenterHash[mashAccountName]) profitCenterHash[mashAccountName] = Dinero();
profitCenterHash[mashAccountName] = profitCenterHash[mashAccountName].add(
Dinero(job.job_totals.rates.mash.total)
);
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
}
}
// console.log(
// Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting),
// typeof Number.isInteger(bodyshop?.cdk_configuration?.sendmaterialscosting)
// );
if (!!bodyshop?.cdk_configuration?.sendmaterialscosting) {
//Manually send the percentage of the costing.
//Paint Mat
const mapaAccountName = selectedDmsAllocationConfig.costs.MAPA;
const mapaAccount = bodyshop.md_responsibility_centers.costs.find((c) => c.name === mapaAccountName);
if (mapaAccount) {
if (!costCenterHash[mapaAccountName]) costCenterHash[mapaAccountName] = Dinero();
if (job.bodyshop.use_paint_scale_data === true) {
if (job.mixdata.length > 0) {
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
Dinero({
amount: Math.round(((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100)
})
);
} else {
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
Dinero(job.job_totals.rates.mapa.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting)
);
}
} else {
costCenterHash[mapaAccountName] = costCenterHash[mapaAccountName].add(
Dinero(job.job_totals.rates.mapa.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting)
);
}
} else {
//console.log("NO MAPA ACCOUNT FOUND!!");
}
//Shop Mat
const mashAccountName = selectedDmsAllocationConfig.costs.MASH;
const mashAccount = bodyshop.md_responsibility_centers.costs.find((c) => c.name === mashAccountName);
if (mashAccount) {
if (!costCenterHash[mashAccountName]) costCenterHash[mashAccountName] = Dinero();
costCenterHash[mashAccountName] = costCenterHash[mashAccountName].add(
Dinero(job.job_totals.rates.mash.total).percentage(bodyshop?.cdk_configuration?.sendmaterialscosting)
);
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
}
}
const { ca_bc_pvrt } = job;
if (ca_bc_pvrt) {
// const pvrtAccount = bodyshop.md_responsibility_centers.profits.find(
// (c) => c.name === mashAccountName
// );
taxAllocations.state.sale = taxAllocations.state.sale.add(Dinero({ amount: Math.round((ca_bc_pvrt || 0) * 100) }));
}
if (job.towing_payable && job.towing_payable !== 0) {
const towAccountName = selectedDmsAllocationConfig.profits.TOW;
const towAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === towAccountName);
if (towAccount) {
if (!profitCenterHash[towAccountName]) profitCenterHash[towAccountName] = Dinero();
profitCenterHash[towAccountName] = profitCenterHash[towAccountName].add(
Dinero({
amount: Math.round((job.towing_payable || 0) * 100)
})
);
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
}
}
if (job.storage_payable && job.storage_payable !== 0) {
const storageAccountName = selectedDmsAllocationConfig.profits.TOW;
const towAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === storageAccountName);
if (towAccount) {
if (!profitCenterHash[storageAccountName]) profitCenterHash[storageAccountName] = Dinero();
profitCenterHash[storageAccountName] = profitCenterHash[storageAccountName].add(
Dinero({
amount: Math.round((job.storage_payable || 0) * 100)
})
);
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
}
}
if (job.adjustment_bottom_line && job.adjustment_bottom_line !== 0) {
const otherAccountName = selectedDmsAllocationConfig.profits.PAO;
const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === otherAccountName);
if (otherAccount) {
if (!profitCenterHash[otherAccountName]) profitCenterHash[otherAccountName] = Dinero();
profitCenterHash[otherAccountName] = profitCenterHash[otherAccountName].add(
Dinero({
amount: Math.round((job.adjustment_bottom_line || 0) * 100)
})
);
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
}
}
if (InstanceManager({ rome: true })) {
//profile level adjustments
Object.keys(job.job_totals.parts.adjustments).forEach((key) => {
const accountName = selectedDmsAllocationConfig.profits[key];
const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName);
if (otherAccount) {
if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero();
profitCenterHash[accountName] = profitCenterHash[accountName].add(
Dinero(job.job_totals.parts.adjustments[key])
);
} else {
CdkBase.createLogEvent(
connectionData,
"ERROR",
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}`
);
}
});
}
const jobAllocations = _.union(Object.keys(profitCenterHash), Object.keys(costCenterHash)).map((key) => {
const profitCenter = bodyshop.md_responsibility_centers.profits.find((c) => c.name === key);
const costCenter = bodyshop.md_responsibility_centers.costs.find((c) => c.name === key);
return {
center: key,
sale: profitCenterHash[key] ? profitCenterHash[key] : Dinero(),
cost: costCenterHash[key] ? costCenterHash[key] : Dinero(),
profitCenter,
costCenter
};
});
return [
...jobAllocations,
...Object.keys(taxAllocations)
.filter((key) => taxAllocations[key].sale.getAmount() > 0 || taxAllocations[key].cost.getAmount() > 0)
.map((key) => {
if (
key === "federal" &&
selectedDmsAllocationConfig.gst_override &&
selectedDmsAllocationConfig.gst_override !== ""
) {
const ret = { ...taxAllocations[key], tax: key };
ret.costCenter.dms_acctnumber = selectedDmsAllocationConfig.gst_override;
ret.profitCenter.dms_acctnumber = selectedDmsAllocationConfig.gst_override;
return ret;
} else {
return { ...taxAllocations[key], tax: key };
}
})
];
}

View File

@@ -211,6 +211,8 @@ const CreateRepairOrderTag = (job, errorCallback) => {
} }
const repairCosts = CreateCosts(job); const repairCosts = CreateCosts(job);
const jobline = CreateJobLines(job.joblines);
const timeticket = CreateTimeTickets(job.timetickets);
try { try {
const ret = { const ret = {
@@ -242,6 +244,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
}, },
InsuranceCompany: job.ins_co_nm || "", InsuranceCompany: job.ins_co_nm || "",
Claim: job.clm_no || "", Claim: job.clm_no || "",
DMSAllocation: job.dms_allocation || "",
Contacts: { Contacts: {
CSR: job.employee_csr_rel CSR: job.employee_csr_rel
? `${ ? `${
@@ -276,8 +279,11 @@ const CreateRepairOrderTag = (job, errorCallback) => {
DateInvoiced: DateInvoiced:
(job.date_invoiced && moment(job.date_invoiced).tz(job.bodyshop.timezone).format(DateFormat)) || "", (job.date_invoiced && moment(job.date_invoiced).tz(job.bodyshop.timezone).format(DateFormat)) || "",
DateExported: DateExported:
(job.date_exported && moment(job.date_exported).tz(job.bodyshop.timezone).format(DateFormat)) || "" (job.date_exported && moment(job.date_exported).tz(job.bodyshop.timezone).format(DateFormat)) || "",
DateVoid: (job.date_void && moment(job.date_void).tz(job.bodyshop.timezone).format(DateFormat)) || ""
}, },
JobLineDetails: { jobline },
TimeTicketDetails: { timeticket },
Sales: { Sales: {
Labour: { Labour: {
Aluminum: Dinero(job.job_totals.rates.laa.total).toFormat(DineroFormat), Aluminum: Dinero(job.job_totals.rates.laa.total).toFormat(DineroFormat),
@@ -625,3 +631,42 @@ const CreateCosts = (job) => {
}, 0) }, 0)
}; };
}; };
const CreateJobLines = (joblines) => {
const repairLines = [];
joblines.forEach((jobline) => {
repairLines.push({
line_description: jobline.line_desc,
oem_part_no: jobline.oem_partno,
alt_part_no: jobline.alt_partno,
op_code_desc: jobline.op_code_desc,
part_type: jobline.part_type,
part_qty: jobline.part_qty,
part_price: jobline.act_price,
labor_type: jobline.mod_lbr_ty,
labor_hours: jobline.mod_lb_hrs,
labor_sale: jobline.lbr_amt
});
});
return repairLines;
};
const CreateTimeTickets = (timetickets) => {
const timeTickets = [];
timetickets.forEach((ticket) => {
timeTickets.push({
date: ticket.date,
employee: ticket.employee.employee_number
.trim()
.concat(" - ", ticket.employee.first_name.trim(), " ", ticket.employee.last_name.trim())
.trim(),
productive_hrs: ticket.productivehrs,
actual_hrs: ticket.actualhrs,
cost_center: ticket.cost_center,
flat_rate: ticket.flat_rate,
rate: ticket.rate,
ticket_cost: ticket.flat_rate ? ticket.rate * ticket.productive_hrs : ticket.rate * ticket.actual_hrs
});
});
return timeTickets;
};

View File

@@ -24,7 +24,7 @@ const ses = new aws.SES({
const transporter = nodemailer.createTransport({ const transporter = nodemailer.createTransport({
SES: { ses, aws }, SES: { ses, aws },
sendingRate: 40 // 40 emails per second. sendingRate: InstanceManager({ imex: 40, rome: 10 })
}); });
// Initialize the Tasks Email Queue // Initialize the Tasks Email Queue
@@ -41,38 +41,28 @@ const tasksEmailQueueCleanup = async () => {
} }
}; };
// Handling SIGINT (e.g., Ctrl+C) if (process.env.NODE_ENV !== "development") {
process.on("SIGINT", async () => { // Handling SIGINT (e.g., Ctrl+C)
await tasksEmailQueueCleanup(); process.on("SIGINT", async () => {
process.exit(0); await tasksEmailQueueCleanup();
}); process.exit(0);
// Handling SIGTERM (e.g., sent by system shutdown) });
process.on("SIGTERM", async () => { // Handling SIGTERM (e.g., sent by system shutdown)
await tasksEmailQueueCleanup(); process.on("SIGTERM", async () => {
process.exit(0); await tasksEmailQueueCleanup();
}); process.exit(0);
// Handling uncaught exceptions });
process.on("uncaughtException", async (err) => { // Handling uncaught exceptions
await tasksEmailQueueCleanup(); process.on("uncaughtException", async (err) => {
process.exit(1); // Exit with an 'error' code await tasksEmailQueueCleanup();
}); process.exit(1); // Exit with an 'error' code
// Handling unhandled promise rejections });
process.on("unhandledRejection", async (reason, promise) => { // Handling unhandled promise rejections
await tasksEmailQueueCleanup(); process.on("unhandledRejection", async (reason, promise) => {
process.exit(1); // Exit with an 'error' code await tasksEmailQueueCleanup();
}); process.exit(1); // Exit with an 'error' code
});
const fromEmails = InstanceManager({ }
imex: "ImEX Online <noreply@imex.online>",
rome: "Rome Online <noreply@romeonline.io>",
promanager: "ProManager <noreply@promanager.web-est.com>"
});
const endPoints = InstanceManager({
imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online",
rome: process.env?.NODE_ENV === "test" ? "https//test.romeonline.io" : "https://romeonline.io",
promanager: process.env?.NODE_ENV === "test" ? "https//test.promanager.web-est.com" : "https://promanager.web-est.com"
});
/** /**
* Format the date for the email. * Format the date for the email.
@@ -105,6 +95,17 @@ const formatPriority = (priority) => {
* @returns {{header, body: string, subHeader: string}} * @returns {{header, body: string, subHeader: string}}
*/ */
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId) => { const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId) => {
const endPoints = InstanceManager({
imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online",
rome:
bodyshop.convenient_company === "promanager"
? process.env?.NODE_ENV === "test"
? "https//test.promanager.web-est.com"
: "https://promanager.web-est.com"
: process.env?.NODE_ENV === "test"
? "https//test.romeonline.io"
: "https://romeonline.io"
});
return { return {
header: title, header: title,
subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)}`, subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)}`,
@@ -121,12 +122,15 @@ const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, j
* @param taskIds * @param taskIds
* @param successCallback * @param successCallback
*/ */
const sendMail = (type, to, subject, html, taskIds, successCallback) => { const sendMail = (type, to, subject, html, taskIds, successCallback, requestInstance) => {
// Push next messages to Nodemailer const fromEmails = InstanceManager({
//transporter.once("idle", () => { imex: "ImEX Online <noreply@imex.online>",
// Note: This is commented out because despite being in the documentation, it does not work rome:
// and stackoverflow suggests it is not needed requestInstance === "promanager"
// if (transporter.isIdle()) { ? "ProManager <noreply@promanager.web-est.com>"
: "Rome Online <noreply@romeonline.io>"
});
transporter.sendMail( transporter.sendMail(
{ {
from: fromEmails, from: fromEmails,
@@ -184,7 +188,10 @@ const taskAssignedEmail = async (req, res) => {
tasks_by_pk.job, tasks_by_pk.job,
newTask.id newTask.id
) )
) ),
null,
null,
tasks_by_pk.bodyshop.convenient_company
); );
// We return success regardless because we don't want to block the event trigger. // We return success regardless because we don't want to block the event trigger.
@@ -233,6 +240,14 @@ const tasksRemindEmail = async (req, res) => {
// Iterate over all recipients and send the email. // Iterate over all recipients and send the email.
recipientCounts.forEach((recipient) => { recipientCounts.forEach((recipient) => {
const fromEmails = InstanceManager({
imex: "ImEX Online <noreply@imex.online>",
rome:
onlyTask.bodyshop.convenient_company === "promanager"
? "ProManager <noreply@promanager.web-est.com>"
: "Rome Online <noreply@romeonline.io>"
});
const emailData = { const emailData = {
from: fromEmails, from: fromEmails,
to: recipient.email to: recipient.email
@@ -261,6 +276,18 @@ const tasksRemindEmail = async (req, res) => {
} }
// There are multiple emails to send to this author. // There are multiple emails to send to this author.
else { else {
const endPoints = InstanceManager({
imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online",
rome:
allTasks[0].bodyshop.convenient_company === "promanager"
? process.env?.NODE_ENV === "test"
? "https//test.promanager.web-est.com"
: "https://promanager.web-est.com"
: process.env?.NODE_ENV === "test"
? "https//test.romeonline.io"
: "https://romeonline.io"
});
const allTasks = groupedTasks[recipient.email]; const allTasks = groupedTasks[recipient.email];
emailData.subject = `New Tasks Reminder - ${allTasks.length} Tasks require your attention`; emailData.subject = `New Tasks Reminder - ${allTasks.length} Tasks require your attention`;
emailData.html = generateEmailTemplate({ emailData.html = generateEmailTemplate({
@@ -278,11 +305,19 @@ const tasksRemindEmail = async (req, res) => {
if (emailData?.subject && emailData?.html) { if (emailData?.subject && emailData?.html) {
// Send Email // Send Email
sendMail("remind", emailData.to, emailData.subject, emailData.html, taskIds, (taskIds) => { sendMail(
for (const taskId of taskIds) { "remind",
tasksEmailQueue.push(taskId); emailData.to,
} emailData.subject,
}); emailData.html,
taskIds,
(taskIds) => {
for (const taskId of taskIds) {
tasksEmailQueue.push(taskId);
}
},
allTasks[0].bodyshop.convenient_company
);
} }
}); });

View File

@@ -206,8 +206,13 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
adjustment_bottom_line adjustment_bottom_line
state_tax_rate state_tax_rate
qb_multiple_payers qb_multiple_payers
parts_tax_rates
tax_paint_mat_rt tax_paint_mat_rt
tax_lbr_rt tax_lbr_rt
tax_shop_mat_rt
tax_sub_rt
tax_tow_rt
tax_str_rt
owner { owner {
accountingid accountingid
} }
@@ -1108,7 +1113,7 @@ exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uu
use_paint_scale_data use_paint_scale_data
timezone timezone
} }
jobs(where: {_and: [{updated_at: {_gt: $start}}, {updated_at: {_lte: $end}}, {shopid: {_eq: $bodyshopid}}]}) { jobs(where: {_and: [{updated_at: {_gt: $start}}, {updated_at: {_lte: $end}}, {converted: {_eq: true}}, {shopid: {_eq: $bodyshopid}}]}) {
actual_completion actual_completion
actual_delivery actual_delivery
actual_in actual_in
@@ -1133,6 +1138,8 @@ exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uu
date_invoiced date_invoiced
date_open date_open
date_repairstarted date_repairstarted
date_void
dms_allocation
employee_body_rel { employee_body_rel {
first_name first_name
last_name last_name
@@ -1163,6 +1170,7 @@ exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uu
ins_co_nm ins_co_nm
joblines(where: {removed: {_eq: false}}) { joblines(where: {removed: {_eq: false}}) {
act_price act_price
alt_partno
billlines(order_by: {bill: {date: desc_nulls_last}} limit: 1) { billlines(order_by: {bill: {date: desc_nulls_last}} limit: 1) {
actual_cost actual_cost
actual_price actual_price
@@ -1177,12 +1185,15 @@ exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uu
} }
db_price db_price
id id
lbr_amt
lbr_op lbr_op
line_desc line_desc
line_ind line_ind
line_no line_no
mod_lb_hrs mod_lb_hrs
mod_lbr_ty mod_lbr_ty
oem_partno
op_code_desc
parts_order_lines(order_by: {parts_order: {order_date: desc_nulls_last}} limit: 1){ parts_order_lines(order_by: {parts_order: {order_date: desc_nulls_last}} limit: 1){
parts_order{ parts_order{
id id
@@ -1195,7 +1206,6 @@ exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uu
profitcenter_labor profitcenter_labor
prt_dsmk_m prt_dsmk_m
prt_dsmk_p prt_dsmk_p
oem_partno
status status
} }
job_totals job_totals
@@ -1246,12 +1256,18 @@ exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uu
scheduled_in scheduled_in
status status
timetickets { timetickets {
id
rate
cost_center
actualhrs actualhrs
productivehrs cost_center
date
employee {
employee_number
first_name
last_name
}
flat_rate flat_rate
id
productivehrs
rate
} }
tlos_ind tlos_ind
v_color v_color
@@ -1534,6 +1550,7 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
op_code_desc op_code_desc
profitcenter_part profitcenter_part
profitcenter_labor profitcenter_labor
act_price_before_ppc
} }
bills { bills {
id id
@@ -1790,6 +1807,7 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
md_responsibility_centers md_responsibility_centers
cdk_configuration cdk_configuration
pbs_configuration pbs_configuration
use_paint_scale_data
} }
ro_number ro_number
dms_allocation dms_allocation
@@ -1897,6 +1915,10 @@ exports.GET_CDK_ALLOCATIONS = `query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
line_ref line_ref
unq_seq unq_seq
} }
mixdata(limit: 1, order_by: {updated_at: desc}) {
jobid
totalliquidcost
}
} }
}`; }`;
@@ -2431,6 +2453,7 @@ exports.QUERY_REMIND_TASKS = `
jobid jobid
bodyshop { bodyshop {
shopname shopname
convenient_company
} }
bodyshopid bodyshopid
} }
@@ -2453,6 +2476,7 @@ query QUERY_TASK_BY_ID($id: uuid!) {
} }
bodyshop{ bodyshop{
shopname shopname
convenient_company
} }
job{ job{
ro_number ro_number
@@ -2467,3 +2491,11 @@ query QUERY_TASK_BY_ID($id: uuid!) {
} }
`; `;
exports.GET_JOBS_BY_PKS = `query GET_JOBS_BY_PKS($ids: [uuid!]!) {
jobs(where: {id: {_in: $ids}}) {
id
shopid
}
}
`;

View File

@@ -6,6 +6,7 @@ const qs = require("query-string");
const axios = require("axios"); const axios = require("axios");
const moment = require("moment"); const moment = require("moment");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const InstanceManager = require("../utils/instanceMgr").default;
require("dotenv").config({ require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
@@ -16,7 +17,10 @@ const domain = process.env.NODE_ENV ? "secure" : "test";
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager"); const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
const client = new SecretsManagerClient({ const client = new SecretsManagerClient({
region: "ca-central-1" region: InstanceManager({
imex: "ca-central-1",
rome: "us-east-2"
})
}); });
const gqlClient = require("../graphql-client/graphql-client").client; const gqlClient = require("../graphql-client/graphql-client").client;
@@ -145,45 +149,89 @@ exports.generate_payment_url = async (req, res) => {
}; };
exports.postback = async (req, res) => { exports.postback = async (req, res) => {
logger.log("intellipay-postback", "ERROR", req.user?.email, null, req.body);
const { body: values } = req;
if (!values.invoice) {
res.sendStatus(200);
return;
}
// TODO query job by account name
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
id: values.invoice
});
// TODO add mutation to database
try { try {
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, { logger.log("intellipay-postback", "DEBUG", req.user?.email, null, req.body);
paymentInput: { const { body: values } = req;
amount: values.total,
transactionid: `C00 ${values.authcode}`,
payer: "Customer",
type: values.cardtype,
jobid: values.invoice,
date: moment(Date.now())
}
});
await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, { const comment = Buffer.from(values?.comment, "base64").toString();
paymentResponse: {
amount: values.total,
bodyshopid: job.jobs_by_pk.shopid,
paymentid: paymentResult.id,
jobid: values.invoice,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
});
res.send({ message: "Postback Successful" }); if ((!values.invoice || values.invoice === "") && !comment) {
//invoice is specified through the pay link. Comment by IO.
logger.log("intellipay-postback-ignored", "DEBUG", req.user?.email, null, req.body);
res.sendStatus(200);
return;
}
if (values.invoice) {
//This is a link email that's been sent out.
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
id: values.invoice
});
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: {
amount: values.total,
transactionid: values.authcode,
payer: "Customer",
type: values.cardtype,
jobid: values.invoice,
date: moment(Date.now())
}
});
const responseResults = await gqlClient.request(queries.INSERT_PAYMENT_RESPONSE, {
paymentResponse: {
amount: values.total,
bodyshopid: job.jobs_by_pk.shopid,
paymentid: paymentResult.id,
jobid: values.invoice,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
});
logger.log("intellipay-postback-link-success", "DEBUG", req.user?.email, null, {
iprequest: values,
responseResults,
paymentResult
});
res.sendStatus(200);
} else if (comment) {
//This has been triggered by IO and may have multiple jobs.
const partialPayments = JSON.parse(comment);
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
ids: partialPayments.map((p) => p.jobid)
});
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: partialPayments.map((p) => ({
amount: p.amount,
transactionid: values.authcode,
payer: "Customer",
type: values.cardtype,
jobid: p.jobid,
date: moment(Date.now()),
payment_responses: {
data: {
amount: values.total,
bodyshopid: jobs.jobs[0].shopid,
jobid: p.jobid,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
}
}))
});
logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, null, {
iprequest: values,
paymentResult
});
res.sendStatus(200);
}
} catch (error) { } catch (error) {
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, { logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error), error: JSON.stringify(error),

View File

@@ -2,7 +2,12 @@ const _ = require("lodash");
const Dinero = require("dinero.js"); const Dinero = require("dinero.js");
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const { DiscountNotAlreadyCounted } = require("./job-totals"); const InstanceManager = require("../utils/instanceMgr").default;
const { DiscountNotAlreadyCounted } = InstanceManager({
imex: require("../job/job-totals"),
rome: require("../job/job-totals-USA"),
promanager: "USE_ROME"
});
// Dinero.defaultCurrency = "USD"; // Dinero.defaultCurrency = "USD";
// Dinero.globalLocale = "en-CA"; // Dinero.globalLocale = "en-CA";
@@ -264,7 +269,7 @@ function GenerateCostingData(job) {
job && job &&
job.joblines.reduce( job.joblines.reduce(
(acc, val) => { (acc, val) => {
//Parts Lines //Shop or Paint Material Flags
if (val.db_ref === "936008") { if (val.db_ref === "936008") {
//If either of these DB REFs change, they also need to change in job-totals/job-costing calculations. //If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
hasMapaLine = true; hasMapaLine = true;
@@ -272,6 +277,8 @@ function GenerateCostingData(job) {
if (val.db_ref === "936007") { if (val.db_ref === "936007") {
hasMashLine = true; hasMashLine = true;
} }
//Labor Profit Center
if (val.mod_lbr_ty) { if (val.mod_lbr_ty) {
const laborProfitCenter = val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "Unknown"; const laborProfitCenter = val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "Unknown";
@@ -302,6 +309,7 @@ function GenerateCostingData(job) {
} }
} }
// Part Profit Center
if (val.part_type && val.part_type !== "PAE" && val.part_type !== "PAS" && val.part_type !== "PASL") { if (val.part_type && val.part_type !== "PAE" && val.part_type !== "PAS" && val.part_type !== "PASL") {
const partsProfitCenter = val.profitcenter_part || defaultProfits[val.part_type] || "Unknown"; const partsProfitCenter = val.profitcenter_part || defaultProfits[val.part_type] || "Unknown";
@@ -310,7 +318,9 @@ function GenerateCostingData(job) {
if (!partsProfitCenter) if (!partsProfitCenter)
console.log("Unknown cost/profit center mapping for parts.", val.line_desc, val.part_type); console.log("Unknown cost/profit center mapping for parts.", val.line_desc, val.part_type);
const partsAmount = Dinero({ const partsAmount = Dinero({
amount: Math.round((val.act_price || 0) * 100) amount: val.act_price_before_ppc
? Math.round(val.act_price_before_ppc * 100)
: Math.round(val.act_price * 100)
}) })
.multiply(val.part_qty || 1) .multiply(val.part_qty || 1)
.add( .add(
@@ -319,7 +329,9 @@ function GenerateCostingData(job) {
? val.prt_dsmk_m ? val.prt_dsmk_m
? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) }) ? Dinero({ amount: Math.round(val.prt_dsmk_m * 100) })
: Dinero({ : Dinero({
amount: Math.round(val.act_price * 100) amount: val.act_price_before_ppc
? Math.round(val.act_price_before_ppc * 100)
: Math.round(val.act_price * 100)
}) })
.multiply(val.part_qty || 0) .multiply(val.part_qty || 0)
.percentage(Math.abs(val.prt_dsmk_p || 0)) .percentage(Math.abs(val.prt_dsmk_p || 0))
@@ -329,6 +341,8 @@ function GenerateCostingData(job) {
if (!acc.parts[partsProfitCenter]) acc.parts[partsProfitCenter] = Dinero(); if (!acc.parts[partsProfitCenter]) acc.parts[partsProfitCenter] = Dinero();
acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add(partsAmount); acc.parts[partsProfitCenter] = acc.parts[partsProfitCenter].add(partsAmount);
} }
//Sublet Profit Center
if (val.part_type && val.part_type !== "PAE" && (val.part_type === "PAS" || val.part_type === "PASL")) { if (val.part_type && val.part_type !== "PAE" && (val.part_type === "PAS" || val.part_type === "PASL")) {
const partsProfitCenter = val.profitcenter_part || defaultProfits[val.part_type] || "Unknown"; const partsProfitCenter = val.profitcenter_part || defaultProfits[val.part_type] || "Unknown";
@@ -357,8 +371,11 @@ function GenerateCostingData(job) {
acc.sublet[partsProfitCenter] = acc.sublet[partsProfitCenter].add(partsAmount); acc.sublet[partsProfitCenter] = acc.sublet[partsProfitCenter].add(partsAmount);
} }
//To deal with additional costs. //Additional Profit Center
if (!val.part_type && !val.mod_lbr_ty) { if (
(!val.part_type && !val.mod_lbr_ty) ||
(!val.part_type && val.mod_lbr_ty && val.act_price > 0 && val.lbr_op !== "OP14")
) {
//Does it already have a defined profit center? //Does it already have a defined profit center?
//If so, use it, otherwise try to use the same from the auto-allocate logic in IO app jobs-close-auto-allocate. //If so, use it, otherwise try to use the same from the auto-allocate logic in IO app jobs-close-auto-allocate.
const partsProfitCenter = val.profitcenter_part || getAdditionalCostCenter(val, defaultProfits) || "Unknown"; const partsProfitCenter = val.profitcenter_part || getAdditionalCostCenter(val, defaultProfits) || "Unknown";

View File

@@ -643,7 +643,7 @@ function CalculateAdditional(job) {
additionalCosts: null, additionalCosts: null,
additionalCostItems: [], additionalCostItems: [],
adjustments: null, adjustments: null,
towing: null, towing: Dinero(),
shipping: Dinero(), shipping: Dinero(),
storage: null, storage: null,
pvrt: null, pvrt: null,
@@ -668,7 +668,7 @@ function CalculateAdditional(job) {
} }
if (val.line_desc.toLowerCase().includes("towing")) { if (val.line_desc.toLowerCase().includes("towing")) {
ret.towing = lineValue; ret.towing = ret.towing.add(lineValue);
return acc; return acc;
} else { } else {
ret.additionalCostItems.push({ key: val.line_desc, total: lineValue }); ret.additionalCostItems.push({ key: val.line_desc, total: lineValue });
@@ -718,6 +718,7 @@ function CalculateTaxesTotals(job, otherTotals) {
const taxableAmounts = { const taxableAmounts = {
PAA: Dinero(), PAA: Dinero(),
PAE: Dinero(),
PAN: Dinero(), PAN: Dinero(),
PAL: Dinero(), PAL: Dinero(),
PAR: Dinero(), PAR: Dinero(),
@@ -908,6 +909,25 @@ function CalculateTaxesTotals(job, otherTotals) {
} }
}); });
if (job.adjustment_bottom_line) {
const subtotal_before_adjustment = subtotal.add(Dinero({ amount: Math.round(job.adjustment_bottom_line * -100) }));
const percent_of_adjustment =
Math.round(
subtotal_before_adjustment.toUnit() /
(job.adjustment_bottom_line > 0 ? job.adjustment_bottom_line : job.adjustment_bottom_line * -1)
) / 100;
Object.keys(taxableAmountsByTier).forEach((taxTierKey) => {
taxable_adjustment = taxableAmountsByTier[taxTierKey].multiply(percent_of_adjustment);
console.log("🚀 ~ taxableAmountsByTier ~ taxable_adjustment:", taxable_adjustment);
if (job.adjustment_bottom_line > 0) {
taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].add(taxable_adjustment);
} else {
taxableAmountsByTier[taxTierKey] = taxableAmountsByTier[taxTierKey].subtract(taxable_adjustment);
}
});
}
const remainingTaxableAmounts = taxableAmountsByTier; const remainingTaxableAmounts = taxableAmountsByTier;
console.log("*** Taxable Amounts by Tier***"); console.log("*** Taxable Amounts by Tier***");
console.table(JSON.parse(JSON.stringify(taxableAmountsByTier))); console.table(JSON.parse(JSON.stringify(taxableAmountsByTier)));

View File

@@ -489,7 +489,7 @@ function CalculateAdditional(job) {
additionalCosts: null, additionalCosts: null,
additionalCostItems: [], additionalCostItems: [],
adjustments: null, adjustments: null,
towing: null, towing: Dinero(),
shipping: Dinero(), shipping: Dinero(),
storage: null, storage: null,
pvrt: null, pvrt: null,
@@ -512,7 +512,7 @@ function CalculateAdditional(job) {
} }
if (val.line_desc.toLowerCase().includes("towing")) { if (val.line_desc.toLowerCase().includes("towing")) {
ret.towing = lineValue; ret.towing = ret.towing.add(lineValue);
return acc; return acc;
} else { } else {
ret.additionalCostItems.push({ key: val.line_desc, total: lineValue }); ret.additionalCostItems.push({ key: val.line_desc, total: lineValue });

View File

@@ -0,0 +1,76 @@
const borderColors = [
"rgba(193, 0, 50, 1)",
"rgba(0, 101, 68, 1)",
"rgba(0, 45, 98, 1)",
"rgba(253, 184, 0, 1)",
"rgba(200, 112, 126, 1)",
"rgba(228, 142, 88, 1)",
"rgba(90, 160, 141, 1)",
"rgba(103, 143, 174, 1)",
"rgba(192, 136, 99, 1)",
"rgba(234, 3, 55, 1)",
"rgba(0, 149, 67, 1)",
"rgba(0, 81, 155, 1)",
"rgba(226, 143, 173, 1)",
"rgba(237, 170, 125, 1)",
"rgba(76, 146, 177, 1)",
"rgba(172, 153, 193, 1)",
"rgba(173, 167, 89, 1)",
"rgba(254, 197, 222, 1)",
"rgba(177, 231, 223, 1)",
"rgba(120, 199, 235, 1)",
"rgba(239, 180, 193, 1)",
"rgba(240, 199, 171, 1)",
"rgba(168, 200, 121, 1)",
"rgba(150, 177, 208, 1)",
"rgba(200, 194, 189, 1)",
"rgba(244, 244, 244, 1)",
"rgba(255, 99, 132, 1)",
"rgba(54, 162, 235, 1)",
"rgba(255, 206, 86, 1)",
"rgba(75, 192, 192, 1)",
"rgba(153, 102, 255, 1)",
"rgba(255, 159, 64, 1)",
"rgba(170, 183, 184, 1)",
];
const backgroundColors = [
'rgba(193, 0, 50, 0.2)',
'rgba(0, 101, 68, 0.2)',
'rgba(0, 45, 98, 0.2)',
'rgba(253, 184, 0, 0.2)',
'rgba(200, 112, 126, 0.2)',
'rgba(228, 142, 88, 0.2)',
'rgba(90, 160, 141, 0.2)',
'rgba(103, 143, 174, 0.2)',
'rgba(192, 136, 99, 0.2)',
'rgba(234, 3, 55, 0.2)',
'rgba(0, 149, 67, 0.2)',
'rgba(0, 81, 155, 0.2)',
'rgba(226, 143, 173, 0.2)',
'rgba(237, 170, 125, 0.2)',
'rgba(76, 146, 177, 0.2)',
'rgba(172, 153, 193, 0.2)',
'rgba(173, 167, 89, 0.2)',
'rgba(254, 197, 222, 0.2)',
'rgba(177, 231, 223, 0.2)',
'rgba(120, 199, 235, 0.2)',
'rgba(239, 180, 193, 0.2)',
'rgba(240, 199, 171, 0.2)',
'rgba(168, 200, 121, 0.2)',
'rgba(150, 177, 208, 0.2)',
'rgba(200, 194, 189, 0.2)',
'rgba(244, 244, 244, 0.2)',
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)',
'rgba(170, 183, 184, 0.2)',
];
module.exports = {
borderColors,
backgroundColors,
};

View File

@@ -0,0 +1,92 @@
const { createCanvas } = require("canvas");
const Chart = require("chart.js/auto");
const { backgroundColors, borderColors } = require("./canvas-colors");
const { isObject, defaultsDeep, isNumber } = require("lodash");
exports.canvastest = function (req, res) {
console.log("Incoming test request.", req);
res.status(200).send("OK");
};
exports.canvas = function (req, res) {
const { w, h, values, keys, override } = req.body;
console.log("Incoming Canvas Request:", w, h, values, keys, override);
// Gate required values
if (!values || !keys) {
res.status(400).send("Missing required data");
return;
}
// Override must be an object if it exists
if (override && !isObject(override)) {
res.status(400).send("Override must be an object");
return;
}
// Set the default Width and Height
let [width, height] = [500, 275];
// Allow for custom width and height
if (isNumber(w)) {
width = w;
}
if (isNumber(h)) {
height = h;
}
const configuration = {
type: "doughnut",
data: {
labels: keys,
datasets: [
{
data: values,
backgroundColor: backgroundColors,
borderColor: borderColors,
borderWidth: 1
}
]
},
options: {
devicePixelRatio: 4,
responsive: false,
maintainAspectRatio: true,
circumference: 180,
rotation: -90,
plugins: {
legend: {
labels: {
boxWidth: 20,
font: {
family: "'Montserrat'",
size: 10,
style: "normal",
weight: "normal"
}
},
position: "left"
}
}
}
};
// If we have a valid override object, merge it with the default configuration object.
// This allows for you to override the default configuration with a custom one.
const defaults = () => {
if (!override || !isObject(override)) {
return configuration;
}
return defaultsDeep(override, configuration);
};
res.status(200).send(
(() => {
const canvas = createCanvas(width, height);
const ctx = canvas.getContext("2d");
new Chart(ctx, defaults());
return canvas.toDataURL();
})()
);
};

View File

@@ -1,11 +1,13 @@
const express = require("express"); const express = require("express");
const router = express.Router(); const router = express.Router();
const cdkGetMake = require("../cdk/cdk-get-makes"); const cdkGetMake = require("../cdk/cdk-get-makes");
const cdkCalculateAllocations = require("../cdk/cdk-calculate-allocations");
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
router.use(validateFirebaseIdTokenMiddleware); router.use(validateFirebaseIdTokenMiddleware);
router.post("/getvehicles", withUserGraphQLClientMiddleware, cdkGetMake.default); router.post("/getvehicles", withUserGraphQLClientMiddleware, cdkGetMake.default);
router.post("/calculate-allocations", withUserGraphQLClientMiddleware, cdkCalculateAllocations.defaultRoute);
module.exports = router; module.exports = router;

View File

@@ -11,6 +11,7 @@ const eventAuthorizationMiddleware = require("../middleware/eventAuthorizationMI
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware"); const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
const { taskAssignedEmail, tasksRemindEmail } = require("../email/tasksEmails"); const { taskAssignedEmail, tasksRemindEmail } = require("../email/tasksEmails");
const { canvastest } = require("../render/canvas-handler");
//Test route to ensure Express is responding. //Test route to ensure Express is responding.
router.get("/test", async function (req, res) { router.get("/test", async function (req, res) {
@@ -49,4 +50,7 @@ router.post("/tasks-remind-handler", eventAuthorizationMiddleware, tasksRemindEm
router.post("/record-handler/arms", data.arms); router.post("/record-handler/arms", data.arms);
router.post("/taskHandler", validateFirebaseIdTokenMiddleware, taskHandler.taskHandler); router.post("/taskHandler", validateFirebaseIdTokenMiddleware, taskHandler.taskHandler);
// Canvas Test
router.post("/canvastest", validateFirebaseIdTokenMiddleware, canvastest);
module.exports = router; module.exports = router;

View File

@@ -2,8 +2,10 @@ const express = require("express");
const router = express.Router(); const router = express.Router();
const { inlinecss } = require("../render/inlinecss"); const { inlinecss } = require("../render/inlinecss");
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware"); const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
const { canvas } = require("../render/canvas-handler");
// Define the route for inline CSS rendering // Define the route for inline CSS rendering
router.post("/inlinecss", validateFirebaseIdTokenMiddleware, inlinecss); router.post("/inlinecss", validateFirebaseIdTokenMiddleware, inlinecss);
router.post("/canvas", validateFirebaseIdTokenMiddleware, canvas);
module.exports = router; module.exports = router;