Compare commits

...

137 Commits

Author SHA1 Message Date
Allan Carr
d06037df1f IO-2598 Restrict IOU from Tech Console 2024-01-19 08:47:17 -08:00
Dave Richer
e3aea55e91 Merged in feature/IO-1828-Beta-Updates-To-Test (pull request #1193)
Feature/IO-1828 Beta Updates To Test
2024-01-18 21:48:51 +00:00
Dave Richer
bf6b1c202f - update handleBeta
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-01-18 16:35:30 -05:00
Dave Richer
0cab47f984 - update handleBeta
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-01-18 16:22:58 -05:00
Dave Richer
829e611692 - update handleBeta
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-01-18 16:16:06 -05:00
Dave Richer
23c0f8e383 - update handleBeta
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-01-18 16:07:17 -05:00
Dave Richer
fcd9c19f0b Merged in feature/IO-1828-Beta-Switch-For-Test (pull request #1180)
Feature/IO-1828 Beta Switch For Test
2024-01-18 18:20:51 +00:00
Dave Richer
430823dde0 - remove source maps from prod
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-01-18 13:20:04 -05:00
Dave Richer
7e919a7221 Merge branch 'master' into feature/IO-1828-Beta-Switch-For-Test 2024-01-18 13:19:09 -05:00
Allan Carr
260607cb72 Merged in release/2024-01-12 (pull request #1161)
IO-2520 Adjust to imexshopid instead of shopname & prettify
2024-01-13 05:12:28 +00:00
Allan Carr
810738539b Merged in release/2024-01-12 (pull request #1160)
IO-2520 Adjust to imexshopid instead of shopname & prettify
2024-01-13 05:05:25 +00:00
Allan Carr
80539949fb Merged in feature/IO-2520-Kaizen-Data-Pump (pull request #1159)
IO-2520 Adjust to imexshopid instead of shopname & prettify
2024-01-13 05:04:54 +00:00
Allan Carr
ebe5c5b113 IO-2520 Adjust to imexshopid instead of shopname & prettify 2024-01-12 21:06:39 -08:00
Dave Richer
525182c2a7 Merged in feature/IO-1828-Beta-Switch-For-Test (pull request #1158)
- Add in the Beta Switch on test
2024-01-13 02:07:43 +00:00
Dave Richer
3704c0cb12 - Add in the Beta Switch on test
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-01-12 20:50:40 -05:00
Dave Richer
c8c844cfba Merged in feature/IO-1828-Beta-Switch-For-Test (pull request #1157)
- Add in the Beta Switch on test
2024-01-13 01:48:01 +00:00
Dave Richer
4c4e16b0c9 - Add in the Beta Switch on test
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-01-12 20:47:06 -05:00
Allan Carr
69f727c4e5 Merged in release/2024-01-12 (pull request #1156)
IO-2520 Add in Server Key format
2024-01-13 00:34:42 +00:00
Allan Carr
04cff4acb1 IO-2520 Add in Server Key format 2024-01-12 16:26:09 -08:00
Allan Carr
4e4fcc3ae4 Merged in release/2024-01-12 (pull request #1154)
IO-2520 Add in Server Key format
2024-01-13 00:25:24 +00:00
Allan Carr
485f9d6025 Merged in feature/IO-2520-Kaizen-Data-Pump (pull request #1153)
IO-2520 Add in Server Key format
2024-01-13 00:24:19 +00:00
Allan Carr
a697ade93a Merged in release/2024-01-12 (pull request #1152)
IO-2602 Beta domain
2024-01-13 00:02:58 +00:00
Allan Carr
7db07b5a94 Merged in release/2024-01-12 (pull request #1151)
IO-2602 Beta domain
2024-01-12 23:41:38 +00:00
Allan Carr
9ec50875a2 Merged in feature/IO-2602-BETA-domain (pull request #1150)
IO-2602 Beta domain
2024-01-12 23:39:22 +00:00
Allan Carr
02b6875eec IO-2602 Beta domain 2024-01-12 15:41:10 -08:00
Allan Carr
e7e4c534bc Merged in release/2024-01-12 (pull request #1149)
Release/2024 01 12
2024-01-12 19:59:15 +00:00
Allan Carr
e438fa1d99 Merged in release/2024-01-12 (pull request #1148)
Release/2024 01 12
2024-01-12 18:50:45 +00:00
Allan Carr
4abbf50a46 Merged in feature/IO-2520-Kaizen-Data-Pump (pull request #1145)
IO-2520 Kaizen Data Pump
2024-01-12 18:16:07 +00:00
Allan Carr
3e9279d89a IO-2520 Change Query Time Bound 2024-01-09 12:00:24 -08:00
Allan Carr
1305277c09 IO-2520 Kaizen Data Pump 2024-01-09 11:08:02 -08:00
Allan Carr
3c47c672d4 Merged in feature/IO-2518-Warning-on-VIN-Length (pull request #1143)
IO-2518 Null coalesce for v_vin for warning
2024-01-08 19:18:24 +00:00
Allan Carr
258d99cd41 Merged in release/2024-01-12 (pull request #1142)
IO-2518 Null coalesce for v_vin for warning
2024-01-08 18:41:15 +00:00
Allan Carr
83356fa4ef Merged in feature/IO-2518-Warning-on-VIN-Length (pull request #1141)
IO-2518 Null coalesce for v_vin for warning
2024-01-08 18:40:43 +00:00
Allan Carr
25429e78f8 IO-2518 Null coalesce for v_vin for warning 2024-01-08 10:37:19 -08:00
Allan Carr
de90bd1bb0 Merged in release/2024-01-05 (pull request #1137)
Release/2024 01 05
2024-01-05 22:43:56 +00:00
Allan Carr
aa6cb4c1d2 Merged in release/2024-01-05 (pull request #1136)
IO-2514 Only Unique items in Menu
2024-01-05 21:08:10 +00:00
Allan Carr
e871ba600f Merged in feature/IO-2514-Production-Board-Estimators (pull request #1135)
IO-2514 Only Unique items in Menu
2024-01-05 21:07:36 +00:00
Allan Carr
fe3698980d IO-2514 Only Unique items in Menu 2024-01-05 13:09:15 -08:00
Allan Carr
89b640f71c Merged in release/2024-01-05 (pull request #1133)
Release/2024 01 05
2024-01-05 20:04:10 +00:00
Allan Carr
d13a9cd04a Merged in feature/IO-2522-Load-Level-Table (pull request #1134)
IO-2522 Load Level Table Change
2024-01-05 20:02:26 +00:00
Allan Carr
c0dab92d0e IO-2522 Load Level Table Change 2024-01-05 12:01:47 -08:00
Patrick Fic
9c897972ad Minor intellipay change. 2024-01-05 08:53:17 -08:00
Allan Carr
307e244475 Merged in feature/IO-2514-Production-Board-Estimators (pull request #1130)
Feature/IO-2514 Production Board Estimators

Approved-by: Dave Richer
2024-01-05 00:57:44 +00:00
Allan Carr
9d3aca646b IO-2514 Production Board Estimator filter by table data 2024-01-03 10:55:45 -08:00
Allan Carr
e6e61466df Merged in feature/IO-2518-Warning-on-VIN-Length (pull request #1129)
IO-2518 Dealership Vin Warning

Approved-by: Dave Richer
2024-01-02 15:01:51 +00:00
Allan Carr
db7f9fe2ab Merged in feature/IO-2517-All-Courtesy-Car-Warning (pull request #1128)
IO-2517 All Courtesy Car Warning Indicator

Approved-by: Dave Richer
2024-01-02 15:01:22 +00:00
Allan Carr
ded798fdf1 IO-2518 Dealership Vin Warning 2023-12-29 16:37:34 -08:00
Allan Carr
bfe94e3068 IO-2517 Add same check within C/C form 2023-12-29 16:17:37 -08:00
Allan Carr
823f07409a IO-2517 All Courtesy Car Warning Indicator 2023-12-29 16:03:40 -08:00
Allan Carr
1a4bc720c2 Merged in release/2023-12-29 (pull request #1127)
Release/2023 12 29
2023-12-29 22:08:52 +00:00
Allan Carr
73cacdec24 Merged in release/2023-12-29 (pull request #1126)
Release/2023 12 29
2023-12-29 21:29:21 +00:00
Allan Carr
18998c4dbe Merged in feature/IO-2489-Regi-#-in-Vehicle-Box (pull request #1125)
IO-2489 Registration # in Vehicle Info Box

Approved-by: Dave Richer
2023-12-28 18:56:27 +00:00
Allan Carr
e236d6e912 IO-2489 Registration # in Vehicle Info Box 2023-12-28 10:57:03 -08:00
Allan Carr
523df670df Merged in feature/IO-2512-Date-Estimated-Manual-Job (pull request #1122)
IO-2512 Date Esimated on Manual Created Jobs

Approved-by: Dave Richer
2023-12-28 18:22:43 +00:00
Allan Carr
ce58181fc3 Merged in feature/IO-2500-Courtesy-Car-Readiness-&-Fuel-Level (pull request #1123)
IO-2500 Readiness and Fuel Level

Approved-by: Dave Richer
2023-12-28 18:21:35 +00:00
Allan Carr
bed0669f73 Merged in feature/IO-2513-Fuel-Level-Tooltip (pull request #1124)
IO-2500 Courtesy Car Readiness

Approved-by: Dave Richer
2023-12-28 18:19:16 +00:00
Allan Carr
b0d077e104 IO-2514 Production Board Estimators 2023-12-28 10:12:59 -08:00
Allan Carr
bdb2951330 IO-2500 Courtesy Car Readiness 2023-12-27 13:55:31 -08:00
Allan Carr
87a01208fb IO-2500 Readiness and Fuel Level 2023-12-27 13:35:29 -08:00
Allan Carr
2daee84fbf IO-2512 Date Esimated on Manual Created Jobs 2023-12-27 11:07:17 -08:00
Allan Carr
cdbf58f3ac Merged in feature/IO-2511-Bill-Label-Reprint (pull request #1121)
IO-2511 Bill Label Reprint

Approved-by: Dave Richer
2023-12-27 16:18:44 +00:00
Allan Carr
f6a59bdf55 IO-2511 Bill Label Reprint 2023-12-26 11:18:39 -08:00
Allan Carr
e12edd977e Merged in release/2023-12-15 (pull request #1120)
Release/2023 12 15

Approved-by: Dave Richer
2023-12-22 18:16:40 +00:00
Allan Carr
ea72d44b42 Merged in release/2023-12-15 (pull request #1119)
IO-2501 Correct for missing query variables
2023-12-22 04:32:06 +00:00
Allan Carr
b925c991eb Merged in feature/IO-2501-Add-Jobs-Completed-Delivered-Not-Invoiced-Section (pull request #1118)
IO-2501 Correct for missing query variables

Approved-by: Dave Richer
2023-12-22 02:33:30 +00:00
Allan Carr
1e7f43fe3d IO-2501 Correct for missing query variables 2023-12-21 18:32:37 -08:00
Allan Carr
d1b9b5546b Merged in release/2023-12-15 (pull request #1117)
IO-2505 Conversation List Print
2023-12-21 17:39:49 +00:00
Allan Carr
6f21de1901 Merged in feature/IO-2505-Conversation-List-Print (pull request #1116)
IO-2505 Conversation List Print

Approved-by: Dave Richer
2023-12-21 17:37:47 +00:00
Allan Carr
25e8eaa1d4 IO-2505 Conversation List Print 2023-12-21 09:24:25 -08:00
Allan Carr
84f0affaed Merged in release/2023-12-15 (pull request #1115)
Release/2023 12 15
2023-12-20 21:04:54 +00:00
Allan Carr
6fe736ce06 Merged in feature/IO-1366-Audit-Logging (pull request #1114)
IO-1366 Invoice Job Audit Trail

Approved-by: Dave Richer
2023-12-20 20:44:11 +00:00
Allan Carr
482b03c2d1 IO-1366 Invoice Job Audit Trail 2023-12-20 11:52:04 -08:00
Allan Carr
ce3c72fc47 Merged in feature/IO-1366-Audit-Logging (pull request #1113)
IO-1366 Audit Logging for Production Alert

Approved-by: Dave Richer
2023-12-20 19:38:46 +00:00
Allan Carr
1ff5ed4141 IO-1366 Audit Logging for Production Alert 2023-12-20 11:36:42 -08:00
Allan Carr
fc4b5c6b1d Merged in release/2023-12-15 (pull request #1112)
IO-2506 Correct for variable immutibility and nested ifs
2023-12-19 17:17:16 +00:00
Allan Carr
10e3421572 Merged in feature/IO-2506-Federal-Tax-Exempt-on-Bill-Entry (pull request #1111)
IO-2506 Correct for variable immutibility and nested ifs
2023-12-19 17:16:48 +00:00
Allan Carr
0117237988 IO-2506 Correct for variable immutibility and nested ifs 2023-12-19 09:18:00 -08:00
Allan Carr
2c232a71d5 Merged in release/2023-12-15 (pull request #1110)
Release/2023 12 15
2023-12-19 16:38:10 +00:00
Allan Carr
373fd817d0 Merged in feature/IO-2506-Federal-Tax-Exempt-on-Bill-Entry (pull request #1108)
IO-2506 Federal Tax Exempt on Bill Entry

Approved-by: Dave Richer
2023-12-19 16:34:38 +00:00
Allan Carr
0ef2d9646d Merged in feature/IO-2509-Report-Center-RBAC (pull request #1109)
IO-2509 Report Center RBAC

Approved-by: Dave Richer
2023-12-19 16:32:29 +00:00
Allan Carr
c8ac417200 IO-2509 Report Center RBAC 2023-12-18 14:12:21 -08:00
Allan Carr
661bedbe5b IO-2506 Federal Tax Exempt on Bill Entry
Will Toggle Federal Tax off on any new line or retroactively toggle it off on all lines when switch is enabled. Limited to PBS or CDK setups.
2023-12-18 12:36:46 -08:00
Allan Carr
2dd56590d3 Admin panel to force email addresses to be lowercase to conform with firebase 2023-12-14 08:57:36 -08:00
Allan Carr
98b760251c Merged in feature/IO-2501-Add-Jobs-Completed-Delivered-Not-Invoiced-Section (pull request #1107)
IO-2501 Add Jobs Complete Not Invoiced Section to Stats

Approved-by: Dave Richer
2023-12-13 18:47:04 +00:00
Allan Carr
b97de32a44 IO-2501 Add Jobs Complete Not Invoiced Section to Stats 2023-12-12 15:41:36 -08:00
Dave Richer
92c8b54f85 Merged in release/2023-12-01 (pull request #1103)
Reversion

Approved-by: Allan Carr
2023-12-04 16:50:13 +00:00
Dave Richer
d8420f472c Merged in feature/reversion-to-active-jobs-pagination (pull request #1102)
Reversion
2023-12-04 16:40:50 +00:00
Dave Richer
34d93c4de0 Reversion 2023-12-04 11:39:20 -05:00
Dave Richer
1c400cd456 Merged in release/2023-12-01 (pull request #1099)
Release/2023 12 01

Approved-by: Allan Carr
2023-12-01 18:18:23 +00:00
Allan Carr
f8e1758788 Merged in revert-pr-1097 (pull request #1098)
Revert "Revert "Revert "Revert "Fix issues with limits.
2023-12-01 02:37:45 +00:00
Allan Carr
5c95c72f40 Revert "Revert "Revert "Revert "Fix issues with limits. (pull request #1097)" 2023-12-01 02:37:10 +00:00
Allan Carr
98f816b069 Merged in revert-pr-1091 (pull request #1097)
Revert "Revert "Revert "Fix issues with limits.
2023-11-30 20:23:05 +00:00
Allan Carr
3ca6308dd2 Revert "Revert "Revert "Fix issues with limits. (pull request #1091)" 2023-11-30 20:22:42 +00:00
Allan Carr
a2c2aa11ac Merged in release/2023-12-01 (pull request #1096)
Release/2023 12 01
2023-11-30 16:58:33 +00:00
Allan Carr
a3cf97fcab Merged in feature/IO-2485-Fix-onlyFuture-Prop (pull request #1095)
IO-2485 Correct onlyFuture on typed values

Approved-by: Dave Richer
2023-11-30 16:56:57 +00:00
Allan Carr
1a9dc7a377 Merged in feature/IO-2484-Next-Service-KMs (pull request #1094)
IO-2484 Next Service KMs

Approved-by: Dave Richer
2023-11-30 16:55:55 +00:00
Allan Carr
806daebd3f IO-2485 Correct onlyFuture on typed values 2023-11-29 19:51:03 -08:00
Allan Carr
dfd8845864 IO-2484 Next Service KMs
Allow null and only display warning if not null and current milage is greater than service KMs
2023-11-29 17:32:09 -08:00
Allan Carr
b5b772d0c2 Merged in release/2023-12-01 (pull request #1093)
IO-2465 Adjust Headerfile override and comment out line
2023-11-30 01:16:39 +00:00
Allan Carr
170108b339 Merged in feature/IO-2465-Add-Vehicle-to-Override-Headers (pull request #1092)
IO-2465 Adjust Headerfile override and comment out line
2023-11-30 01:15:53 +00:00
Allan Carr
9f1f58a9c7 IO-2465 Adjust Headerfile override and comment out line 2023-11-29 17:14:39 -08:00
Allan Carr
4d8a2e635c Merged in revert-pr-1090 (pull request #1091)
Revert "Revert "Fix issues with limits.
2023-11-30 00:25:08 +00:00
Allan Carr
0852d55837 Revert "Revert "Fix issues with limits. (pull request #1090)" 2023-11-30 00:22:20 +00:00
Allan Carr
4c38ddf3cd Merged in revert-pr-1089 (pull request #1090)
Revert "Fix issues with limits.
2023-11-29 23:55:28 +00:00
Allan Carr
e15edeadb5 Revert "Fix issues with limits. (pull request #1089)" 2023-11-29 23:54:37 +00:00
Dave Richer
422c7baada Merged in release/2023-12-01 (pull request #1089)
Fix issues with limits.

Approved-by: Allan Carr
2023-11-29 22:30:12 +00:00
Dave Richer
4288e2d986 Merged in feature/IO-2403-Paginated-Active-Jobs (pull request #1088)
Fix issues with limits.
2023-11-29 22:27:49 +00:00
Dave Richer
4b289388bf Fix issues with limits. 2023-11-29 17:27:08 -05:00
Dave Richer
2a2f8e51b3 Merged in release/2023-12-01 (pull request #1087)
Release/2023 12 01

Approved-by: Allan Carr
2023-11-29 21:27:34 +00:00
Dave Richer
ed0136090c Merged in feature/IO-2403-Paginated-Active-Jobs (pull request #1086)
Fix order issue on all jobs
2023-11-29 21:10:56 +00:00
Dave Richer
0d70545b98 Fix order issue on all jobs 2023-11-29 16:10:30 -05:00
Dave Richer
2252091b53 Fix order issue on all jobs 2023-11-29 16:09:20 -05:00
Dave Richer
85b1875a22 Merged in release/2023-12-01 (pull request #1085)
Release/2023 12 01

Approved-by: Allan Carr
2023-11-29 18:59:37 +00:00
Dave Richer
386531884b Merged in feature/IO-2403-Paginated-Active-Jobs (pull request #1068)
Paginated Active Jobs / Pagination refactor
2023-11-29 18:43:00 +00:00
Dave Richer
afbe328aa7 Merged release/2023-12-01 into feature/IO-2403-Paginated-Active-Jobs 2023-11-29 18:40:47 +00:00
Allan Carr
b7c0fba48b Merged in feature/IO-2483-Translation-Update (pull request #1084)
IO-2483 Update Transations

Approved-by: Dave Richer
2023-11-29 18:40:01 +00:00
Allan Carr
68635b1629 Merged in feature/IO-2465-Add-Vehicle-to-Override-Headers (pull request #1083)
IO-2465 Restrict Update of Vehicle on Supplement to Override Header

Approved-by: Dave Richer
2023-11-29 18:39:42 +00:00
Allan Carr
3bd0058dc8 Merged in feature/IO-2481-Parts-Queue-Query (pull request #1082)
Feature/IO-2481 Parts Queue Query

Approved-by: Dave Richer
2023-11-29 18:39:24 +00:00
Allan Carr
270a512585 Merged in feature/IO-2480-Unqueue-Parts-Queue-Label (pull request #1081)
IO-2480 Unqueue Parts Label instead of Remove

Approved-by: Dave Richer
2023-11-29 18:38:49 +00:00
Dave Richer
ec2519eae4 Move PageSize (PageLimit) to an external configuration file. 2023-11-29 13:37:02 -05:00
Dave Richer
d7f52d864a Add Global search to Active Jobs. 2023-11-28 16:05:23 -05:00
Allan Carr
5cb2b3940e IO-2483 Update Transations 2023-11-28 10:08:33 -08:00
Dave Richer
b5efaa944d Merge branch 'master' into feature/IO-2403-Paginated-Active-Jobs 2023-11-28 12:55:00 -05:00
Allan Carr
bbcfc420d2 IO-2465 Restrict Update of Vehicle on Supplement to Override Header 2023-11-27 14:34:36 -08:00
Allan Carr
8ebf7baa71 IO-2481 Parts Queue Query
prettyier
2023-11-27 10:16:10 -08:00
Allan Carr
742d2b5ff2 IO-2481 Parts Queue Query
Adjust query to only show converted Jobs
2023-11-27 10:13:43 -08:00
Dave Richer
547b58f05a Merged in release/2023-11-24 (pull request #1079)
Update translations, move configuration toggle for autopartsqueue

Approved-by: Allan Carr
2023-11-24 17:52:32 +00:00
Dave Richer
432aa9f1e1 Progress 2023-11-24 12:34:17 -05:00
Dave Richer
c5bed4f36d Progress 2023-11-24 12:32:59 -05:00
Allan Carr
334306e3c9 Merged in release/2023-11-24 (pull request #1076)
IO-2438 Remove unneeded imports
2023-11-24 03:14:25 +00:00
Allan Carr
2a7606836c Merged in release/2023-11-24 (pull request #1074)
IO-2438 Adjust Start & End dates for timetickets
2023-11-24 03:11:22 +00:00
Allan Carr
79c966f9e4 Merged in release/2023-11-24 (pull request #1072)
Release/2023 11 24
2023-11-23 23:16:27 +00:00
Dave Richer
65f960db00 Add Margin around the Messaging Icon 2023-11-23 16:58:56 -05:00
Dave Richer
8670f386dc refactors 2023-11-22 17:14:52 -05:00
Allan Carr
54e673176c Merged in release/2023-11-17 (pull request #1059)
IO-2470-CC-Year-b-Make-UI
2023-11-17 19:09:14 +00:00
Dave Richer
189c60a6d1 Merged in release/2023-11-17 (pull request #1056)
IO-2331 Remove required CC fields Next Service KMS and Next Service Date

Approved-by: Patrick Fic
2023-11-15 22:15:30 +00:00
Dave Richer
a718f2a012 Merged in release/2023-11-17 (pull request #1054)
Release/2023 11 17

Approved-by: Patrick Fic
2023-11-15 21:42:36 +00:00
101 changed files with 2724 additions and 941 deletions

View File

@@ -22,6 +22,7 @@ import {
} from "../redux/user/user.selectors"; } from "../redux/user/user.selectors";
import PrivateRoute from "../utils/private-route"; import PrivateRoute from "../utils/private-route";
import "./App.styles.scss"; import "./App.styles.scss";
import handleBeta from "../utils/handleBeta";
const ResetPassword = lazy(() => const ResetPassword = lazy(() =>
import("../pages/reset-password/reset-password.component") import("../pages/reset-password/reset-password.component")
@@ -57,7 +58,6 @@ export function App({
if (!navigator.onLine) { if (!navigator.onLine) {
setOnline(false); setOnline(false);
} }
checkUserSession(); checkUserSession();
}, [checkUserSession, setOnline]); }, [checkUserSession, setOnline]);
@@ -73,6 +73,7 @@ export function App({
window.addEventListener("online", function (e) { window.addEventListener("online", function (e) {
setOnline(true); setOnline(true);
}); });
useEffect(() => { useEffect(() => {
if (currentUser.authorized && bodyshop) { if (currentUser.authorized && bodyshop) {
client.setAttribute("imexshopid", bodyshop.imexshopid); client.setAttribute("imexshopid", bodyshop.imexshopid);
@@ -107,6 +108,8 @@ export function App({
/> />
); );
handleBeta();
return ( return (
<Switch> <Switch>
<Suspense fallback={<LoadingSpinner message="ImEX Online" />}> <Suspense fallback={<LoadingSpinner message="ImEX Online" />}>

View File

@@ -15,6 +15,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component"; import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component"; import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -210,7 +211,7 @@ export function AccountingPayablesTableComponent({
<Table <Table
loading={loading} loading={loading}
dataSource={dataSource} dataSource={dataSource}
pagination={{ position: "top", pageSize: 50 }} pagination={{ position: "top", pageSize: pageLimit }}
columns={columns} columns={columns}
rowKey="id" rowKey="id"
onChange={handleTableChange} onChange={handleTableChange}

View File

@@ -15,6 +15,7 @@ import PaymentExportButton from "../payment-export-button/payment-export-button.
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component"; import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component"; import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component"; import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -209,7 +210,7 @@ export function AccountingPayablesTableComponent({
<Table <Table
loading={loading} loading={loading}
dataSource={dataSource} dataSource={dataSource}
pagination={{ position: "top", pageSize: 50 }} pagination={{ position: "top", pageSize: pageLimit }}
columns={columns} columns={columns}
rowKey="id" rowKey="id"
onChange={handleTableChange} onChange={handleTableChange}

View File

@@ -4,6 +4,7 @@ import { alphaSort } from "../../utils/sorters";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import AuditTrailValuesComponent from "../audit-trail-values/audit-trail-values.component"; import AuditTrailValuesComponent from "../audit-trail-values/audit-trail-values.component";
import {pageLimit} from "../../utils/config";
export default function AuditTrailListComponent({ loading, data }) { export default function AuditTrailListComponent({ loading, data }) {
const [state, setState] = useState({ const [state, setState] = useState({
@@ -74,7 +75,7 @@ export default function AuditTrailListComponent({ loading, data }) {
<Table <Table
{...formItemLayout} {...formItemLayout}
loading={loading} loading={loading}
pagination={{ position: "top", defaultPageSize: 25 }} pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns} columns={columns}
rowKey="id" rowKey="id"
dataSource={data} dataSource={data}

View File

@@ -3,6 +3,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import {pageLimit} from "../../utils/config";
export default function EmailAuditTrailListComponent({ loading, data }) { export default function EmailAuditTrailListComponent({ loading, data }) {
const [state, setState] = useState({ const [state, setState] = useState({
@@ -53,7 +54,7 @@ export default function EmailAuditTrailListComponent({ loading, data }) {
<Table <Table
{...formItemLayout} {...formItemLayout}
loading={loading} loading={loading}
pagination={{ position: "top", defaultPageSize: 25 }} pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns} columns={columns}
rowKey="id" rowKey="id"
dataSource={data} dataSource={data}

View File

@@ -10,7 +10,7 @@ import { createStructuredSelector } from "reselect";
import { import {
DELETE_BILL_LINE, DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES, INSERT_NEW_BILL_LINES,
UPDATE_BILL_LINE UPDATE_BILL_LINE,
} from "../../graphql/bill-lines.queries"; } from "../../graphql/bill-lines.queries";
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries"; import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
@@ -20,6 +20,7 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import BillFormContainer from "../bill-form/bill-form.container"; import BillFormContainer from "../bill-form/bill-form.container";
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component"; import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
import BillPrintButton from "../bill-print-button/bill-print-button.component";
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component"; import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container"; import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container"; import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
@@ -176,7 +177,7 @@ export function BillDetailEditcontainer({
extra={ extra={
<Space> <Space>
<BillDetailEditReturn data={data} /> <BillDetailEditReturn data={data} />
<BillPrintButton billid={search.billid} />
<Popconfirm <Popconfirm
visible={visible} visible={visible}
onConfirm={() => form.submit()} onConfirm={() => form.submit()}

View File

@@ -79,6 +79,20 @@ export function BillFormComponent({
}); });
}; };
const handleFederalTaxExemptSwitchToggle = (checked) => {
// Early gate
if (!checked) return;
const values = form.getFieldsValue("billlines");
// Gate bill lines
if (!values?.billlines?.length) return;
const billlines = values.billlines.map((b) => {
b.applicable_taxes.federal = false;
return b;
});
form.setFieldsValue({ billlines });
};
useEffect(() => { useEffect(() => {
if (job) form.validateFields(["is_credit_memo"]); if (job) form.validateFields(["is_credit_memo"]);
}, [job, form]); }, [job, form]);
@@ -387,7 +401,16 @@ export function BillFormComponent({
> >
<CurrencyInput min={0} /> <CurrencyInput min={0} />
</Form.Item> </Form.Item>
<Form.Item shouldUpdate span={15}> {bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
<Form.Item
span={2}
label={t("bills.labels.federal_tax_exempt")}
name="federal_tax_exempt"
>
<Switch onChange={handleFederalTaxExemptSwitchToggle} />
</Form.Item>
) : null}
<Form.Item shouldUpdate span={13}>
{() => { {() => {
const values = form.getFieldsValue([ const values = form.getFieldsValue([
"billlines", "billlines",
@@ -405,7 +428,7 @@ export function BillFormComponent({
totals = CalculateBillTotal(values); totals = CalculateBillTotal(values);
if (!!totals) if (!!totals)
return ( return (
<div> <div align="right">
<Space wrap> <Space wrap>
<Statistic <Statistic
title={t("bills.labels.subtotal")} title={t("bills.labels.subtotal")}

View File

@@ -1,14 +1,15 @@
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons"; import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useTreatments } from "@splitsoftware/splitio-react";
import { import {
Button, Form, Button,
Form,
Input, Input,
InputNumber, InputNumber,
Select, Select,
Space, Space,
Switch, Switch,
Table, Table,
Tooltip Tooltip,
} from "antd"; } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -466,7 +467,8 @@ export function BillEnterModalLinesComponent({
return { return {
key: `${field.index}fedtax`, key: `${field.index}fedtax`,
valuePropName: "checked", valuePropName: "checked",
initialValue: true, initialValue:
form.getFieldValue("federal_tax_exempt") === true ? false : true,
name: [field.name, "applicable_taxes", "federal"], name: [field.name, "applicable_taxes", "federal"],
}; };
}, },

View File

@@ -0,0 +1,38 @@
import { Button, Space } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
export default function BillPrintButton({ billid }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const Templates = TemplateList("job_special");
const submitHandler = async () => {
setLoading(true);
try {
await GenerateDocument(
{
name: Templates.parts_invoice_label_single.key,
variables: {
id: billid,
},
},
{},
"p"
);
} catch (e) {
console.warn("Warning: Error generating a document.");
}
setLoading(false);
};
return (
<Space wrap>
<Button loading={loading} onClick={submitHandler}>
{t("bills.labels.printlabels")}
</Button>
</Space>
);
}

View File

@@ -4,6 +4,7 @@ import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component"; import ChatArchiveButton from "../chat-archive-button/chat-archive-button.component";
import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component"; import ChatConversationTitleTags from "../chat-conversation-title-tags/chat-conversation-title-tags.component";
import ChatLabelComponent from "../chat-label/chat-label.component"; import ChatLabelComponent from "../chat-label/chat-label.component";
import ChatPrintButton from "../chat-print-button/chat-print-button.component";
import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container"; import ChatTagRoContainer from "../chat-tag-ro/chat-tag-ro.container";
export default function ChatConversationTitle({ conversation }) { export default function ChatConversationTitle({ conversation }) {
@@ -13,6 +14,7 @@ export default function ChatConversationTitle({ conversation }) {
{conversation && conversation.phone_num} {conversation && conversation.phone_num}
</PhoneNumberFormatter> </PhoneNumberFormatter>
<ChatLabelComponent conversation={conversation} /> <ChatLabelComponent conversation={conversation} />
<ChatPrintButton conversation={conversation} />
<ChatConversationTitleTags <ChatConversationTitleTags
jobConversations={ jobConversations={
(conversation && conversation.job_conversations) || [] (conversation && conversation.job_conversations) || []

View File

@@ -132,7 +132,7 @@ export function ChatPopupComponent({
onClick={() => toggleChatVisible()} onClick={() => toggleChatVisible()}
style={{ cursor: "pointer" }} style={{ cursor: "pointer" }}
> >
<MessageOutlined /> <MessageOutlined className="chat-popup-info-icon" />
<strong>{t("messaging.labels.messaging")}</strong> <strong>{t("messaging.labels.messaging")}</strong>
</div> </div>
)} )}

View File

@@ -13,6 +13,9 @@
height: 100%; height: 100%;
} }
} }
.chat-popup-info-icon {
margin-right: 5px;
}
@media only screen and (min-width: 992px) { @media only screen and (min-width: 992px) {
.chat-popup { .chat-popup {

View File

@@ -0,0 +1,59 @@
import { MailOutlined, PrinterOutlined } from "@ant-design/icons";
import { Space, Spin } from "antd";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setEmailOptions } from "../../redux/email/email.actions";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setEmailOptions: (e) => dispatch(setEmailOptions(e)),
});
export function ChatPrintButton({ conversation }) {
const [loading, setLoading] = useState(false);
return (
<Space wrap>
<PrinterOutlined
onClick={() => {
setLoading(true);
GenerateDocument(
{
name: TemplateList("messaging").conversation_list.key,
variables: { id: conversation.id },
},
{
subject: TemplateList("messaging").conversation_list.subject,
},
"p",
conversation.id
);
setLoading(false);
}}
/>
<MailOutlined
onClick={() => {
setLoading(true);
GenerateDocument(
{
name: TemplateList("messaging").conversation_list.key,
variables: { id: conversation.id },
},
{
subject: TemplateList("messaging").conversation_list.subject,
},
"e",
conversation.id
);
setLoading(false);
}}
/>
{loading && <Spin />}
</Space>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ChatPrintButton);

View File

@@ -35,6 +35,15 @@ export default function ContractsCarsComponent({
state.sortedInfo.columnKey === "status" && state.sortedInfo.order, state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
render: (text, record) => <div>{t(record.status)}</div>, render: (text, record) => <div>{t(record.status)}</div>,
}, },
{
title: t("courtesycars.fields.readiness"),
dataIndex: "readiness",
key: "readiness",
sorter: (a, b) => alphaSort(a.readiness, b.readiness),
sortOrder:
state.sortedInfo.columnKey === "readiness" && state.sortedInfo.order,
render: (text, record) => t(record.readiness),
},
{ {
title: t("courtesycars.fields.year"), title: t("courtesycars.fields.year"),
dataIndex: "year", dataIndex: "year",

View File

@@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import {pageLimit} from "../../utils/config";
export default function ContractsJobsComponent({ export default function ContractsJobsComponent({
loading, loading,
@@ -175,7 +176,7 @@ export default function ContractsJobsComponent({
loading={loading} loading={loading}
pagination={{ pagination={{
position: "top", position: "top",
defaultPageSize: 10, defaultPageSize: pageLimit,
defaultCurrent: defaultCurrent, defaultCurrent: defaultCurrent,
}} }}
columns={columns} columns={columns}

View File

@@ -13,6 +13,7 @@ import moment from "moment";
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 {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -209,7 +210,7 @@ export function ContractsList({
}} }}
pagination={{ pagination={{
position: "top", position: "top",
pageSize: 25, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(page || 1),
total: total, total: total,
}} }}

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import {pageLimit} from "../../utils/config";
export default function CourtesyCarContractListComponent({ export default function CourtesyCarContractListComponent({
contracts, contracts,
@@ -89,7 +90,7 @@ export default function CourtesyCarContractListComponent({
scroll={{ x: true }} scroll={{ x: true }}
pagination={{ pagination={{
position: "top", position: "top",
pageSize: 25, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(page || 1),
total: totalContracts, total: totalContracts,
}} }}

View File

@@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
import { CHECK_CC_FLEET_NUMBER } from "../../graphql/courtesy-car.queries"; import { CHECK_CC_FLEET_NUMBER } from "../../graphql/courtesy-car.queries";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component"; import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
import CourtesyCarReadiness from "../courtesy-car-readiness-select/courtesy-car-readiness-select.component";
import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component"; import CourtesyCarStatus from "../courtesy-car-status-select/courtesy-car-status-select.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
//import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component"; //import FormFieldsChanged from "../form-fields-changed-alert/form-fields-changed-alert.component";
@@ -213,6 +214,9 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
> >
<CourtesyCarStatus /> <CourtesyCarStatus />
</Form.Item> </Form.Item>
<Form.Item label={t("courtesycars.fields.readiness")} name="readiness">
<CourtesyCarReadiness />
</Form.Item>
<div> <div>
<Form.Item <Form.Item
label={t("courtesycars.fields.nextservicekm")} label={t("courtesycars.fields.nextservicekm")}
@@ -227,9 +231,9 @@ export default function CourtesyCarCreateFormComponent({ form, saveLoading }) {
> >
{() => { {() => {
const nextservicekm = form.getFieldValue("nextservicekm"); const nextservicekm = form.getFieldValue("nextservicekm");
const mileageOver = const mileageOver = nextservicekm
nextservicekm <= form.getFieldValue("mileage"); ? nextservicekm <= form.getFieldValue("mileage")
: false;
if (mileageOver) if (mileageOver)
return ( return (
<Space direction="vertical" style={{ color: "tomato" }}> <Space direction="vertical" style={{ color: "tomato" }}>

View File

@@ -34,6 +34,32 @@ const CourtesyCarFuelComponent = (props, ref) => {
step={null} step={null}
style={{ marginLeft: "2rem", marginRight: "2rem" }} style={{ marginLeft: "2rem", marginRight: "2rem" }}
{...props} {...props}
tooltip={{
formatter: (value) => {
switch (value) {
case 0:
return t("courtesycars.labels.fuel.empty");
case 13:
return t("courtesycars.labels.fuel.18");
case 25:
return t("courtesycars.labels.fuel.14");
case 38:
return t("courtesycars.labels.fuel.38");
case 50:
return t("courtesycars.labels.fuel.12");
case 63:
return t("courtesycars.labels.fuel.58");
case 75:
return t("courtesycars.labels.fuel.34");
case 88:
return t("courtesycars.labels.fuel.78");
case 100:
return t("courtesycars.labels.fuel.full");
default:
return value;
}
},
}}
/> />
); );
}; };

View File

@@ -0,0 +1,35 @@
import { Select } from "antd";
import React, { forwardRef, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
const { Option } = Select;
const CourtesyCarReadinessComponent = ({ value, onChange }, ref) => {
const [option, setOption] = useState(value);
const { t } = useTranslation();
useEffect(() => {
if (value !== option && onChange) {
onChange(option);
}
}, [value, option, onChange]);
return (
<Select
allowClear
ref={ref}
value={option}
style={{
width: 100,
}}
onChange={setOption}
>
<Option value="courtesycars.readiness.ready">
{t("courtesycars.readiness.ready")}
</Option>
<Option value="courtesycars.readiness.notready">
{t("courtesycars.readiness.notready")}
</Option>
</Select>
);
};
export default forwardRef(CourtesyCarReadinessComponent);

View File

@@ -74,10 +74,11 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
render: (text, record) => { render: (text, record) => {
const { nextservicedate, nextservicekm, mileage } = record; const { nextservicedate, nextservicekm, mileage } = record;
const mileageOver = nextservicekm <= mileage; const mileageOver = nextservicekm ? nextservicekm <= mileage : false;
const dueForService = const dueForService =
nextservicedate && moment(nextservicedate).isBefore(moment()); nextservicedate &&
moment(nextservicedate).endOf("day").isSameOrBefore(moment());
return ( return (
<Space> <Space>
@@ -91,6 +92,26 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
); );
}, },
}, },
{
title: t("courtesycars.fields.readiness"),
dataIndex: "readiness",
key: "readiness",
sorter: (a, b) => alphaSort(a.readiness, b.readiness),
filters: [
{
text: t("courtesycars.readiness.ready"),
value: "courtesycars.readiness.ready",
},
{
text: t("courtesycars.readiness.notready"),
value: "courtesycars.readiness.notready",
},
],
onFilter: (value, record) => value.includes(record.readiness),
sortOrder:
state.sortedInfo.columnKey === "readiness" && state.sortedInfo.order,
render: (text, record) => t(record.readiness),
},
{ {
title: t("courtesycars.fields.year"), title: t("courtesycars.fields.year"),
dataIndex: "year", dataIndex: "year",
@@ -131,6 +152,36 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
sortOrder: sortOrder:
state.sortedInfo.columnKey === "plate" && state.sortedInfo.order, state.sortedInfo.columnKey === "plate" && state.sortedInfo.order,
}, },
{
title: t("courtesycars.fields.fuel"),
dataIndex: "fuel",
key: "fuel",
sorter: (a, b) => alphaSort(a.fuel, b.fuel),
sortOrder:
state.sortedInfo.columnKey === "fuel" && state.sortedInfo.order,
render: (text, record) => {
switch (record.fuel) {
case 100:
return t("courtesycars.labels.fuel.full");
case 88:
return t("courtesycars.labels.fuel.78");
case 63:
return t("courtesycars.labels.fuel.58");
case 50:
return t("courtesycars.labels.fuel.12");
case 38:
return t("courtesycars.labels.fuel.34");
case 25:
return t("courtesycars.labels.fuel.14");
case 13:
return t("courtesycars.labels.fuel.18");
case 0:
return t("courtesycars.labels.fuel.empty");
default:
return record.fuel;
}
},
},
{ {
title: t("courtesycars.labels.outwith"), title: t("courtesycars.labels.outwith"),
dataIndex: "outwith", dataIndex: "outwith",

View File

@@ -7,6 +7,7 @@ import { Link, useHistory, useLocation } from "react-router-dom";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import {pageLimit} from "../../utils/config";
export default function CsiResponseListPaginated({ export default function CsiResponseListPaginated({
refetch, refetch,
@@ -106,7 +107,7 @@ export default function CsiResponseListPaginated({
loading={loading} loading={loading}
pagination={{ pagination={{
position: "top", position: "top",
pageSize: 25, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(page || 1),
total: total, total: total,
}} }}

View File

@@ -6,6 +6,7 @@ import { alphaSort } from "../../../utils/sorters";
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component"; import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import DashboardRefreshRequired from "../refresh-required.component"; import DashboardRefreshRequired from "../refresh-required.component";
import {pageLimit} from "../../../utils/config";
export default function DashboardMonthlyJobCosting({ data, ...cardProps }) { export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -118,7 +119,7 @@ export default function DashboardMonthlyJobCosting({ data, ...cardProps }) {
<div style={{ height: "100%" }}> <div style={{ height: "100%" }}>
<Table <Table
onChange={handleTableChange} onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: 50 }} pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns} columns={columns}
scroll={{ x: true, y: "calc(100% - 4em)" }} scroll={{ x: true, y: "calc(100% - 4em)" }}
rowKey="id" rowKey="id"

View File

@@ -11,6 +11,7 @@ import { Link } from "react-router-dom";
import ChatOpenButton from "../../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
import DashboardRefreshRequired from "../refresh-required.component"; import DashboardRefreshRequired from "../refresh-required.component";
import {pageLimit} from "../../../utils/config";
export default function DashboardScheduledInToday({ data, ...cardProps }) { export default function DashboardScheduledInToday({ data, ...cardProps }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -195,7 +196,7 @@ export default function DashboardScheduledInToday({ data, ...cardProps }) {
<div style={{ height: "100%" }}> <div style={{ height: "100%" }}>
<Table <Table
onChange={handleTableChange} onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: 50 }} pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns} columns={columns}
scroll={{ x: true, y: "calc(100% - 2em)" }} scroll={{ x: true, y: "calc(100% - 2em)" }}
rowKey="id" rowKey="id"

View File

@@ -11,6 +11,7 @@ import { Link } from "react-router-dom";
import ChatOpenButton from "../../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../../owner-name-display/owner-name-display.component";
import DashboardRefreshRequired from "../refresh-required.component"; import DashboardRefreshRequired from "../refresh-required.component";
import {pageLimit} from "../../../utils/config";
export default function DashboardScheduledOutToday({ data, ...cardProps }) { export default function DashboardScheduledOutToday({ data, ...cardProps }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -165,7 +166,7 @@ export default function DashboardScheduledOutToday({ data, ...cardProps }) {
<div style={{ height: "100%" }}> <div style={{ height: "100%" }}>
<Table <Table
onChange={handleTableChange} onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: 50 }} pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns} columns={columns}
scroll={{ x: true, y: "calc(100% - 2em)" }} scroll={{ x: true, y: "calc(100% - 2em)" }}
rowKey="id" rowKey="id"

View File

@@ -5,6 +5,7 @@ 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 {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -117,7 +118,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
} }
> >
<Table <Table
pagination={{ position: "top", defaultPageSize: 50 }} pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns} columns={columns}
rowKey={(record) => `${record.InvoiceNumber}${record.Account}`} rowKey={(record) => `${record.InvoiceNumber}${record.Account}`}
dataSource={allocationsSummary} dataSource={allocationsSummary}

View File

@@ -6,6 +6,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import { SyncOutlined } from "@ant-design/icons"; import { SyncOutlined } from "@ant-design/icons";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -94,7 +95,7 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
<Alert type="warning" message={t("jobs.labels.dms.disablebillwip")} /> <Alert type="warning" message={t("jobs.labels.dms.disablebillwip")} />
)} )}
<Table <Table
pagination={{ position: "top", defaultPageSize: 50 }} pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns} columns={columns}
rowKey="center" rowKey="center"
dataSource={allocationsSummary} dataSource={allocationsSummary}

View File

@@ -65,8 +65,17 @@ export function FormDatePicker({
}); });
} }
if (_a.isValid() && onChange) if (_a.isValid() && onChange) {
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a); if (onlyFuture) {
if (moment().subtract(1, "day").isBefore(_a)) {
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
} else {
onChange(isDateOnly ? moment().format("YYYY-MM-DD") : moment());
}
} else {
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
}
}
}; };
return ( return (

View File

@@ -1,9 +1,9 @@
import React, { forwardRef } from "react"; import React, { forwardRef } from "react";
//import DatePicker from "react-datepicker"; //import DatePicker from "react-datepicker";
//import "react-datepicker/src/stylesheets/datepicker.scss"; //import "react-datepicker/src/stylesheets/datepicker.scss";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import { TimePicker } from "antd"; import { TimePicker } from "antd";
import moment from "moment"; import moment from "moment";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
//To be used as a form element only. //To be used as a form element only.
const DateTimePicker = ( const DateTimePicker = (
@@ -26,20 +26,21 @@ const DateTimePicker = (
value={value} value={value}
onBlur={onBlur} onBlur={onBlur}
onChange={onChange} onChange={onChange}
onlyFuture={onlyFuture}
isDateOnly={false} isDateOnly={false}
/> />
<TimePicker <TimePicker
value={value ? moment(value) : null} value={value ? moment(value) : null}
{...(onlyFuture && { {...(onlyFuture && {
disabledDate: (d) => moment().isAfter(d), disabledDate: (d) => moment().isAfter(d),
})} })}
onChange={onChange} onChange={onChange}
showSecond={false} showSecond={false}
minuteStep={15} minuteStep={15}
onBlur={onBlur} onBlur={onBlur}
format="hh:mm a" format="hh:mm a"
{...restProps} {...restProps}
/> />
</div> </div>
); );

View File

@@ -13,7 +13,7 @@ import Icon, {
FileFilled, FileFilled,
//GlobalOutlined, //GlobalOutlined,
HomeFilled, HomeFilled,
ImportOutlined, ImportOutlined, InfoCircleOutlined,
LineChartOutlined, LineChartOutlined,
PaperClipOutlined, PaperClipOutlined,
PhoneOutlined, PhoneOutlined,
@@ -26,8 +26,8 @@ import Icon, {
UserOutlined, UserOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react"; import { useTreatments } from "@splitsoftware/splitio-react";
import { Layout, Menu } from "antd"; import {Layout, Menu, Switch, Tooltip} from "antd";
import React from "react"; 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 { import {
@@ -52,6 +52,7 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import {handleBeta, setBeta, checkBeta} from "../../utils/handleBeta";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
@@ -102,9 +103,21 @@ function Header({
{}, {},
bodyshop && bodyshop.imexshopid bodyshop && bodyshop.imexshopid
); );
const [betaSwitch, setBetaSwitch] = useState(false);
const { t } = useTranslation(); const { t } = useTranslation();
useEffect(() => {
const isBeta = checkBeta();
setBetaSwitch(isBeta);
}, []);
const betaSwitchChange = (checked) => {
setBeta(checked);
setBetaSwitch(checked);
handleBeta();
}
return ( return (
<Layout.Header> <Layout.Header>
<Menu <Menu
@@ -431,6 +444,17 @@ function Header({
</Menu.Item> </Menu.Item>
))} ))}
</Menu.SubMenu> </Menu.SubMenu>
<Menu.Item style={{marginLeft: 'auto'}} key="profile">
<Tooltip title="A more modern ImEX Online is ready for you to try! You can switch back at any time.">
<InfoCircleOutlined/>
<span style={{marginRight: 8}}>Try the new ImEX Online</span>
<Switch
checked={betaSwitch}
onChange={betaSwitchChange}
/>
</Tooltip>
</Menu.Item>
</Menu> </Menu>
</Layout.Header> </Layout.Header>
); );

View File

@@ -11,6 +11,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import InventoryBillRo from "../inventory-bill-ro/inventory-bill-ro.component"; import InventoryBillRo from "../inventory-bill-ro/inventory-bill-ro.component";
import InventoryLineDelete from "../inventory-line-delete/inventory-line-delete.component"; import InventoryLineDelete from "../inventory-line-delete/inventory-line-delete.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -213,7 +214,7 @@ export function JobsList({
loading={loading} loading={loading}
pagination={{ pagination={{
position: "top", position: "top",
pageSize: 25, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(page || 1),
total: total, total: total,
}} }}

View File

@@ -11,6 +11,7 @@ import {
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import InventoryListPaginated from "./inventory-list.component"; import InventoryListPaginated from "./inventory-list.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//bodyshop: selectBodyshop, //bodyshop: selectBodyshop,
@@ -32,8 +33,8 @@ export function InventoryList({ setBreadcrumbs, setSelectedHeader }) {
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
search: search || "", search: search || "",
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * pageLimit : 0,
limit: 25, limit: pageLimit,
consumedIsNull: showall === "true" ? null : true, consumedIsNull: showall === "true" ? null : true,
order: [ order: [
{ {

View File

@@ -3,6 +3,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import {pageLimit} from "../../utils/config";
export default function JobCostingPartsTable({ data, summaryData }) { export default function JobCostingPartsTable({ data, summaryData }) {
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
const [state, setState] = useState({ const [state, setState] = useState({
@@ -98,7 +99,7 @@ export default function JobCostingPartsTable({ data, summaryData }) {
x: "50%", //y: "40rem" x: "50%", //y: "40rem"
}} }}
onChange={handleTableChange} onChange={handleTableChange}
pagination={{ position: "top", defaultPageSize: 50 }} pagination={{ position: "top", defaultPageSize: pageLimit }}
columns={columns} columns={columns}
rowKey="id" rowKey="id"
dataSource={filteredData} dataSource={filteredData}

View File

@@ -7,21 +7,31 @@ import { connect } from "react-redux";
import { useHistory } from "react-router"; import { useHistory } from "react-router";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { UPDATE_JOB_LINES_IOU } from "../../graphql/jobs-lines.queries"; import { UPDATE_JOB_LINES_IOU } from "../../graphql/jobs-lines.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import { CreateIouForJob } from "../jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util"; import { CreateIouForJob } from "../jobs-detail-header-actions/jobs-detail-header-actions.duplicate.util";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
technician: selectTechnician,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) //setUserLanguage: language => dispatch(setUserLanguage(language))
}); });
export default connect(mapStateToProps, mapDispatchToProps)(JobCreateIOU); export default connect(mapStateToProps, mapDispatchToProps)(JobCreateIOU);
export function JobCreateIOU({ bodyshop, currentUser, job, selectedJobLines }) { export function JobCreateIOU({
bodyshop,
currentUser,
job,
selectedJobLines,
technician,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const client = useApolloClient(); const client = useApolloClient();
@@ -79,13 +89,19 @@ export function JobCreateIOU({ bodyshop, currentUser, job, selectedJobLines }) {
title={t("jobs.labels.createiouwarning")} title={t("jobs.labels.createiouwarning")}
onConfirm={handleCreateIou} onConfirm={handleCreateIou}
disabled={ disabled={
!selectedJobLines || selectedJobLines.length === 0 || !job.converted !selectedJobLines ||
selectedJobLines.length === 0 ||
!job.converted ||
technician
} }
> >
<Button <Button
loading={loading} loading={loading}
disabled={ disabled={
!selectedJobLines || selectedJobLines.length === 0 || !job.converted !selectedJobLines ||
selectedJobLines.length === 0 ||
!job.converted ||
technician
} }
> >
{t("jobs.actions.createiou")} {t("jobs.actions.createiou")}

View File

@@ -1,6 +1,6 @@
import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries";
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
import _ from "lodash"; import _ from "lodash";
import { GET_ALL_JOBLINES_BY_PK } from "../../graphql/jobs-lines.queries";
export const GetSupplementDelta = async (client, jobId, newLines) => { export const GetSupplementDelta = async (client, jobId, newLines) => {
const { const {
@@ -50,7 +50,7 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
.reduce((acc, value, idx) => { .reduce((acc, value, idx) => {
return acc + generateRemoveQuery(value, idx); return acc + generateRemoveQuery(value, idx);
}, ""); }, "");
console.log(insertQueries, updateQueries, removeQueries); //console.log(insertQueries, updateQueries, removeQueries);
if ((insertQueries + updateQueries + removeQueries).trim() === "") { if ((insertQueries + updateQueries + removeQueries).trim() === "") {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View File

@@ -114,7 +114,7 @@ const headerFields = [
"ins_ct_ph", "ins_ct_ph",
"ins_ct_phx", "ins_ct_phx",
"loss_cat", "loss_cat",
//ad2 //AD2
"clmt_ln", "clmt_ln",
"clmt_fn", "clmt_fn",
"clmt_title", "clmt_title",
@@ -219,7 +219,16 @@ const headerFields = [
"loc_title", "loc_title",
"loc_ph", "loc_ph",
"loc_phx", "loc_phx",
"loc_ea" "loc_ea",
//VEH
"plate_no",
"plate_st",
"v_vin",
"v_model_yr",
"v_make_desc",
"v_model_desc",
"v_options",
"v_color",
]; ];
export default headerFields; export default headerFields;

View File

@@ -108,6 +108,14 @@ export function JobsDetailHeaderActions({
}, },
}, },
}); });
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.alertToggle(
!!job.production_vars && !!job.production_vars.alert
? !job.production_vars.alert
: true
),
});
}; };
const handleSuspend = (e) => { const handleSuspend = (e) => {

View File

@@ -1,8 +1,8 @@
import { import {
BranchesOutlined,
ExclamationCircleFilled, ExclamationCircleFilled,
PauseCircleOutlined, PauseCircleOutlined,
WarningFilled, WarningFilled,
BranchesOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { Card, Col, Row, Space, Tag, Tooltip } from "antd"; import { Card, Col, Row, Space, Tag, Tooltip } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
@@ -221,6 +221,14 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<VehicleVinDisplay> <VehicleVinDisplay>
{`${job.v_vin || t("general.labels.na")}`} {`${job.v_vin || t("general.labels.na")}`}
</VehicleVinDisplay> </VehicleVinDisplay>
{bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid ? (
job.v_vin?.length !== 17 ? (
<WarningFilled style={{ color: "tomato", marginLeft: ".3rem" }} />
) : null
) : null}
</DataLabel>
<DataLabel label={t("jobs.fields.regie_number")}>
{job.regie_number || t("general.labels.na")}
</DataLabel> </DataLabel>
<DataLabel label={t("jobs.labels.relatedros")}> <DataLabel label={t("jobs.labels.relatedros")}>
<JobsRelatedRos jobid={job.id} job={job} /> <JobsRelatedRos jobid={job.id} job={job} />

View File

@@ -12,6 +12,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import StartChatButton from "../chat-open-button/chat-open-button.component"; import StartChatButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -33,17 +34,14 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number), sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder, sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) => ( render: (text, record) => (
<Link to={"/manage/jobs/" + record.id}> <Link to={"/manage/jobs/" + record.id}>
{record.ro_number || t("general.labels.na")} {record.ro_number || t("general.labels.na")}
</Link> </Link>
), ),
}, },
{ {
title: t("jobs.fields.owner"), title: t("jobs.fields.owner"),
dataIndex: "ownr_ln", dataIndex: "ownr_ln",
@@ -125,7 +123,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("vehicles.fields.plate_no"), title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no", dataIndex: "plate_no",
key: "plate_no", key: "plate_no",
ellipsis: true, ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no), sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder: sortcolumn === "plate_no" && sortorder, sortOrder: sortcolumn === "plate_no" && sortorder,
@@ -137,7 +134,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.clm_no"), title: t("jobs.fields.clm_no"),
dataIndex: "clm_no", dataIndex: "clm_no",
key: "clm_no", key: "clm_no",
ellipsis: true, ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no), sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder: sortcolumn === "clm_no" && sortorder, sortOrder: sortcolumn === "clm_no" && sortorder,
@@ -259,11 +255,11 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
pagination={ pagination={
search?.search search?.search
? { ? {
pageSize: 25, pageSize: pageLimit,
showSizeChanger: false, showSizeChanger: false,
} }
: { : {
pageSize: 25, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(page || 1),
total: total, total: total,
showSizeChanger: false, showSizeChanger: false,

View File

@@ -1,8 +1,8 @@
import { import {
SyncOutlined, SyncOutlined,
ExclamationCircleFilled, ExclamationCircleFilled,
PauseCircleOutlined, PauseCircleOutlined,
BranchesOutlined, BranchesOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd"; import { Button, Card, Grid, Input, Space, Table, Tooltip } from "antd";
@@ -22,374 +22,374 @@ 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";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
export function JobsList({ bodyshop }) { export function JobsList({ bodyshop }) {
const searchParams = queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
const { selected } = searchParams; const { selected } = searchParams;
const selectedBreakpoint = Object.entries(Grid.useBreakpoint()) const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1]) .filter((screen) => !!screen[1])
.slice(-1)[0]; .slice(-1)[0];
const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, { const { loading, error, data, refetch } = useQuery(QUERY_ALL_ACTIVE_JOBS, {
variables: { variables: {
statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"], statuses: bodyshop.md_ro_statuses.active_statuses || ["Open", "Open*"],
}, },
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
}); });
const [state, setState] = useState({ const [state, setState] = useState({
sortedInfo: {}, sortedInfo: {},
filteredInfo: { text: "" }, filteredInfo: { text: "" },
}); });
const { t } = useTranslation(); const { t } = useTranslation();
const history = useHistory(); const history = useHistory();
const [searchText, setSearchText] = useState(""); const [searchText, setSearchText] = useState("");
if (error) return <AlertComponent message={error.message} type="error" />; if (error) return <AlertComponent message={error.message} type="error" />;
const jobs = data const jobs = data
? searchText === "" ? searchText === ""
? data.jobs ? data.jobs
: data.jobs.filter( : data.jobs.filter(
(j) => (j) =>
(j.ro_number || "") (j.ro_number || "")
.toString() .toString()
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
(j.ownr_co_nm || "") (j.ownr_co_nm || "")
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
(j.comments || "") (j.comments || "")
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
(j.ownr_fn || "") (j.ownr_fn || "")
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
(j.ownr_ln || "") (j.ownr_ln || "")
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
(j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) || (j.clm_no || "").toLowerCase().includes(searchText.toLowerCase()) ||
(j.plate_no || "") (j.plate_no || "")
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
(j.v_model_desc || "") (j.v_model_desc || "")
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
(j.est_ct_fn || "") (j.est_ct_fn || "")
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
(j.est_ct_ln || "") (j.est_ct_ln || "")
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) || .includes(searchText.toLowerCase()) ||
(j.v_make_desc || "") (j.v_make_desc || "")
.toLowerCase() .toLowerCase()
.includes(searchText.toLowerCase()) .includes(searchText.toLowerCase())
) )
: []; : [];
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter }); setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
}; };
const handleOnRowClick = (record) => { const handleOnRowClick = (record) => {
if (record) { if (record) {
if (record.id) { if (record.id) {
history.push({ history.push({
search: queryString.stringify({ search: queryString.stringify({
...searchParams, ...searchParams,
selected: record.id, selected: record.id,
}), }),
}); });
} }
} }
}; };
const columns = [ const columns = [
{ {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
sorter: (a, b) => sorter: (a, b) =>
parseInt((a.ro_number || "0").replace(/\D/g, "")) - parseInt((a.ro_number || "0").replace(/\D/g, "")) -
parseInt((b.ro_number || "0").replace(/\D/g, "")), parseInt((b.ro_number || "0").replace(/\D/g, "")),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => ( render: (text, record) => (
<Link <Link
to={"/manage/jobs/" + record.id} to={"/manage/jobs/" + record.id}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<Space> <Space>
{record.ro_number || t("general.labels.na")} {record.ro_number || t("general.labels.na")}
{record.production_vars && record.production_vars.alert ? ( {record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" /> <ExclamationCircleFilled className="production-alert" />
) : null} ) : null}
{record.suspended && ( {record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} /> <PauseCircleOutlined style={{ color: "orangered" }} />
)} )}
{record.iouparent && ( {record.iouparent && (
<Tooltip title={t("jobs.labels.iou")}> <Tooltip title={t("jobs.labels.iou")}>
<BranchesOutlined style={{ color: "orangered" }} /> <BranchesOutlined style={{ color: "orangered" }} />
</Tooltip> </Tooltip>
)} )}
</Space> </Space>
</Link> </Link>
), ),
}, },
{ {
title: t("jobs.fields.owner"), title: t("jobs.fields.owner"),
dataIndex: "owner", dataIndex: "owner",
key: "owner", key: "owner",
ellipsis: true, ellipsis: true,
responsive: ["md"], responsive: ["md"],
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "owner" && state.sortedInfo.order, state.sortedInfo.columnKey === "owner" && state.sortedInfo.order,
render: (text, record) => { render: (text, record) => {
return record.ownerid ? ( return record.ownerid ? (
<Link <Link
to={"/manage/owners/" + record.ownerid} to={"/manage/owners/" + record.ownerid}
onClick={(e) => e.stopPropagation()} onClick={(e) => e.stopPropagation()}
> >
<OwnerNameDisplay ownerObject={record} /> <OwnerNameDisplay ownerObject={record} />
</Link> </Link>
) : ( ) : (
<span> <span>
<OwnerNameDisplay ownerObject={record} /> <OwnerNameDisplay ownerObject={record} />
</span> </span>
); );
},
},
{
title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
),
},
{
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
ellipsis: true,
sorter: (a, b) => alphaSort(a.status, b.status),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filters:
(jobs &&
jobs
.map((j) => j.status)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.status),
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
<Link
to={"/manage/vehicles/" + record.vehicleid}
onClick={(e) => e.stopPropagation()}
>
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</Link>
) : (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
);
},
},
{
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder:
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
},
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
render: (text, record) =>
`${record.clm_no || ""}${
record.po_number ? ` (PO: ${record.po_number})` : ""
}`,
},
{
title: t("jobs.fields.ins_co_nm"),
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true,
filters:
(jobs &&
jobs
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s,
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"],
},
{
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
responsive: ["md"],
ellipsis: true,
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
},
{
title: t("jobs.labels.estimator"),
dataIndex: "jobs.labels.estimator",
key: "jobs.labels.estimator",
ellipsis: true,
responsive: ["xl"],
filterSearch: true,
filters:
(jobs &&
jobs
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
.filter(onlyUnique)
.map((s) => {
return {
text: s || "N/A",
value: [s],
};
})) ||
[],
onFilter: (value, record) =>
value.includes(
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
),
render: (text, record) =>
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
responsive: ["md"],
},
// {
// title: t("jobs.fields.owner_owing"),
// dataIndex: "owner_owing",
// key: "owner_owing",
// responsive: ["md"],
// render: (text, record) => (
// <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
// ),
// },
];
const scrollMapper = {
xs: true,
sm: true,
md: true,
lg: "100%",
xl: "100%",
xxl: "100%",
};
return (
<Card
title={t("titles.bc.jobs-active")}
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.target.value);
}}
value={searchText}
enterButton
/>
</Space>
}
>
<Table
loading={loading}
pagination={{ defaultPageSize: 50 }}
columns={columns}
rowKey="id"
dataSource={jobs}
scroll={{
x: selectedBreakpoint ? scrollMapper[selectedBreakpoint[0]] : "100%",
}}
rowSelection={{
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selected],
type: "radio",
}}
onChange={handleTableChange}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
}, },
}; },
}} {
/> title: t("jobs.fields.ownr_ph1"),
</Card> dataIndex: "ownr_ph1",
); key: "ownr_ph1",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
),
},
{
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
ellipsis: true,
responsive: ["md"],
render: (text, record) => (
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
ellipsis: true,
sorter: (a, b) => alphaSort(a.status, b.status),
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
filters:
(jobs &&
jobs
.map((j) => j.status)
.filter(onlyUnique)
.map((s) => {
return {
text: s || "No Status*",
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.status),
},
{
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
<Link
to={"/manage/vehicles/" + record.vehicleid}
onClick={(e) => e.stopPropagation()}
>
{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}
</Link>
) : (
<span>{`${record.v_model_yr || ""} ${record.v_make_desc || ""} ${
record.v_model_desc || ""
}`}</span>
);
},
},
{
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder:
state.sortedInfo.columnKey === "plate_no" && state.sortedInfo.order,
},
{
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
ellipsis: true,
responsive: ["md"],
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
render: (text, record) =>
`${record.clm_no || ""}${
record.po_number ? ` (PO: ${record.po_number})` : ""
}`,
},
{
title: t("jobs.fields.ins_co_nm"),
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true,
filters:
(jobs &&
jobs
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s,
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"],
},
{
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
responsive: ["md"],
ellipsis: true,
sorter: (a, b) => a.clm_total - b.clm_total,
sortOrder:
state.sortedInfo.columnKey === "clm_total" && state.sortedInfo.order,
render: (text, record) => (
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
},
{
title: t("jobs.labels.estimator"),
dataIndex: "jobs.labels.estimator",
key: "jobs.labels.estimator",
ellipsis: true,
responsive: ["xl"],
filterSearch: true,
filters:
(jobs &&
jobs
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
.filter(onlyUnique)
.map((s) => {
return {
text: s || "N/A",
value: [s],
};
})) ||
[],
onFilter: (value, record) =>
value.includes(
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
),
render: (text, record) =>
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
responsive: ["md"],
},
// {
// title: t("jobs.fields.owner_owing"),
// dataIndex: "owner_owing",
// key: "owner_owing",
// responsive: ["md"],
// render: (text, record) => (
// <CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
// ),
// },
];
const scrollMapper = {
xs: true,
sm: true,
md: true,
lg: "100%",
xl: "100%",
xxl: "100%",
};
return (
<Card
title={t("titles.bc.jobs-active")}
extra={
<Space wrap>
<Button onClick={() => refetch()}>
<SyncOutlined />
</Button>
<Input.Search
placeholder={t("general.labels.search")}
onChange={(e) => {
setSearchText(e.target.value);
}}
value={searchText}
enterButton
/>
</Space>
}
>
<Table
loading={loading}
pagination={{ defaultPageSize: 50 }}
columns={columns}
rowKey="id"
dataSource={jobs}
scroll={{
x: selectedBreakpoint ? scrollMapper[selectedBreakpoint[0]] : "100%",
}}
rowSelection={{
onSelect: (record) => {
handleOnRowClick(record);
},
selectedRowKeys: [selected],
type: "radio",
}}
onChange={handleTableChange}
onRow={(record, rowIndex) => {
return {
onClick: (event) => {
handleOnRowClick(record);
},
};
}}
/>
</Card>
);
} }
export default connect(mapStateToProps, null)(JobsList); export default connect(mapStateToProps, null)(JobsList);

View File

@@ -20,6 +20,7 @@ import { alphaSort } from "../../utils/sorters";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -377,7 +378,7 @@ export function JobsReadyList({ bodyshop }) {
> >
<Table <Table
loading={loading} loading={loading}
pagination={{ defaultPageSize: 50 }} pagination={{ defaultPageSize: pageLimit }}
columns={columns} columns={columns}
rowKey="id" rowKey="id"
dataSource={jobs} dataSource={jobs}

View File

@@ -6,6 +6,7 @@ import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
import PhoneFormatter from "../../utils/PhoneFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import {pageLimit} from "../../utils/config";
export default function OwnersListComponent({ export default function OwnersListComponent({
loading, loading,
@@ -122,7 +123,7 @@ export default function OwnersListComponent({
loading={loading} loading={loading}
pagination={{ pagination={{
position: "top", position: "top",
pageSize: 25, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(page || 1),
total: total, total: total,
}} }}

View File

@@ -5,6 +5,7 @@ import AlertComponent from "../alert/alert.component";
import OwnersListComponent from "./owners-list.component"; import OwnersListComponent from "./owners-list.component";
import queryString from "query-string"; import queryString from "query-string";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import {pageLimit} from "../../utils/config";
export default function OwnersListContainer() { export default function OwnersListContainer() {
const searchParams = queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
@@ -16,8 +17,8 @@ export default function OwnersListContainer() {
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
search: search || "", search: search || "",
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * pageLimit : 0,
limit: 25, limit: pageLimit,
order: [ order: [
{ {
[sortcolumn || "created_at"]: sortorder [sortcolumn || "created_at"]: sortorder

View File

@@ -18,6 +18,7 @@ import { alphaSort } from "../../utils/sorters";
import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container"; import CaBcEtfTableModalContainer from "../ca-bc-etf-table-modal/ca-bc-etf-table-modal.container";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component"; import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -282,11 +283,11 @@ export function PaymentsListPaginated({
pagination={ pagination={
search?.search search?.search
? { ? {
pageSize: 25, pageSize: pageLimit,
showSizeChanger: false, showSizeChanger: false,
} }
: { : {
pageSize: 25, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(page || 1),
total: total, total: total,
showSizeChanger: false, showSizeChanger: false,

View File

@@ -1,7 +1,7 @@
import React from "react";
import { Button, Dropdown, Menu } from "antd"; import { Button, Dropdown, Menu } from "antd";
import dataSource from "./production-list-columns.data"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import dataSource from "./production-list-columns.data";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -24,6 +24,7 @@ export function ProductionColumnsComponent({
columnState, columnState,
technician, technician,
bodyshop, bodyshop,
data,
tableState, tableState,
}) { }) {
const [columns, setColumns] = columnState; const [columns, setColumns] = columnState;
@@ -36,6 +37,7 @@ export function ProductionColumnsComponent({
bodyshop, bodyshop,
technician, technician,
state: tableState, state: tableState,
data: data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).filter((i) => i.key === e.key), }).filter((i) => i.key === e.key),
]); ]);
@@ -46,6 +48,7 @@ export function ProductionColumnsComponent({
technician, technician,
state: tableState, state: tableState,
activeStatuses: bodyshop.md_ro_statuses.active_statuses, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
data: data,
}); });
const menu = ( const menu = (
<Menu <Menu

View File

@@ -1,12 +1,23 @@
import { ExclamationCircleFilled } from "@ant-design/icons"; import { ExclamationCircleFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Dropdown, Menu } from "antd"; import { Dropdown, Menu } from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useMutation } from "@apollo/client"; import { connect } from "react-redux";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
export default function ProductionListColumnAlert({ record }) { const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function ProductionListColumnAlert({ record, insertAuditTrail }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updateAlert] = useMutation(UPDATE_JOB); const [updateAlert] = useMutation(UPDATE_JOB);
@@ -27,6 +38,14 @@ export default function ProductionListColumnAlert({ record }) {
}, },
}, },
}, },
});
insertAuditTrail({
jobid: record.id,
operation: AuditTrailMapping.alertToggle(
!!record.production_vars && !!record.production_vars.alert
? !record.production_vars.alert
: true
),
}).then(() => { }).then(() => {
if (record.refetch) record.refetch(); if (record.refetch) record.refetch();
}); });
@@ -58,3 +77,8 @@ export default function ProductionListColumnAlert({ record }) {
</Dropdown> </Dropdown>
); );
} }
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListColumnAlert);

View File

@@ -1,4 +1,4 @@
import { PauseCircleOutlined, BranchesOutlined } from "@ant-design/icons"; import { BranchesOutlined, PauseCircleOutlined } from "@ant-design/icons";
import { Space, Tooltip } from "antd"; import { Space, Tooltip } from "antd";
import i18n from "i18next"; import i18n from "i18next";
import moment from "moment"; import moment from "moment";
@@ -6,6 +6,7 @@ import { Link } from "react-router-dom";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { TimeFormatter } from "../../utils/DateFormatter"; import { TimeFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter"; import PhoneFormatter from "../../utils/PhoneFormatter";
import { onlyUnique } from "../../utils/arrayHelper";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters"; import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import JobAltTransportChange from "../job-at-change/job-at-change.component"; import JobAltTransportChange from "../job-at-change/job-at-change.component";
import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component"; import JobPartsQueueCount from "../job-parts-queue-count/job-parts-queue-count.component";
@@ -25,7 +26,7 @@ import ProductionListColumnCategory from "./production-list-columns.status.categ
import ProductionListColumnStatus from "./production-list-columns.status.component"; import ProductionListColumnStatus from "./production-list-columns.status.component";
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component"; import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
const r = ({ technician, state, activeStatuses, bodyshop }) => { const r = ({ technician, state, activeStatuses, data, bodyshop }) => {
return [ return [
{ {
title: i18n.t("jobs.actions.viewdetail"), title: i18n.t("jobs.actions.viewdetail"),
@@ -536,6 +537,36 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
<JobPartsQueueCount parts={record.joblines_status} record={record} /> <JobPartsQueueCount parts={record.joblines_status} record={record} />
), ),
}, },
{
title: i18n.t("jobs.labels.estimator"),
dataIndex: "estimator",
key: "estimator",
sorter: (a, b) =>
alphaSort(
`${a.est_ct_fn || ""} ${a.est_ct_ln || ""}`.trim(),
`${b.est_ct_fn || ""} ${b.est_ct_ln || ""}`.trim()
),
sortOrder:
state.sortedInfo.columnKey === "estimator" && state.sortedInfo.order,
filters:
(data &&
data
.map((j) => `${j.est_ct_fn || ""} ${j.est_ct_ln || ""}`.trim())
.filter(onlyUnique)
.map((s) => {
return {
text: s || "N/A",
value: [s],
};
})) ||
[],
onFilter: (value, record) =>
value.includes(
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim()
),
render: (text, record) =>
`${record.est_ct_fn || ""} ${record.est_ct_ln || ""}`.trim(),
},
//Added as a place holder for St Claude. Not implemented as it requires another join for a field used by only 1 client. //Added as a place holder for St Claude. Not implemented as it requires another join for a field used by only 1 client.
// { // {

View File

@@ -24,6 +24,7 @@ export function ProductionListTable({
technician, technician,
currentUser, currentUser,
state, state,
data,
setColumns, setColumns,
setState, setState,
}) { }) {
@@ -41,6 +42,7 @@ export function ProductionListTable({
bodyshop, bodyshop,
technician, technician,
state, state,
data: data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).find((e) => e.key === k.key), }).find((e) => e.key === k.key),
width: k.width, width: k.width,
@@ -95,6 +97,7 @@ export function ProductionListTable({
...ProductionListColumns({ ...ProductionListColumns({
technician, technician,
state, state,
data: data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).find((e) => e.key === k.key), }).find((e) => e.key === k.key),
width: k.width, width: k.width,

View File

@@ -10,7 +10,7 @@ import {
Statistic, Statistic,
Table, Table,
} from "antd"; } from "antd";
import React, { useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import ReactDragListView from "react-drag-listview"; import ReactDragListView from "react-drag-listview";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -79,6 +79,7 @@ export function ProductionListTable({
bodyshop, bodyshop,
technician, technician,
state, state,
data: data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses, activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).find((e) => e.key === k.key), }).find((e) => e.key === k.key),
width: k.width ?? 100, width: k.width ?? 100,
@@ -87,6 +88,33 @@ export function ProductionListTable({
[] []
); );
useEffect(() => {
const newColumns =
(state &&
matchingColumnConfig &&
matchingColumnConfig.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
technician,
state,
data: data,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
}).find((e) => e.key === k.key),
width: k.width ?? 100,
};
})) ||
[];
setColumns(newColumns);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [
//state,
matchingColumnConfig,
bodyshop,
technician,
data,
]); //State removed from dependency array as it causes race condition when removing columns from table view and is not needed.
const handleTableChange = (pagination, filters, sorter) => { const handleTableChange = (pagination, filters, sorter) => {
setState({ setState({
...state, ...state,
@@ -104,7 +132,8 @@ export function ProductionListTable({
const removeColumn = (e) => { const removeColumn = (e) => {
const { key } = e; const { key } = e;
setColumns(columns.filter((i) => i.key !== key)); const newColumns = columns.filter((i) => i.key !== key);
setColumns(newColumns);
}; };
const handleResize = const handleResize =
@@ -227,6 +256,7 @@ export function ProductionListTable({
<ProductionListColumnsAdd <ProductionListColumnsAdd
columnState={[columns, setColumns]} columnState={[columns, setColumns]}
tableState={state} tableState={state}
data={data}
/> />
<ProductionListSaveConfigButton <ProductionListSaveConfigButton
columns={columns} columns={columns}
@@ -237,6 +267,7 @@ export function ProductionListTable({
state={state} state={state}
setState={setState} setState={setState}
setColumns={setColumns} setColumns={setColumns}
data={data}
/> />
<Input <Input

View File

@@ -55,10 +55,11 @@ const ret = {
"shiftclock:view": 2, "shiftclock:view": 2,
"shop:config": 4, "shop:config": 4,
"shop:rbac": 5,
"shop:vendors": 2,
"shop:dashboard": 3, "shop:dashboard": 3,
"shop:rbac": 5,
"shop:reportcenter": 2,
"shop:templates": 4, "shop:templates": 4,
"shop:vendors": 2,
"temporarydocs:view": 2, "temporarydocs:view": 2,

View File

@@ -5,6 +5,7 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectReportCenter } from "../../redux/modals/modals.selectors"; import { selectReportCenter } from "../../redux/modals/modals.selectors";
import RbacWrapperComponent from "../rbac-wrapper/rbac-wrapper.component";
import ReportCenterModalComponent from "./report-center-modal.component"; import ReportCenterModalComponent from "./report-center-modal.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -33,7 +34,9 @@ export function ReportCenterModalContainer({
destroyOnClose destroyOnClose
width="80%" width="80%"
> >
<ReportCenterModalComponent /> <RbacWrapperComponent action="shop:reportcenter">
<ReportCenterModalComponent />
</RbacWrapperComponent>
</Modal> </Modal>
); );
} }

View File

@@ -10,13 +10,14 @@ import OwnerNameDisplay from "../owner-name-display/owner-name-display.component
import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component"; import ScoreboardEntryEdit from "../scoreboard-entry-edit/scoreboard-entry-edit.component";
import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component"; import ScoreboardRemoveButton from "../scoreboard-remove-button/scorebard-remove-button.component";
import { SyncOutlined } from "@ant-design/icons"; import { SyncOutlined } from "@ant-design/icons";
import {pageLimit} from "../../utils/config";
export default function ScoreboardJobsList({ scoreBoardlist }) { export default function ScoreboardJobsList({ scoreBoardlist }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [state, setState] = useState({ const [state, setState] = useState({
visible: false, visible: false,
search: "", search: "",
current: 1, current: 1,
pageSize: 10, pageSize: pageLimit,
}); });
const { loading, error, data, refetch } = useQuery( const { loading, error, data, refetch } = useQuery(
@@ -148,7 +149,7 @@ export default function ScoreboardJobsList({ scoreBoardlist }) {
} }
pagination={{ pagination={{
position: "top", position: "top",
pageSize: state.pageSize || 10, pageSize: state.pageSize || pageLimit,
current: state.current || 1, current: state.current || 1,
total: data ? data.scoreboard_aggregate.aggregate.count : 0, total: data ? data.scoreboard_aggregate.aggregate.count : 0,
}} }}

View File

@@ -29,7 +29,7 @@ export default connect(
export function ScoreboardTimeTicketsStats({ bodyshop }) { export function ScoreboardTimeTicketsStats({ bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const startDate = moment().startOf("month") const startDate = moment().startOf("month");
const endDate = moment().endOf("month"); const endDate = moment().endOf("month");
const fixedPeriods = useMemo(() => { const fixedPeriods = useMemo(() => {
@@ -84,6 +84,8 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
end: endDate.format("YYYY-MM-DD"), end: endDate.format("YYYY-MM-DD"),
fixedStart: fixedPeriods.start.format("YYYY-MM-DD"), fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"), fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
jobStart: startDate,
jobEnd: endDate,
}, },
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
@@ -340,11 +342,21 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
larData.push({ ...r, ...lar }); larData.push({ ...r, ...lar });
}); });
const jobData = {};
data.jobs.forEach((job) => {
job.tthrs = job.joblines.reduce((acc, val) => acc + val.mod_lb_hrs, 0);
});
jobData.tthrs = data.jobs
.reduce((acc, val) => acc + val.tthrs, 0)
.toFixed(1);
jobData.count = data.jobs.length.toFixed(0);
return { return {
fixed: ret, fixed: ret,
combinedData: combinedData, combinedData: combinedData,
labData: labData, labData: labData,
larData: larData, larData: larData,
jobData: jobData,
}; };
}, [fixedPeriods, data, bodyshop]); }, [fixedPeriods, data, bodyshop]);
@@ -356,7 +368,10 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
<ScoreboardTimeticketsTargetsTable /> <ScoreboardTimeticketsTargetsTable />
</Col> </Col>
<Col span={24}> <Col span={24}>
<ScoreboardTicketsStats data={calculatedData.fixed} /> <ScoreboardTicketsStats
data={calculatedData.fixed}
jobData={calculatedData.jobData}
/>
</Col> </Col>
<Col span={24}> <Col span={24}>
<ScoreboardTimeTicketsChart <ScoreboardTimeTicketsChart

View File

@@ -41,7 +41,7 @@ function useLocalStorage(key, initialValue) {
return [storedValue, setStoredValue]; return [storedValue, setStoredValue];
} }
export function ScoreboardTicketsStats({ data, bodyshop }) { export function ScoreboardTicketsStats({ data, jobData, bodyshop }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [isLarge, setIsLarge] = useLocalStorage("isLargeStatistic", false); const [isLarge, setIsLarge] = useLocalStorage("isLargeStatistic", false);
@@ -408,7 +408,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
{/* Monthly Stats */} {/* Monthly Stats */}
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
{/* This Month */} {/* This Month */}
<Col span={8} align="center"> <Col span={7} align="center">
<Card size="small" title={t("scoreboard.labels.thismonth")}> <Card size="small" title={t("scoreboard.labels.thismonth")}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
@@ -482,7 +482,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
</Card> </Card>
</Col> </Col>
{/* Last Month */} {/* Last Month */}
<Col span={8} align="center"> <Col span={7} align="center">
<Card size="small" title={t("scoreboard.labels.lastmonth")}> <Card size="small" title={t("scoreboard.labels.lastmonth")}>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
@@ -556,7 +556,7 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
</Card> </Card>
</Col> </Col>
{/* Efficiency Over Period */} {/* Efficiency Over Period */}
<Col span={8} align="center"> <Col span={7} align="center">
<Card <Card
size="small" size="small"
title={t("scoreboard.labels.efficiencyoverperiod")} title={t("scoreboard.labels.efficiencyoverperiod")}
@@ -604,6 +604,40 @@ export function ScoreboardTicketsStats({ data, bodyshop }) {
</Row> </Row>
</Card> </Card>
</Col> </Col>
<Col span={3} align="center">
<Card
size="small"
title={t("scoreboard.labels.jobscompletednotinvoiced")}
>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
value={jobData.count}
valueStyle={{
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
<Row gutter={[16, 16]}>
<Col span={24}>
<Statistic
title={
<Typography.Text strong>
{t("scoreboard.labels.totalhrs")}
</Typography.Text>
}
value={jobData.tthrs}
valueStyle={{
fontSize: statisticSize,
fontWeight: statisticWeight,
}}
/>
</Col>
</Row>
</Card>
</Col>
</Row> </Row>
</Space> </Space>
{/* Disclaimer */} {/* Disclaimer */}

View File

@@ -65,6 +65,8 @@ export default function ScoreboardTimeTickets() {
end: endDate.format("YYYY-MM-DD"), end: endDate.format("YYYY-MM-DD"),
fixedStart: fixedPeriods.start.format("YYYY-MM-DD"), fixedStart: fixedPeriods.start.format("YYYY-MM-DD"),
fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"), fixedEnd: fixedPeriods.end.format("YYYY-MM-DD"),
jobStart: startDate,
jobEnd: endDate,
}, },
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",

View File

@@ -28,18 +28,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
return ( return (
<RbacWrapper action="shop:rbac"> <RbacWrapper action="shop:rbac">
<LayoutFormRow> <LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payables")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "accounting:payables"]}
>
<InputNumber />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.accounting.exportlog")} label={t("bodyshop.fields.rbac.accounting.exportlog")}
rules={[ rules={[
@@ -52,6 +40,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.accounting.payables")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "accounting:payables"]}
>
<InputNumber />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.accounting.payments")} label={t("bodyshop.fields.rbac.accounting.payments")}
rules={[ rules={[
@@ -77,26 +77,62 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.csi.page")} label={t("bodyshop.fields.rbac.bills.delete")}
rules={[ rules={[
{ {
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
]} ]}
name={["md_rbac", "csi:page"]} name={["md_rbac", "bills:delete"]}
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.csi.export")} label={t("bodyshop.fields.rbac.bills.enter")}
rules={[ rules={[
{ {
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
]} ]}
name={["md_rbac", "csi:export"]} name={["md_rbac", "bills:enter"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.reexport")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:reexport"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.view")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:view"]}
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
@@ -173,26 +209,38 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.jobs.list-active")} label={t("bodyshop.fields.rbac.csi.export")}
rules={[ rules={[
{ {
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
]} ]}
name={["md_rbac", "jobs:list-active"]} name={["md_rbac", "csi:export"]}
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.jobs.list-ready")} label={t("bodyshop.fields.rbac.csi.page")}
rules={[ rules={[
{ {
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
]} ]}
name={["md_rbac", "jobs:list-ready"]} name={["md_rbac", "csi:page"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.employees.page")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "employees:page"]}
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
@@ -208,30 +256,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.partsqueue")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:partsqueue"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.list-all")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:list-all"]}
>
<InputNumber />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.jobs.available-list")} label={t("bodyshop.fields.rbac.jobs.available-list")}
rules={[ rules={[
@@ -245,26 +269,14 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.jobs.create")} label={t("bodyshop.fields.rbac.jobs.checklist-view")}
rules={[ rules={[
{ {
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
]} ]}
name={["md_rbac", "jobs:create"]} name={["md_rbac", "jobs:checklist-view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.intake")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:intake"]}
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
@@ -280,6 +292,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.create")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:create"]}
>
<InputNumber />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.jobs.deliver")} label={t("bodyshop.fields.rbac.jobs.deliver")}
rules={[ rules={[
@@ -305,14 +329,62 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.jobs.checklist-view")} label={t("bodyshop.fields.rbac.jobs.intake")}
rules={[ rules={[
{ {
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
]} ]}
name={["md_rbac", "jobs:checklist-view"]} name={["md_rbac", "jobs:intake"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.list-active")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:list-active"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.list-all")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:list-all"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.list-ready")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:list-ready"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.jobs.partsqueue")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "jobs:partsqueue"]}
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
@@ -329,74 +401,14 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.bills.enter")} label={t("bodyshop.fields.rbac.owners.detail")}
rules={[ rules={[
{ {
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
]} ]}
name={["md_rbac", "bills:enter"]} name={["md_rbac", "owners:detail"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.delete")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:delete"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.reexport")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:reexport"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.view")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.bills.list")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "bills:list"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.employees.page")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "employees:page"]}
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
@@ -412,18 +424,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.owners.detail")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "owners:detail"]}
>
<InputNumber />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.payments.enter")} label={t("bodyshop.fields.rbac.payments.enter")}
rules={[ rules={[
@@ -448,6 +448,30 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.phonebook.edit")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "phonebook:edit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.phonebook.view")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "phonebook:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.production.board")} label={t("bodyshop.fields.rbac.production.board")}
rules={[ rules={[
@@ -509,38 +533,14 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.timetickets.edit")} label={t("bodyshop.fields.rbac.shop.config")}
rules={[ rules={[
{ {
required: true, required: true,
//message: t("general.validation.required"), //message: t("general.validation.required"),
}, },
]} ]}
name={["md_rbac", "timetickets:edit"]} name={["md_rbac", "shop:config"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.shiftedit")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "timetickets:shiftedit"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.vendors")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "shop:vendors"]}
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
@@ -556,18 +556,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.config")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "shop:config"]}
>
<InputNumber />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.shop.rbac")} label={t("bodyshop.fields.rbac.shop.rbac")}
rules={[ rules={[
@@ -580,6 +568,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.reportcenter")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "shop:reportcenter"]}
>
<InputNumber />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.shop.templates")} label={t("bodyshop.fields.rbac.shop.templates")}
rules={[ rules={[
@@ -592,6 +592,42 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.shop.vendors")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "shop:vendors"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.temporarydocs.view")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "temporarydocs:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.timetickets.edit")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "timetickets:edit"]}
>
<InputNumber />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.fields.rbac.timetickets.enter")} label={t("bodyshop.fields.rbac.timetickets.enter")}
rules={[ rules={[
@@ -616,6 +652,18 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </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={[
@@ -628,42 +676,6 @@ export function ShopInfoRbacComponent({ form, bodyshop }) {
> >
<InputNumber /> <InputNumber />
</Form.Item> </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.phonebook.view")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "phonebook:view"]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.rbac.phonebook.edit")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={["md_rbac", "phonebook:edit"]}
>
<InputNumber />
</Form.Item>
{Simple_Inventory.treatment === "on" && ( {Simple_Inventory.treatment === "on" && (
<> <>
<Form.Item <Form.Item

View File

@@ -5,6 +5,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component"; import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
import {pageLimit} from "../../utils/config";
export default function VehiclesListComponent({ export default function VehiclesListComponent({
loading, loading,
vehicles, vehicles,
@@ -106,7 +107,7 @@ export default function VehiclesListComponent({
loading={loading} loading={loading}
pagination={{ pagination={{
position: "top", position: "top",
pageSize: 25, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(page || 1),
total: total, total: total,
}} }}

View File

@@ -5,6 +5,7 @@ import AlertComponent from "../alert/alert.component";
import { QUERY_ALL_VEHICLES_PAGINATED } from "../../graphql/vehicles.queries"; import { QUERY_ALL_VEHICLES_PAGINATED } from "../../graphql/vehicles.queries";
import queryString from "query-string"; import queryString from "query-string";
import { useLocation } from "react-router-dom"; import { useLocation } from "react-router-dom";
import {pageLimit} from "../../utils/config";
export default function VehiclesListContainer() { export default function VehiclesListContainer() {
const searchParams = queryString.parse(useLocation().search); const searchParams = queryString.parse(useLocation().search);
@@ -15,8 +16,8 @@ export default function VehiclesListContainer() {
{ {
variables: { variables: {
search: search || "", search: search || "",
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * pageLimit : 0,
limit: 25, limit: pageLimit,
order: [ order: [
{ {
[sortcolumn || "created_at"]: sortorder [sortcolumn || "created_at"]: sortorder

View File

@@ -30,15 +30,15 @@ export const QUERY_AVAILABLE_CC = gql`
fuel fuel
id id
make make
model
plate
status
year
dailycost
mileage mileage
model
notes notes
nextservicekm nextservicekm
nextservicedate nextservicedate
plate
readiness
status
year
} }
} }
`; `;
@@ -68,19 +68,20 @@ export const QUERY_ALL_CC = gql`
insuranceexpires insuranceexpires
leaseenddate leaseenddate
make make
mileage
model model
nextservicedate nextservicedate
nextservicekm nextservicekm
notes notes
plate plate
purchasedate purchasedate
readiness
registrationexpires registrationexpires
serviceenddate serviceenddate
servicestartdate servicestartdate
status status
vin vin
year year
mileage
cccontracts( cccontracts(
where: { status: { _eq: "contracts.status.out" } } where: { status: { _eq: "contracts.status.out" } }
order_by: { contract_date: desc } order_by: { contract_date: desc }
@@ -90,10 +91,10 @@ export const QUERY_ALL_CC = gql`
scheduledreturn scheduledreturn
job { job {
id id
ro_number
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_co_nm ownr_co_nm
ro_number
} }
} }
} }
@@ -119,19 +120,20 @@ export const QUERY_CC_BY_PK = gql`
insuranceexpires insuranceexpires
leaseenddate leaseenddate
make make
mileage
model model
nextservicedate nextservicedate
nextservicekm nextservicekm
notes notes
plate plate
purchasedate purchasedate
readiness
registrationexpires registrationexpires
serviceenddate serviceenddate
servicestartdate servicestartdate
status status
vin vin
year year
mileage
cccontracts_aggregate { cccontracts_aggregate {
aggregate { aggregate {
count(distinct: true) count(distinct: true)
@@ -139,21 +141,20 @@ export const QUERY_CC_BY_PK = gql`
} }
cccontracts(offset: $offset, limit: $limit, order_by: $order) { cccontracts(offset: $offset, limit: $limit, order_by: $order) {
agreementnumber agreementnumber
driver_fn
driver_ln
id id
status
start
scheduledreturn
kmstart kmstart
kmend kmend
driver_ln scheduledreturn
driver_fn start
status
job { job {
ro_number id
ownr_ln ownr_ln
ownr_fn ownr_fn
ownr_co_nm ownr_co_nm
id ro_number
} }
} }
} }

View File

@@ -1,5 +1,65 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
export const QUERY_ALL_ACTIVE_JOBS_PAGINATED = gql`
query QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED(
$offset: Int
$limit: Int
$order: [jobs_order_by!]
$statuses: [String!]!,
$isConverted: Boolean
) {
jobs(
offset: $offset
limit: $limit
where: { status: { _in: $statuses }, converted: { _eq: $isConverted } }
order_by: $order
) {
iouparent
ownr_fn
ownr_ln
ownr_co_nm
ownr_ph1
ownr_ph2
ownr_ea
ownerid
comment
plate_no
plate_st
v_vin
v_model_yr
v_model_desc
v_make_desc
v_color
vehicleid
actual_completion
actual_delivery
actual_in
production_vars
id
ins_co_nm
clm_no
po_number
clm_total
owner_owing
ro_number
scheduled_completion
scheduled_in
scheduled_delivery
status
updated_at
ded_amt
suspended
est_ct_fn
est_ct_ln
}
jobs_aggregate(where: { status: { _in: $statuses } }) {
aggregate {
count(distinct: true)
}
}
}
`;
export const QUERY_ALL_ACTIVE_JOBS = gql` export const QUERY_ALL_ACTIVE_JOBS = gql`
query QUERY_ALL_ACTIVE_JOBS($statuses: [String!]!, $isConverted: Boolean) { query QUERY_ALL_ACTIVE_JOBS($statuses: [String!]!, $isConverted: Boolean) {
jobs( jobs(
@@ -60,7 +120,7 @@ export const QUERY_PARTS_QUEUE = gql`
} }
} }
jobs( jobs(
where: { _and: [{ status: { _in: $statuses } }] } where: { _and: [{ status: { _in: $statuses }, converted: { _eq: true } }] }
offset: $offset offset: $offset
limit: $limit limit: $limit
order_by: $order order_by: $order
@@ -304,6 +364,8 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
employee_refinish employee_refinish
employee_prep employee_prep
employee_csr employee_csr
est_ct_fn
est_ct_ln
suspended suspended
date_repairstarted date_repairstarted
joblines_status { joblines_status {

View File

@@ -143,9 +143,14 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
$end: date! $end: date!
$fixedStart: date! $fixedStart: date!
$fixedEnd: date! $fixedEnd: date!
$jobStart: timestamptz!
$jobEnd: timestamptz!
) { ) {
timetickets( timetickets(
where: { date: { _gte: $start, _lte: $end }, cost_center: {_neq: "timetickets.labels.shift"} } where: {
date: { _gte: $start, _lte: $end }
cost_center: { _neq: "timetickets.labels.shift" }
}
order_by: { date: desc_nulls_first } order_by: { date: desc_nulls_first }
) { ) {
actualhrs actualhrs
@@ -176,7 +181,10 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
} }
} }
fixedperiod: timetickets( fixedperiod: timetickets(
where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, cost_center: {_neq: "timetickets.labels.shift"} } where: {
date: { _gte: $fixedStart, _lte: $fixedEnd }
cost_center: { _neq: "timetickets.labels.shift" }
}
order_by: { date: desc_nulls_first } order_by: { date: desc_nulls_first }
) { ) {
actualhrs actualhrs
@@ -205,6 +213,25 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
last_name last_name
} }
} }
jobs(
where: {
date_invoiced: { _is_null: true }
ro_number: { _is_null: false }
voided: { _eq: false }
_or: [
{ actual_completion: { _gte: $jobStart, _lte: $jobEnd } }
{ actual_delivery: { _gte: $jobStart, _lte: $jobEnd } }
]
}
) {
id
joblines(order_by: { line_no: asc }, where: { removed: { _eq: false } }) {
convertedtolbr
convertedtolbr_data
mod_lb_hrs
mod_lbr_ty
}
}
} }
`; `;

View File

@@ -14,6 +14,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import { alphaSort, dateSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
import {pageLimit} from "../../utils/config";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) => setPartsOrderContext: (context) =>
@@ -295,11 +296,11 @@ export function BillsListPage({
pagination={ pagination={
search?.search search?.search
? { ? {
pageSize: 25, pageSize: pageLimit,
showSizeChanger: false, showSizeChanger: false,
} }
: { : {
pageSize: 25, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(page || 1),
total: total, total: total,
showSizeChanger: false, showSizeChanger: false,

View File

@@ -13,6 +13,7 @@ import {
setSelectedHeader, setSelectedHeader,
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import BillsPageComponent from "./bills.page.component"; import BillsPageComponent from "./bills.page.component";
import {pageLimit} from "../../utils/config";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -38,8 +39,8 @@ export function BillsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * pageLimit : 0,
limit: 25, limit: pageLimit,
order: [ order: [
searchObj searchObj
? JSON.parse(searchObj) ? JSON.parse(searchObj)

View File

@@ -12,6 +12,7 @@ import {
setSelectedHeader, setSelectedHeader,
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import ContractsPageComponent from "./contracts.page.component"; import ContractsPageComponent from "./contracts.page.component";
import {pageLimit} from "../../utils/config";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -29,8 +30,8 @@ export function ContractsPageContainer({ setBreadcrumbs, setSelectedHeader }) {
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
search: search || "", search: search || "",
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * pageLimit : 0,
limit: 25, limit: pageLimit,
order: [ order: [
{ {
[sortcolumn || "start"]: sortorder [sortcolumn || "start"]: sortorder

View File

@@ -1,11 +1,14 @@
import { useMutation, useQuery } from "@apollo/client"; import { useMutation, useQuery } from "@apollo/client";
import { Form, notification } from "antd"; import { Form, notification } from "antd";
import moment from "moment"; import moment from "moment";
import queryString from "query-string";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { useParams } from "react-router-dom"; import { useLocation, useParams } from "react-router-dom";
import AlertComponent from "../../components/alert/alert.component"; import AlertComponent from "../../components/alert/alert.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import NotFound from "../../components/not-found/not-found.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { QUERY_CC_BY_PK, UPDATE_CC } from "../../graphql/courtesy-car.queries"; import { QUERY_CC_BY_PK, UPDATE_CC } from "../../graphql/courtesy-car.queries";
import { import {
@@ -13,12 +16,10 @@ import {
setBreadcrumbs, setBreadcrumbs,
setSelectedHeader, setSelectedHeader,
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import { pageLimit } from "../../utils/config";
import { CreateRecentItem } from "../../utils/create-recent-item"; import { CreateRecentItem } from "../../utils/create-recent-item";
import UndefinedToNull from "./../../utils/undefinedtonull";
import CourtesyCarDetailPageComponent from "./courtesy-car-detail.page.component"; import CourtesyCarDetailPageComponent from "./courtesy-car-detail.page.component";
import NotFound from "../../components/not-found/not-found.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import queryString from "query-string";
import { useLocation } from "react-router-dom";
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)), setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -41,8 +42,8 @@ export function CourtesyCarDetailPageContainer({
const { loading, error, data } = useQuery(QUERY_CC_BY_PK, { const { loading, error, data } = useQuery(QUERY_CC_BY_PK, {
variables: { variables: {
id: ccId, id: ccId,
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * pageLimit : 0,
limit: 25, limit: pageLimit,
order: [ order: [
{ {
[sortcolumn || "start"]: sortorder [sortcolumn || "start"]: sortorder
@@ -111,7 +112,10 @@ export function CourtesyCarDetailPageContainer({
setSaveLoading(true); setSaveLoading(true);
const result = await updateCourtesyCar({ const result = await updateCourtesyCar({
variables: { cc: { ...values }, ccId: ccId }, variables: {
cc: { ...UndefinedToNull(values, ["readiness"]) },
ccId: ccId,
},
refetchQueries: ["QUERY_CC_BY_PK"], refetchQueries: ["QUERY_CC_BY_PK"],
awaitRefetchQueries: true, awaitRefetchQueries: true,
}); });

View File

@@ -12,6 +12,7 @@ import AlertComponent from "../../components/alert/alert.component";
import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries"; import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -29,8 +30,8 @@ export function ExportLogsPageComponent({ bodyshop }) {
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
search: search || "", search: search || "",
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * pageLimit : 0,
limit: 25, limit: pageLimit,
order: [ order: [
{ {
[sortcolumn || "created_at"]: sortorder [sortcolumn || "created_at"]: sortorder
@@ -178,7 +179,7 @@ export function ExportLogsPageComponent({ bodyshop }) {
loading={loading} loading={loading}
pagination={{ pagination={{
position: "top", position: "top",
pageSize: 25, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(page || 1),
total: data && data.search_exportlog_aggregate.aggregate.count, total: data && data.search_exportlog_aggregate.aggregate.count,
}} }}

View File

@@ -13,6 +13,7 @@ import {
setBreadcrumbs, setBreadcrumbs,
setSelectedHeader, setSelectedHeader,
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//bodyshop: selectBodyshop, //bodyshop: selectBodyshop,
@@ -33,16 +34,16 @@ export function AllJobs({ setBreadcrumbs, setSelectedHeader }) {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * pageLimit : 0,
limit: 25, limit: pageLimit,
...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}), ...(statusFilters ? { statusList: JSON.parse(statusFilters) } : {}),
order: [ order: [
{ {
[sortcolumn || "ro_number"]: [sortcolumn || "ro_number"]:
sortorder && sortorder !== "false" sortorder && sortorder !== "false"
? sortorder === "descend" ? (sortorder === "descend"
? "desc" ? "desc"
: "asc" : "asc")
: "desc", : "desc",
}, },
], ],

View File

@@ -36,14 +36,22 @@ import JobsCloseLines from "../../components/jobs-close-lines/jobs-close-lines.c
import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component"; import LayoutFormRow from "../../components/layout-form-row/layout-form-row.component";
import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries"; import { generateJobLinesUpdatesForInvoicing } from "../../graphql/jobs-lines.queries";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
}); });
export function JobsCloseComponent({ job, bodyshop, jobRO }) { const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function JobsCloseComponent({ job, bodyshop, jobRO, insertAuditTrail, }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const client = useApolloClient(); const client = useApolloClient();
@@ -110,6 +118,10 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
notification["success"]({ notification["success"]({
message: t("jobs.successes.closed"), message: t("jobs.successes.closed"),
}); });
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobinvoiced(),
});
// history.push(`/manage/jobs/${job.id}`); // history.push(`/manage/jobs/${job.id}`);
} else { } else {
setLoading(false); setLoading(false);
@@ -527,4 +539,4 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
</div> </div>
); );
} }
export default connect(mapStateToProps, null)(JobsCloseComponent); export default connect(mapStateToProps, mapDispatchToProps)(JobsCloseComponent);

View File

@@ -1,6 +1,6 @@
import _ from "lodash";
import { useLazyQuery, useMutation } from "@apollo/client"; import { useLazyQuery, useMutation } from "@apollo/client";
import { Form, notification } from "antd"; import { Form, notification } from "antd";
import _ from "lodash";
import React, { useEffect, useState } from "react"; import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -90,6 +90,7 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
{}, {},
values, values,
{ date_open: new Date() }, { date_open: new Date() },
{ date_estimated: new Date() },
{ {
vehicle: vehicle:
state.vehicle.selectedid || state.vehicle.none state.vehicle.selectedid || state.vehicle.none

View File

@@ -18,6 +18,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter, TimeAgoFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter, TimeAgoFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
import useLocalStorage from "../../utils/useLocalStorage"; import useLocalStorage from "../../utils/useLocalStorage";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -296,7 +297,7 @@ export function PartsQueuePageComponent({ bodyshop }) {
loading={loading} loading={loading}
pagination={{ pagination={{
position: "top", position: "top",
pageSize: 50, pageSize: pageLimit,
// current: parseInt(page || 1), // current: parseInt(page || 1),
// total: data && data.jobs_aggregate.aggregate.count, // total: data && data.jobs_aggregate.aggregate.count,
}} }}

View File

@@ -14,6 +14,7 @@ import {
setSelectedHeader, setSelectedHeader,
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -34,8 +35,8 @@ export function AllJobs({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
fetchPolicy: "network-only", fetchPolicy: "network-only",
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * pageLimit : 0,
limit: 25, limit: pageLimit,
order: [ order: [
searchObj searchObj
? JSON.parse(searchObj) ? JSON.parse(searchObj)

View File

@@ -17,6 +17,7 @@ import {
import ChatOpenButton from "../../components/chat-open-button/chat-open-button.component"; import ChatOpenButton from "../../components/chat-open-button/chat-open-button.component";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import { HasRbacAccess } from "../../components/rbac-wrapper/rbac-wrapper.component"; import { HasRbacAccess } from "../../components/rbac-wrapper/rbac-wrapper.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -35,8 +36,8 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) {
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
search: search || "", search: search || "",
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * pageLimit : 0,
limit: 25, limit: pageLimit,
order: [ order: [
{ {
[sortcolumn || "lastname"]: sortorder [sortcolumn || "lastname"]: sortorder
@@ -189,7 +190,7 @@ export function PhonebookPageComponent({ bodyshop, authLevel }) {
loading={loading} loading={loading}
pagination={{ pagination={{
position: "top", position: "top",
pageSize: 25, pageSize: pageLimit,
current: parseInt(page || 1), current: parseInt(page || 1),
total: data && data.search_phonebook_aggregate.aggregate.count, total: data && data.search_phonebook_aggregate.aggregate.count,
}} }}

View File

@@ -16,6 +16,7 @@ import {
} from "../../redux/application/application.actions"; } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import {pageLimit} from "../../utils/config";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
@@ -42,8 +43,8 @@ export function ShopCsiContainer({
nextFetchPolicy: "network-only", nextFetchPolicy: "network-only",
variables: { variables: {
//search: search || "", //search: search || "",
offset: page ? (page - 1) * 25 : 0, offset: page ? (page - 1) * pageLimit : 0,
limit: 25, limit: pageLimit,
order: [ order: [
{ {
[sortcolumn || "completedon"]: sortorder [sortcolumn || "completedon"]: sortorder

View File

@@ -56,13 +56,13 @@
"history": "History", "history": "History",
"inproduction": "Jobs In Production", "inproduction": "Jobs In Production",
"manualevent": "Add Manual Appointment", "manualevent": "Add Manual Appointment",
"noarrivingjobs": "No jobs are arriving.", "noarrivingjobs": "No Jobs are arriving.",
"nocompletingjobs": "No jobs scheduled for completion.", "nocompletingjobs": "No Jobs scheduled for completion.",
"nodateselected": "No date has been selected.", "nodateselected": "No date has been selected.",
"priorappointments": "Previous Appointments", "priorappointments": "Previous Appointments",
"reminder": "This is {{shopname}} reminding you about an appointment on {{date}} at {{time}}. Please let us know if you are not able to make the appointment. We look forward to seeing you soon. ", "reminder": "This is {{shopname}} reminding you about an appointment on {{date}} at {{time}}. Please let us know if you are not able to make the appointment. We look forward to seeing you soon. ",
"scheduledfor": "Scheduled appointment for: ", "scheduledfor": "Scheduled appointment for: ",
"severalerrorsfound": "Several jobs have issues which may prevent accurate smart scheduling. Click to expand.", "severalerrorsfound": "Several Jobs have issues which may prevent accurate smart scheduling. Click to expand.",
"smartscheduling": "Smart Scheduling", "smartscheduling": "Smart Scheduling",
"suggesteddates": "Suggested Dates" "suggesteddates": "Suggested Dates"
}, },
@@ -103,6 +103,7 @@
"admin_jobmarkforreexport": "ADMIN: Job marked for re-export.", "admin_jobmarkforreexport": "ADMIN: Job marked for re-export.",
"admin_jobuninvoice": "ADMIN: Job has been uninvoiced.", "admin_jobuninvoice": "ADMIN: Job has been uninvoiced.",
"admin_jobunvoid": "ADMIN: Job has been unvoided.", "admin_jobunvoid": "ADMIN: Job has been unvoided.",
"alerttoggle": "Alert Toggle set to {{status}}",
"appointmentcancel": "Appointment canceled. Lost Reason: {{lost_sale_reason}}.", "appointmentcancel": "Appointment canceled. Lost Reason: {{lost_sale_reason}}.",
"appointmentinsert": "Appointment created. Appointment Date: {{start}}.", "appointmentinsert": "Appointment created. Appointment Date: {{start}}.",
"billposted": "Bill with invoice number {{invoice_number}} posted.", "billposted": "Bill with invoice number {{invoice_number}} posted.",
@@ -111,17 +112,18 @@
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}", "jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
"jobassignmentremoved": "Employee assignment removed for {{operation}}", "jobassignmentremoved": "Employee assignment removed for {{operation}}",
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.", "jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.",
"jobinvoiced": "Job has been invoiced.",
"jobconverted": "Job converted and assigned number {{ro_number}}.", "jobconverted": "Job converted and assigned number {{ro_number}}.",
"jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.", "jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.",
"jobimported": "Job imported.", "jobimported": "Job imported.",
"jobinproductionchange": "Job production status set to {{inproduction}}", "jobinproductionchange": "Job production status set to {{inproduction}}",
"jobioucreated": "IOU Created.", "jobioucreated": "IOU Created.",
"jobmodifylbradj": "Labor adjustments modified {{mod_lbr_ty}} / {{hours}}.", "jobmodifylbradj": "Labor adjustments modified {{mod_lbr_ty}} / {{hours}}.",
"jobnoteadded": "Note added to job.", "jobnoteadded": "Note added to Job.",
"jobnotedeleted": "Note deleted from job.", "jobnotedeleted": "Note deleted from Job.",
"jobnoteupdated": "Note updated on job.", "jobnoteupdated": "Note updated on Job.",
"jobspartsorder": "Parts order {{order_number}} added to job.", "jobspartsorder": "Parts order {{order_number}} added to Job.",
"jobspartsreturn": "Parts return {{order_number}} added to job.", "jobspartsreturn": "Parts return {{order_number}} added to Job.",
"jobstatuschange": "Job status changed to {{status}}.", "jobstatuschange": "Job status changed to {{status}}.",
"jobsupplement": "Job supplement imported." "jobsupplement": "Job supplement imported."
} }
@@ -203,6 +205,7 @@
"entered_total": "Total of Entered Lines", "entered_total": "Total of Entered Lines",
"enteringcreditmemo": "You are entering a credit memo. Please ensure you are also entering positive values.", "enteringcreditmemo": "You are entering a credit memo. Please ensure you are also entering positive values.",
"federal_tax": "Federal Tax", "federal_tax": "Federal Tax",
"federal_tax_exempt": "Federal Tax Exempt?",
"generatepartslabel": "Generate Parts Labels after Saving?", "generatepartslabel": "Generate Parts Labels after Saving?",
"iouexists": "An IOU exists that is associated to this RO.", "iouexists": "An IOU exists that is associated to this RO.",
"local_tax": "Local Tax", "local_tax": "Local Tax",
@@ -210,7 +213,8 @@
"markforreexport": "Mark for Re-export", "markforreexport": "Mark for Re-export",
"new": "New Bill", "new": "New Bill",
"noneselected": "No bill selected.", "noneselected": "No bill selected.",
"onlycmforinvoiced": "Only credit memos can be entered for any job that has been invoiced, exported, or voided.", "onlycmforinvoiced": "Only credit memos can be entered for any Job that has been invoiced, exported, or voided.",
"printlabels": "Print Labels",
"retailtotal": "Bills Retail Total", "retailtotal": "Bills Retail Total",
"savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.", "savewithdiscrepancy": "You are about to save this bill with a discrepancy. The system will continue to use the calculated amount using the bill lines. Press cancel to return to the bill.",
"state_tax": "Provincial/State Tax", "state_tax": "Provincial/State Tax",
@@ -447,6 +451,7 @@
"config": "Shop -> Config", "config": "Shop -> Config",
"dashboard": "Shop -> Dashboard", "dashboard": "Shop -> Dashboard",
"rbac": "Shop -> RBAC", "rbac": "Shop -> RBAC",
"reportcenter": "Shop -> Report Center",
"templates": "Shop -> Templates", "templates": "Shop -> Templates",
"vendors": "Shop -> Vendors" "vendors": "Shop -> Vendors"
}, },
@@ -658,7 +663,7 @@
"printall": "Print All Documents" "printall": "Print All Documents"
}, },
"errors": { "errors": {
"complete": "Error during job checklist completion. {{error}}", "complete": "Error during Job checklist completion. {{error}}",
"nochecklist": "No checklist has been configured for your shop. " "nochecklist": "No checklist has been configured for your shop. "
}, },
"labels": { "labels": {
@@ -666,7 +671,7 @@
"allow_text_message": "Permission to Text?", "allow_text_message": "Permission to Text?",
"checklist": "Checklist", "checklist": "Checklist",
"printpack": "Job Intake Print Pack", "printpack": "Job Intake Print Pack",
"removefromproduction": "Remove job from production?" "removefromproduction": "Remove Job from Production?"
}, },
"successes": { "successes": {
"completed": "Job checklist completed." "completed": "Job checklist completed."
@@ -682,9 +687,9 @@
"senddltoform": "Insert Driver's License Information" "senddltoform": "Insert Driver's License Information"
}, },
"errors": { "errors": {
"fetchingjobinfo": "Error fetching job info. {{error}}.", "fetchingjobinfo": "Error fetching Job Info. {{error}}.",
"returning": "Error returning courtesy car. {{error}}", "returning": "Error returning Courtesy Car. {{error}}",
"saving": "Error saving contract. {{error}}", "saving": "Error saving Contract. {{error}}",
"selectjobandcar": "Please ensure both a car and job are selected." "selectjobandcar": "Please ensure both a car and job are selected."
}, },
"fields": { "fields": {
@@ -741,7 +746,7 @@
"driverinformation": "Driver's Information", "driverinformation": "Driver's Information",
"findcontract": "Find Contract", "findcontract": "Find Contract",
"findermodal": "Contract Finder", "findermodal": "Contract Finder",
"noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.", "noteconvertedfrom": "R.O. created from converted Courtesy Car Contract {{agreementnumber}}.",
"populatefromjob": "Populate from Job", "populatefromjob": "Populate from Job",
"rates": "Contract Rates", "rates": "Contract Rates",
"time": "Time", "time": "Time",
@@ -763,7 +768,7 @@
"return": "Return Car" "return": "Return Car"
}, },
"errors": { "errors": {
"saving": "Error saving courtesy card. {{error}}" "saving": "Error saving Courtesy Car. {{error}}"
}, },
"fields": { "fields": {
"color": "Color", "color": "Color",
@@ -781,6 +786,7 @@
"notes": "Notes", "notes": "Notes",
"plate": "Plate Number", "plate": "Plate Number",
"purchasedate": "Purchase Date", "purchasedate": "Purchase Date",
"readiness": "Readiness",
"registrationexpires": "Registration Expires On", "registrationexpires": "Registration Expires On",
"serviceenddate": "Usage End Date", "serviceenddate": "Usage End Date",
"servicestartdate": "Usage Start Date", "servicestartdate": "Usage Start Date",
@@ -817,6 +823,10 @@
}, },
"successes": { "successes": {
"saved": "Courtesy Car saved successfully." "saved": "Courtesy Car saved successfully."
},
"readiness": {
"notready": "Not Ready",
"ready": "Ready"
} }
}, },
"csi": { "csi": {
@@ -914,7 +924,7 @@
"upload_limitexceeded": "Uploading all selected documents will exceed the job storage limit for your shop. ", "upload_limitexceeded": "Uploading all selected documents will exceed the job storage limit for your shop. ",
"upload_limitexceeded_title": "Unable to upload document(s)", "upload_limitexceeded_title": "Unable to upload document(s)",
"uploading": "Uploading...", "uploading": "Uploading...",
"usage": "of job storage used. ({{used}} / {{total}})" "usage": "of Job storage used. ({{used}} / {{total}})"
}, },
"successes": { "successes": {
"delete": "Document(s) deleted successfully.", "delete": "Document(s) deleted successfully.",
@@ -1375,28 +1385,28 @@
}, },
"errors": { "errors": {
"addingtoproduction": "Error adding to production. {{error}}", "addingtoproduction": "Error adding to production. {{error}}",
"cannotintake": "Intake cannot be completed for this job. It has either already been completed or the job is already here.", "cannotintake": "Intake cannot be completed for this Job. It has either already been completed or the Job is already here.",
"closing": "Error closing job. {{error}}", "closing": "Error closing Job. {{error}}",
"creating": "Error encountered while creating job. {{error}}", "creating": "Error encountered while creating Job. {{error}}",
"deleted": "Error deleting job. {{error}}", "deleted": "Error deleting Job. {{error}}",
"exporting": "Error exporting job. {{error}}", "exporting": "Error exporting Job. {{error}}",
"exporting-partner": "Unable to connect to ImEX Partner. Please ensure it is running and logged in.", "exporting-partner": "Unable to connect to ImEX Partner. Please ensure it is running and logged in.",
"invoicing": "Error invoicing job. {{error}}", "invoicing": "Error invoicing Job. {{error}}",
"noaccess": "This job does not exist or you do not have access to it.", "noaccess": "This Job does not exist or you do not have access to it.",
"nodamage": "No damage points on estimate.", "nodamage": "No damage points on estimate.",
"nodates": "No dates specified for this job.", "nodates": "No dates specified for this Job.",
"nofinancial": "No financial data has been calculated yet for this job. Please save it again.", "nofinancial": "No financial data has been calculated yet for this job. Please save it again.",
"nojobselected": "No job is selected.", "nojobselected": "No Job is selected.",
"noowner": "No owner associated.", "noowner": "No owner associated.",
"novehicle": "No vehicle associated.", "novehicle": "No vehicle associated.",
"partspricechange": "", "partspricechange": "",
"saving": "Error encountered while saving record.", "saving": "Error encountered while saving record.",
"scanimport": "Error importing job. {{message}}", "scanimport": "Error importing Job. {{message}}",
"totalscalc": "Error while calculating new job totals.", "totalscalc": "Error while calculating new Job totals.",
"updating": "Error while updating job(s). {{error}}", "updating": "Error while updating Job(s). {{error}}",
"validation": "Please ensure all fields are entered correctly.", "validation": "Please ensure all fields are entered correctly.",
"validationtitle": "Validation Error", "validationtitle": "Validation Error",
"voiding": "Error voiding job. {{error}}" "voiding": "Error voiding Job. {{error}}"
}, },
"fields": { "fields": {
"actual_completion": "Actual Completion", "actual_completion": "Actual Completion",
@@ -1673,9 +1683,9 @@
"adminwarning": "Use the functionality on this page at your own risk. You are responsible for any and all changes to your data.", "adminwarning": "Use the functionality on this page at your own risk. You are responsible for any and all changes to your data.",
"allocations": "Allocations", "allocations": "Allocations",
"alreadyaddedtoscoreboard": "Job has already been added to scoreboard. Saving will update the previous entry.", "alreadyaddedtoscoreboard": "Job has already been added to scoreboard. Saving will update the previous entry.",
"alreadyclosed": "This job has already been closed.", "alreadyclosed": "This Job has already been closed.",
"appointmentconfirmation": "Send confirmation to customer?", "appointmentconfirmation": "Send confirmation to customer?",
"associationwarning": "Any changes to associations will require updating the data from the new parent record to the job.", "associationwarning": "Any changes to associations will require updating the data from the new parent record to the Job.",
"audit": "Audit Trail", "audit": "Audit Trail",
"available": "Available", "available": "Available",
"availablejobs": "Available Jobs", "availablejobs": "Available Jobs",
@@ -1683,7 +1693,7 @@
"days": "Days", "days": "Days",
"rate": "PVRT Rate" "rate": "PVRT Rate"
}, },
"ca_gst_all_if_null": "If the job is marked as a \"GST Registrant\" and this value is set to $0, the customer will be responsible for paying all of the GST by default. ", "ca_gst_all_if_null": "If the Job is marked as a \"GST Registrant\" and this value is set to $0, the customer will be responsible for paying all of the GST by default. ",
"calc_repair_days": "Calculated Repair Days", "calc_repair_days": "Calculated Repair Days",
"calc_repair_days_tt": "This is the approximate number of days required to complete the repair according to the target touch time in your shop configuration (current set to {{target_touchtime}}).", "calc_repair_days_tt": "This is the approximate number of days required to complete the repair according to the target touch time in your shop configuration (current set to {{target_touchtime}}).",
"cards": { "cards": {
@@ -1699,7 +1709,7 @@
"totals": "Totals", "totals": "Totals",
"vehicle": "Vehicle" "vehicle": "Vehicle"
}, },
"changeclass": "Changing the job's class can have fundamental impacts to already exported accounting items. Are you sure you want to do this?", "changeclass": "Changing the Job's class can have fundamental impacts to already exported accounting items. Are you sure you want to do this?",
"checklistcompletedby": "Checklist completed by {{by}} at {{at}}", "checklistcompletedby": "Checklist completed by {{by}} at {{at}}",
"checklistdocuments": "Checklist Documents", "checklistdocuments": "Checklist Documents",
"checklists": "Checklists", "checklists": "Checklists",
@@ -1723,12 +1733,12 @@
"vehicleinfo": "Vehicle Info" "vehicleinfo": "Vehicle Info"
}, },
"createiouwarning": "Are you sure you want to create an IOU for these lines? A new RO will be created based on those lines for this customer.", "createiouwarning": "Are you sure you want to create an IOU for these lines? A new RO will be created based on those lines for this customer.",
"creating_new_job": "Creating new job...", "creating_new_job": "Creating new Job...",
"deductible": { "deductible": {
"stands": "Stands", "stands": "Stands",
"waived": "Waived" "waived": "Waived"
}, },
"deleteconfirm": "Are you sure you want to delete this job? This cannot be undone. ", "deleteconfirm": "Are you sure you want to delete this Job? This cannot be undone. ",
"deletedelivery": "Delete Delivery Checklist", "deletedelivery": "Delete Delivery Checklist",
"deleteintake": "Delete Intake Checklist", "deleteintake": "Delete Intake Checklist",
"deliverchecklist": "Deliver Checklist", "deliverchecklist": "Deliver Checklist",
@@ -1749,7 +1759,7 @@
"documents": "Documents", "documents": "Documents",
"documents-images": "Images", "documents-images": "Images",
"documents-other": "Other Documents", "documents-other": "Other Documents",
"duplicateconfirm": "Are you sure you want to duplicate this job? Some elements of this job will not be duplicated.", "duplicateconfirm": "Are you sure you want to duplicate this Job? Some elements of this Job will not be duplicated.",
"emailaudit": "Email Audit Trail", "emailaudit": "Email Audit Trail",
"employeeassignments": "Employee Assignments", "employeeassignments": "Employee Assignments",
"estimatelines": "Estimate Lines", "estimatelines": "Estimate Lines",
@@ -1760,7 +1770,7 @@
"gppercent": "% G.P.", "gppercent": "% G.P.",
"hrs_claimed": "Hours Claimed", "hrs_claimed": "Hours Claimed",
"hrs_total": "Hours Total", "hrs_total": "Hours Total",
"importnote": "The job was initially imported.", "importnote": "The Job was initially imported.",
"inproduction": "In Production", "inproduction": "In Production",
"intakechecklist": "Intake Checklist", "intakechecklist": "Intake Checklist",
"iou": "IOU", "iou": "IOU",
@@ -1793,9 +1803,9 @@
"calculatedcreditsnotreceived": "The calculated credits not received is derived by subtracting the amount of credit memos entered from the <b>retail</b> total of returns created. This does not take into account whether the credit was marked as received. You can find more information <a href=\"https://help.imex.online/en/article/credits-not-received-changes-1jy9snw\" target=\"_blank\">here</a>.", "calculatedcreditsnotreceived": "The calculated credits not received is derived by subtracting the amount of credit memos entered from the <b>retail</b> total of returns created. This does not take into account whether the credit was marked as received. You can find more information <a href=\"https://help.imex.online/en/article/credits-not-received-changes-1jy9snw\" target=\"_blank\">here</a>.",
"creditmemos": "The total <b>retail</b> amount of all returns created. This amount does not reflect credit memos that have been posted.", "creditmemos": "The total <b>retail</b> amount of all returns created. This amount does not reflect credit memos that have been posted.",
"creditsnotreceived": "This total reflects the total <b>retail</b> of parts returns lines that have not been explicitly marked as returned when posting a credit memo. You can learn more about this here <a href=\"https://help.imex.online/en/article/credits-not-received-changes-1jy9snw\" target=\"_blank\">here</a>. ", "creditsnotreceived": "This total reflects the total <b>retail</b> of parts returns lines that have not been explicitly marked as returned when posting a credit memo. You can learn more about this here <a href=\"https://help.imex.online/en/article/credits-not-received-changes-1jy9snw\" target=\"_blank\">here</a>. ",
"discrep1": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Too many bills/bill lines that have been posted against this RO. Check to make sure every bill posted on this RO is correctly posted and assigned.</li>\n<li>You do not have the latest supplement imported, or, a supplement must be submitted and then imported.</li>\n<li>You have posted a bill line to labor.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this job from reconciling.</i>", "discrep1": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Too many bills/bill lines that have been posted against this RO. Check to make sure every bill posted on this RO is correctly posted and assigned.</li>\n<li>You do not have the latest supplement imported, or, a supplement must be submitted and then imported.</li>\n<li>You have posted a bill line to labor.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this Job from reconciling.</i>",
"discrep2": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Used an incorrect rate when deducting from labor.</li>\n<li>An outstanding imbalance higher in the reconciliation process.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this job from reconciling.</i>", "discrep2": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Used an incorrect rate when deducting from labor.</li>\n<li>An outstanding imbalance higher in the reconciliation process.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this Job from reconciling.</i>",
"discrep3": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>A parts order return has not been created.</li>\n<li>An outstanding imbalance higher in the reconciliation process.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this job from reconciling.</i>", "discrep3": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>A parts order return has not been created.</li>\n<li>An outstanding imbalance higher in the reconciliation process.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this Job from reconciling.</i>",
"laboradj": "The sum of all bill lines that deducted from labor hours, rather than part prices.", "laboradj": "The sum of all bill lines that deducted from labor hours, rather than part prices.",
"partstotal": "This is the total of all parts and sublet amounts on the vehicle (some of these may require an in-house invoice).<br/>\nItems such as shop and paint materials, labor online lines, etc. are not included in this total.", "partstotal": "This is the total of all parts and sublet amounts on the vehicle (some of these may require an in-house invoice).<br/>\nItems such as shop and paint materials, labor online lines, etc. are not included in this total.",
"totalreturns": "The total <b>retail</b> amount of returns created for this job." "totalreturns": "The total <b>retail</b> amount of returns created for this job."
@@ -1826,13 +1836,13 @@
"sale_parts": "Sales - Parts", "sale_parts": "Sales - Parts",
"sale_sublet": "Sales - Sublet", "sale_sublet": "Sales - Sublet",
"sales": "Sales", "sales": "Sales",
"savebeforeconversion": "You have unsaved changes on the job. Please save them before converting it. ", "savebeforeconversion": "You have unsaved changes on the Job. Please save them before converting it. ",
"scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the job. ", "scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the Job. ",
"specialcoveragepolicy": "Special Coverage Policy Applies", "specialcoveragepolicy": "Special Coverage Policy Applies",
"state_tax_amt": "Provincial/State Taxes", "state_tax_amt": "Provincial/State Taxes",
"subletstotal": "Sublets Total", "subletstotal": "Sublets Total",
"subtotal": "Subtotal", "subtotal": "Subtotal",
"supplementnote": "The job had a supplement imported.", "supplementnote": "The Job had a supplement imported.",
"suspended": "SUSPENDED", "suspended": "SUSPENDED",
"suspense": "Suspense", "suspense": "Suspense",
"threshhold": "Max Threshold: ${{amount}}", "threshhold": "Max Threshold: ${{amount}}",
@@ -1841,16 +1851,16 @@
"total_repairs": "Total Repairs", "total_repairs": "Total Repairs",
"total_sales": "Total Sales", "total_sales": "Total Sales",
"totals": "Totals", "totals": "Totals",
"unvoidnote": "This job was unvoided.", "unvoidnote": "This Job was unvoided.",
"vehicle_info": "Vehicle", "vehicle_info": "Vehicle",
"vehicleassociation": "Vehicle Association", "vehicleassociation": "Vehicle Association",
"viewallocations": "View Allocations", "viewallocations": "View Allocations",
"voidjob": "Are you sure you want to void this job? This cannot be easily undone. ", "voidjob": "Are you sure you want to void this Job? This cannot be easily undone. ",
"voidnote": "This job was voided." "voidnote": "This Job was voided."
}, },
"successes": { "successes": {
"addedtoproduction": "Job added to production board.", "addedtoproduction": "Job added to production board.",
"all_deleted": "{{count}} jobs deleted successfully.", "all_deleted": "{{count}} Jobs deleted successfully.",
"closed": "Job closed successfully.", "closed": "Job closed successfully.",
"converted": "Job converted successfully.", "converted": "Job converted successfully.",
"created": "Job created successfully. Click to view.", "created": "Job created successfully. Click to view.",
@@ -2026,7 +2036,7 @@
}, },
"errors": { "errors": {
"invalidphone": "The phone number is invalid. Unable to open conversation. ", "invalidphone": "The phone number is invalid. Unable to open conversation. ",
"noattachedjobs": "No jobs have been associated to this conversation. ", "noattachedjobs": "No Jobs have been associated to this conversation. ",
"updatinglabel": "Error updating label. {{error}}" "updatinglabel": "Error updating label. {{error}}"
}, },
"labels": { "labels": {
@@ -2035,7 +2045,7 @@
"maxtenimages": "You can only select up to a maximum of 10 images at a time.", "maxtenimages": "You can only select up to a maximum of 10 images at a time.",
"messaging": "Messaging", "messaging": "Messaging",
"noallowtxt": "This customer has not indicated their permission to be messaged.", "noallowtxt": "This customer has not indicated their permission to be messaged.",
"nojobs": "Not associated to any job.", "nojobs": "Not associated to any Job.",
"nopush": "Polling Mode Enabled", "nopush": "Polling Mode Enabled",
"phonenumber": "Phone #", "phonenumber": "Phone #",
"presets": "Presets", "presets": "Presets",
@@ -2044,6 +2054,9 @@
"sentby": "Sent by {{by}} at {{time}}", "sentby": "Sent by {{by}} at {{time}}",
"typeamessage": "Send a message...", "typeamessage": "Send a message...",
"unarchive": "Unarchive" "unarchive": "Unarchive"
},
"render": {
"conversation_list": "Conversation List"
} }
}, },
"notes": { "notes": {
@@ -2455,15 +2468,15 @@
"unsuspend": "Unsuspend" "unsuspend": "Unsuspend"
}, },
"errors": { "errors": {
"boardupdate": "Error encountered updating job. {{message}}", "boardupdate": "Error encountered updating Job. {{message}}",
"removing": "Error removing from production board. {{error}}", "removing": "Error removing from production board. {{error}}",
"settings": "Error saving board settings: {{error}}" "settings": "Error saving board settings: {{error}}"
}, },
"labels": { "labels": {
"actual_in": "Actual In", "actual_in": "Actual In",
"alert": "Alert", "alert": "Alert",
"alertoff": "Remove alert from job", "alertoff": "Remove alert from Job",
"alerton": "Add alert to job", "alerton": "Add alert to Job",
"ats": "Alternative Transportation", "ats": "Alternative Transportation",
"bodyhours": "B", "bodyhours": "B",
"bodypriority": "B/P", "bodypriority": "B/P",
@@ -2674,9 +2687,9 @@
"edit": "Edit" "edit": "Edit"
}, },
"errors": { "errors": {
"adding": "Error adding job to scoreboard. {{message}}", "adding": "Error adding Job to Scoreboard. {{message}}",
"removing": "Error removing job from scoreboard. {{message}}", "removing": "Error removing Job from Scoreboard. {{message}}",
"updating": "Error updating scoreboard. {{message}}" "updating": "Error updating Scoreboard. {{message}}"
}, },
"fields": { "fields": {
"bodyhrs": "Body Hours", "bodyhrs": "Body Hours",
@@ -2695,6 +2708,7 @@
"efficiencyoverperiod": "Efficiency over Selected Dates", "efficiencyoverperiod": "Efficiency over Selected Dates",
"entries": "Scoreboard Entries", "entries": "Scoreboard Entries",
"jobs": "Jobs", "jobs": "Jobs",
"jobscompletednotinvoiced": "Completed Not Invoiced",
"lastmonth": "Last Month", "lastmonth": "Last Month",
"lastweek": "Last Week", "lastweek": "Last Week",
"monthlytarget": "Monthly", "monthlytarget": "Monthly",
@@ -2709,6 +2723,7 @@
"timetickets": "Time Tickets", "timetickets": "Time Tickets",
"timeticketsemployee": "Time Tickets by Employee", "timeticketsemployee": "Time Tickets by Employee",
"todateactual": "Actual (MTD)", "todateactual": "Actual (MTD)",
"totalhrs": "Total Hours",
"totaloverperiod": "Total over Selected Dates", "totaloverperiod": "Total over Selected Dates",
"weeklyactual": "Actual (W)", "weeklyactual": "Actual (W)",
"weeklytarget": "Weekly", "weeklytarget": "Weekly",
@@ -2774,7 +2789,7 @@
"ro_number": "Job to Post Against" "ro_number": "Job to Post Against"
}, },
"labels": { "labels": {
"alreadyclockedon": "You are already clocked in to the following job(s):", "alreadyclockedon": "You are already clocked in to the following Job(s):",
"ambreak": "AM Break", "ambreak": "AM Break",
"amshift": "AM Shift", "amshift": "AM Shift",
"clockhours": "Shift Clock Hours Summary", "clockhours": "Shift Clock Hours Summary",

View File

@@ -103,6 +103,7 @@
"admin_jobmarkforreexport": "", "admin_jobmarkforreexport": "",
"admin_jobuninvoice": "", "admin_jobuninvoice": "",
"admin_jobunvoid": "", "admin_jobunvoid": "",
"alerttoggle": "",
"appointmentcancel": "", "appointmentcancel": "",
"appointmentinsert": "", "appointmentinsert": "",
"billposted": "", "billposted": "",
@@ -111,6 +112,7 @@
"jobassignmentchange": "", "jobassignmentchange": "",
"jobassignmentremoved": "", "jobassignmentremoved": "",
"jobchecklist": "", "jobchecklist": "",
"jobinvoiced": "",
"jobconverted": "", "jobconverted": "",
"jobfieldchanged": "", "jobfieldchanged": "",
"jobimported": "", "jobimported": "",
@@ -203,6 +205,7 @@
"entered_total": "", "entered_total": "",
"enteringcreditmemo": "", "enteringcreditmemo": "",
"federal_tax": "", "federal_tax": "",
"federal_tax_exempt": "",
"generatepartslabel": "", "generatepartslabel": "",
"iouexists": "", "iouexists": "",
"local_tax": "", "local_tax": "",
@@ -211,6 +214,7 @@
"new": "", "new": "",
"noneselected": "", "noneselected": "",
"onlycmforinvoiced": "", "onlycmforinvoiced": "",
"printlabels": "",
"retailtotal": "", "retailtotal": "",
"savewithdiscrepancy": "", "savewithdiscrepancy": "",
"state_tax": "", "state_tax": "",
@@ -447,6 +451,7 @@
"config": "", "config": "",
"dashboard": "", "dashboard": "",
"rbac": "", "rbac": "",
"reportcenter": "",
"templates": "", "templates": "",
"vendors": "" "vendors": ""
}, },
@@ -781,6 +786,7 @@
"notes": "", "notes": "",
"plate": "", "plate": "",
"purchasedate": "", "purchasedate": "",
"readiness": "",
"registrationexpires": "", "registrationexpires": "",
"serviceenddate": "", "serviceenddate": "",
"servicestartdate": "", "servicestartdate": "",
@@ -817,6 +823,10 @@
}, },
"successes": { "successes": {
"saved": "" "saved": ""
},
"readiness": {
"notready": "",
"ready": ""
} }
}, },
"csi": { "csi": {
@@ -2044,6 +2054,9 @@
"sentby": "", "sentby": "",
"typeamessage": "Enviar un mensaje...", "typeamessage": "Enviar un mensaje...",
"unarchive": "" "unarchive": ""
},
"render": {
"conversation_list": ""
} }
}, },
"notes": { "notes": {
@@ -2695,6 +2708,7 @@
"efficiencyoverperiod": "", "efficiencyoverperiod": "",
"entries": "", "entries": "",
"jobs": "", "jobs": "",
"jobscompletednotinvoiced": "",
"lastmonth": "", "lastmonth": "",
"lastweek": "", "lastweek": "",
"monthlytarget": "", "monthlytarget": "",
@@ -2709,6 +2723,7 @@
"timetickets": "", "timetickets": "",
"timeticketsemployee": "", "timeticketsemployee": "",
"todateactual": "", "todateactual": "",
"totalhrs": "",
"totaloverperiod": "", "totaloverperiod": "",
"weeklyactual": "", "weeklyactual": "",
"weeklytarget": "", "weeklytarget": "",

View File

@@ -103,6 +103,7 @@
"admin_jobmarkforreexport": "", "admin_jobmarkforreexport": "",
"admin_jobuninvoice": "", "admin_jobuninvoice": "",
"admin_jobunvoid": "", "admin_jobunvoid": "",
"alerttoggle": "",
"appointmentcancel": "", "appointmentcancel": "",
"appointmentinsert": "", "appointmentinsert": "",
"billposted": "", "billposted": "",
@@ -111,6 +112,7 @@
"jobassignmentchange": "", "jobassignmentchange": "",
"jobassignmentremoved": "", "jobassignmentremoved": "",
"jobchecklist": "", "jobchecklist": "",
"jobinvoiced": "",
"jobconverted": "", "jobconverted": "",
"jobfieldchanged": "", "jobfieldchanged": "",
"jobimported": "", "jobimported": "",
@@ -203,6 +205,7 @@
"entered_total": "", "entered_total": "",
"enteringcreditmemo": "", "enteringcreditmemo": "",
"federal_tax": "", "federal_tax": "",
"federal_tax_exempt": "",
"generatepartslabel": "", "generatepartslabel": "",
"iouexists": "", "iouexists": "",
"local_tax": "", "local_tax": "",
@@ -211,6 +214,7 @@
"new": "", "new": "",
"noneselected": "", "noneselected": "",
"onlycmforinvoiced": "", "onlycmforinvoiced": "",
"printlabels": "",
"retailtotal": "", "retailtotal": "",
"savewithdiscrepancy": "", "savewithdiscrepancy": "",
"state_tax": "", "state_tax": "",
@@ -447,6 +451,7 @@
"config": "", "config": "",
"dashboard": "", "dashboard": "",
"rbac": "", "rbac": "",
"reportcenter": "",
"templates": "", "templates": "",
"vendors": "" "vendors": ""
}, },
@@ -781,6 +786,7 @@
"notes": "", "notes": "",
"plate": "", "plate": "",
"purchasedate": "", "purchasedate": "",
"readiness": "",
"registrationexpires": "", "registrationexpires": "",
"serviceenddate": "", "serviceenddate": "",
"servicestartdate": "", "servicestartdate": "",
@@ -817,6 +823,10 @@
}, },
"successes": { "successes": {
"saved": "" "saved": ""
},
"readiness": {
"notready": "",
"ready": ""
} }
}, },
"csi": { "csi": {
@@ -2044,6 +2054,9 @@
"sentby": "", "sentby": "",
"typeamessage": "Envoyer un message...", "typeamessage": "Envoyer un message...",
"unarchive": "" "unarchive": ""
},
"render": {
"conversation_list": ""
} }
}, },
"notes": { "notes": {
@@ -2695,6 +2708,7 @@
"efficiencyoverperiod": "", "efficiencyoverperiod": "",
"entries": "", "entries": "",
"jobs": "", "jobs": "",
"jobscompletednotinvoiced": "",
"lastmonth": "", "lastmonth": "",
"lastweek": "", "lastweek": "",
"monthlytarget": "", "monthlytarget": "",
@@ -2709,6 +2723,7 @@
"timetickets": "", "timetickets": "",
"timeticketsemployee": "", "timeticketsemployee": "",
"todateactual": "", "todateactual": "",
"totalhrs": "",
"totaloverperiod": "", "totaloverperiod": "",
"weeklyactual": "", "weeklyactual": "",
"weeklytarget": "", "weeklytarget": "",

View File

@@ -1,6 +1,7 @@
import i18n from "i18next"; import i18n from "i18next";
const AuditTrailMapping = { const AuditTrailMapping = {
alertToggle: (status) => i18n.t("audit_trail.messages.alerttoggle", { status }),
appointmentcancel: (lost_sale_reason) => appointmentcancel: (lost_sale_reason) =>
i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }), i18n.t("audit_trail.messages.appointmentcancel", { lost_sale_reason }),
appointmentinsert: (start) => appointmentinsert: (start) =>
@@ -11,6 +12,8 @@ const AuditTrailMapping = {
"ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }), "ADMIN: " + i18n.t("audit_trail.messages.jobstatuschange", { status }),
jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"), jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"),
jobimported: () => i18n.t("audit_trail.messages.jobimported"), jobimported: () => i18n.t("audit_trail.messages.jobimported"),
jobinvoiced: () =>
i18n.t("audit_trail.messages.jobinvoiced"),
jobconverted: (ro_number) => jobconverted: (ro_number) =>
i18n.t("audit_trail.messages.jobconverted", { ro_number }), i18n.t("audit_trail.messages.jobconverted", { ro_number }),
jobfieldchange: (field, value) => jobfieldchange: (field, value) =>

View File

@@ -2102,6 +2102,17 @@ export const TemplateList = (type, context) => {
// }, // },
} }
: {}), : {}),
...(!type || type === "messaging"
? {
conversation_list: {
title: i18n.t("messaging.render.conversation_list"),
description: "",
subject: i18n.t("messaging.render.conversation_list"),
key: "conversation_list",
disabled: false,
},
}
: {}),
...(!type || type === "vendor" ...(!type || type === "vendor"
? { ? {
purchases_by_vendor_detailed: { purchases_by_vendor_detailed: {

View File

@@ -0,0 +1,4 @@
// Sometimes referred to as PageSize, this variable controls the amount of records
// to show on one page during pagination.
export const pageLimit = 50;

View File

@@ -0,0 +1,37 @@
export const BETA_KEY = 'betaSwitchImex';
export const checkBeta = () => {
const cookie = document.cookie.split('; ').find(row => row.startsWith(BETA_KEY));
return cookie ? cookie.split('=')[1] === 'true' : false;
}
export const setBeta = (value) => {
const domain = window.location.hostname.split('.').slice(-2).join('.');
document.cookie = `${BETA_KEY}=${value}; path=/; domain=.${domain}`;
}
export const handleBeta = () => {
// If the current host name does not start with beta or test, then we don't need to do anything.
if (window.location.hostname.startsWith('localhost')) {
console.log('Not on beta or test, so no need to handle beta.');
return;
}
const isBeta = checkBeta();
const currentHostName = window.location.hostname;
// Beta is enabled, but the current host name does start with beta.
if (isBeta && !currentHostName.startsWith('beta')) {
const href= `${window.location.protocol}//beta.${currentHostName}${window.location.pathname}${window.location.search}${window.location.hash}`;
window.location.replace(href);
}
// Beta is not enabled, but the current host name does start with beta.
else if (!isBeta && currentHostName.startsWith('beta')) {
const href = `${window.location.protocol}//${currentHostName.replace('beta.', '')}${window.location.pathname}${window.location.search}${window.location.hash}`;
window.location.replace(href);
}
}
export default handleBeta;

View File

@@ -1388,60 +1388,62 @@
- active: - active:
_eq: true _eq: true
columns: columns:
- id
- created_at
- updated_at
- bodyshopid - bodyshopid
- make
- model
- year
- plate
- color - color
- vin - created_at
- fleetnumber
- purchasedate
- servicestartdate
- serviceenddate
- leaseenddate
- status
- nextservicekm
- nextservicedate
- damage
- notes
- fuel
- registrationexpires
- insuranceexpires
- dailycost - dailycost
- damage
- fleetnumber
- fuel
- id
- insuranceexpires
- leaseenddate
- make
- mileage - mileage
- model
- nextservicedate
- nextservicekm
- notes
- plate
- purchasedate
- readiness
- registrationexpires
- serviceenddate
- servicestartdate
- status
- updated_at
- vin
- year
select_permissions: select_permissions:
- role: user - role: user
permission: permission:
columns: columns:
- bodyshopid
- color
- created_at
- dailycost
- damage
- fleetnumber
- fuel
- id
- insuranceexpires - insuranceexpires
- leaseenddate - leaseenddate
- make
- mileage
- model
- nextservicedate - nextservicedate
- nextservicekm
- notes
- plate
- purchasedate - purchasedate
- readiness
- registrationexpires - registrationexpires
- serviceenddate - serviceenddate
- servicestartdate - servicestartdate
- dailycost
- fuel
- mileage
- nextservicekm
- color
- damage
- fleetnumber
- make
- model
- notes
- plate
- status - status
- updated_at
- vin - vin
- year - year
- created_at
- updated_at
- bodyshopid
- id
filter: filter:
bodyshop: bodyshop:
associations: associations:
@@ -1456,31 +1458,32 @@
- role: user - role: user
permission: permission:
columns: columns:
- bodyshopid
- color
- created_at
- dailycost
- damage
- fleetnumber
- fuel
- id
- insuranceexpires - insuranceexpires
- leaseenddate - leaseenddate
- make
- mileage
- model
- nextservicedate - nextservicedate
- nextservicekm
- notes
- plate
- purchasedate - purchasedate
- readiness
- registrationexpires - registrationexpires
- serviceenddate - serviceenddate
- servicestartdate - servicestartdate
- dailycost
- fuel
- mileage
- nextservicekm
- color
- damage
- fleetnumber
- make
- model
- notes
- plate
- status - status
- updated_at
- vin - vin
- year - year
- created_at
- updated_at
- bodyshopid
- id
filter: filter:
bodyshop: bodyshop:
associations: associations:
@@ -2020,24 +2023,24 @@
- active: - active:
_eq: true _eq: true
columns: columns:
- labor_rates
- percentage
- created_at - created_at
- updated_at
- employeeid - employeeid
- id - id
- labor_rates
- percentage
- teamid - teamid
- updated_at
select_permissions: select_permissions:
- role: user - role: user
permission: permission:
columns: columns:
- labor_rates
- percentage
- created_at - created_at
- updated_at
- employeeid - employeeid
- id - id
- labor_rates
- percentage
- teamid - teamid
- updated_at
filter: filter:
employee_team: employee_team:
bodyshop: bodyshop:
@@ -2052,13 +2055,13 @@
- role: user - role: user
permission: permission:
columns: columns:
- labor_rates
- percentage
- created_at - created_at
- updated_at
- employeeid - employeeid
- id - id
- labor_rates
- percentage
- teamid - teamid
- updated_at
filter: filter:
employee_team: employee_team:
bodyshop: bodyshop:
@@ -2120,21 +2123,23 @@
_eq: true _eq: true
columns: columns:
- active - active
- name
- created_at
- updated_at
- bodyshopid - bodyshopid
- created_at
- id - id
- max_load
- name
- updated_at
select_permissions: select_permissions:
- role: user - role: user
permission: permission:
columns: columns:
- active - active
- name
- created_at
- updated_at
- bodyshopid - bodyshopid
- created_at
- id - id
- max_load
- name
- updated_at
filter: filter:
bodyshop: bodyshop:
associations: associations:
@@ -2150,6 +2155,7 @@
columns: columns:
- active - active
- bodyshopid - bodyshopid
- max_load
- name - name
- updated_at - updated_at
filter: filter:

View File

@@ -0,0 +1,2 @@
alter table "public"."courtesycars" alter column "nextservicekm" set not null;
alter table "public"."courtesycars" alter column "nextservicekm" set default '0';

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."courtesycars" ALTER COLUMN "nextservicekm" drop default;
alter table "public"."courtesycars" alter column "nextservicekm" drop not null;

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"."courtesycars" add column "readiness" text
-- null;

View File

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

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"."employee_team_members" add column "max_load" numeric
-- not null default '10000';

View File

@@ -0,0 +1,2 @@
alter table "public"."employee_team_members" add column "max_load" numeric
not null default '10000';

View File

@@ -0,0 +1,3 @@
alter table "public"."employee_team_members" alter column "max_load" set default '10000'::numeric;
alter table "public"."employee_team_members" alter column "max_load" drop not null;
alter table "public"."employee_team_members" add column "max_load" numeric;

View File

@@ -0,0 +1 @@
alter table "public"."employee_team_members" drop column "max_load" cascade;

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"."employee_teams" add column "max_load" numeric
-- not null default '10000';

View File

@@ -0,0 +1,2 @@
alter table "public"."employee_teams" add column "max_load" numeric
not null default '10000';

56
libs/awsUtils.js Normal file
View File

@@ -0,0 +1,56 @@
require("dotenv").config({
path: require("path").resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const {isNil} = require('lodash');
const aws4 = require("aws4");
const {Connection, Client} = require("@opensearch-project/opensearch");
const {defaultProvider} = require("@aws-sdk/credential-provider-node");
const createAwsConnector = (credentials, region) => {
class AmazonConnection extends Connection {
buildRequestObject(params) {
const request = super.buildRequestObject(params);
request.service = "es";
request.region = region;
request.headers = request.headers || {};
request.headers["host"] = request.hostname;
return aws4.sign(request, credentials);
}
}
return {
Connection: AmazonConnection,
};
};
const getClient = async () => {
// We have manual configuration for OpenSearch,
// Return a client using these custom credentials
if (
!isNil(process.env.OPEN_SEARCH_PASSWORD) &&
!isNil(process.env.OPEN_SEARCH_USER) &&
!isNil(process.env.OPEN_SEARCH_HOST) &&
!isNil(process.env.OPEN_SEARCH_PROTOCOL)
) {
// The URI is currently being stored in its entirety, so strip protocol prior to rebuilding it.
const hostUrl = process.env.OPEN_SEARCH_HOST.replace(/^https?:\/\//i, '');
const node = `${process.env.OPEN_SEARCH_PROTOCOL}://${process.env.OPEN_SEARCH_USER}:${process.env.OPEN_SEARCH_PASSWORD}@${hostUrl}`;
return new Client({
node,
});
}
// Default to the AWS Credentials Provider.
const credentials = await defaultProvider()();
return new Client({
...createAwsConnector(credentials, "ca-central-1"),
node: process.env.OPEN_SEARCH_HOST,
});
};
module.exports = { getClient };

View File

@@ -1,59 +1,17 @@
const Dinero = require("dinero.js");
//const client = require("../graphql-client/graphql-client").client;
const _ = require("lodash");
const GraphQLClient = require("graphql-request").GraphQLClient;
const logger = require("./server/utils/logger");
const path = require("path");
const client = require("./server/graphql-client/graphql-client").client;
require("dotenv").config({ require("dotenv").config({
path: path.resolve( path: require("path").resolve(
process.cwd(), process.cwd(),
`.env.${process.env.NODE_ENV || "development"}` `.env.${process.env.NODE_ENV || "development"}`
), ),
}); });
const { Client, Connection } = require("@opensearch-project/opensearch");
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
const aws4 = require("aws4");
const { gql } = require("graphql-request");
const gqlclient = require("./server/graphql-client/graphql-client").client;
// const osClient = new Client({
// node: `https://imex:Wl0d8k@!@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com/`,
// });
var host = process.env.OPEN_SEARCH_HOST; // e.g. https://my-domain.region.es.amazonaws.com const {omit} = require("lodash");
const createAwsConnector = (credentials, region) => { const gqlClient = require("./server/graphql-client/graphql-client").client;
class AmazonConnection extends Connection { const getClient = require('./libs/awsUtils');
buildRequestObject(params) {
const request = super.buildRequestObject(params);
request.service = "es";
request.region = region;
request.headers = request.headers || {};
request.headers["host"] = request.hostname;
return aws4.sign(request, credentials);
}
}
return {
Connection: AmazonConnection,
};
};
const getClient = async () => {
const credentials = await defaultProvider()();
return new Client({
...createAwsConnector(credentials, "ca-central-1"),
node: host,
});
};
async function OpenSearchUpdateHandler(req, res) { async function OpenSearchUpdateHandler(req, res) {
try { try {
var osClient = await getClient(); const osClient = await getClient();
// const osClient = new Client({
// node: `https://imex:password@search-imexonline-search-ixp2stfvwp6qocjsowzjzyreoy.ca-central-1.es.amazonaws.com`,
// });
//Clear out all current documents //Clear out all current documents
// const deleteResult = await osClient.deleteByQuery({ // const deleteResult = await osClient.deleteByQuery({
@@ -67,11 +25,11 @@ async function OpenSearchUpdateHandler(req, res) {
// return; // return;
var batchSize = 1000; const batchSize = 1000;
var promiseQueue = []; const promiseQueue = [];
//Jobs Load. //Jobs Load.
const jobsData = await gqlclient.request(`query{jobs{ const jobsData = await gqlClient.request(`query{jobs{
id id
bodyshopid:shopid bodyshopid:shopid
clm_no clm_no
@@ -105,7 +63,7 @@ async function OpenSearchUpdateHandler(req, res) {
} }
//Owner Load //Owner Load
const ownersData = await gqlclient.request(`{ const ownersData = await gqlClient.request(`{
owners { owners {
id id
bodyshopid: shopid bodyshopid: shopid
@@ -131,7 +89,7 @@ async function OpenSearchUpdateHandler(req, res) {
} }
//Vehicles //Vehicles
const vehiclesData = await gqlclient.request(`{ const vehiclesData = await gqlClient.request(`{
vehicles { vehicles {
id id
bodyshopid: shopid bodyshopid: shopid
@@ -158,7 +116,7 @@ async function OpenSearchUpdateHandler(req, res) {
} }
//payments //payments
const paymentsData = await gqlclient.request(`{ const paymentsData = await gqlClient.request(`{
payments { payments {
id id
amount amount
@@ -198,7 +156,7 @@ async function OpenSearchUpdateHandler(req, res) {
slicedArray.forEach((payment) => { slicedArray.forEach((payment) => {
bulkOperation.push({ index: { _index: "payments", _id: payment.id } }); bulkOperation.push({ index: { _index: "payments", _id: payment.id } });
bulkOperation.push({ bulkOperation.push({
..._.omit(payment, ["job"]), ...omit(payment, ["job"]),
bodyshopid: payment.job.bodyshopid, bodyshopid: payment.job.bodyshopid,
}); });
}); });
@@ -206,7 +164,7 @@ async function OpenSearchUpdateHandler(req, res) {
} }
//bills //bills
const billsData = await gqlclient.request(`{ const billsData = await gqlClient.request(`{
bills { bills {
id id
date date
@@ -235,7 +193,7 @@ async function OpenSearchUpdateHandler(req, res) {
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({
..._.omit(bill, ["job"]), ...omit(bill, ["job"]),
bodyshopid: bill.job.bodyshopid, bodyshopid: bill.job.bodyshopid,
}); });
}); });

View File

@@ -34,6 +34,10 @@ const io = new Server(server, {
"http://localhost:3000", "http://localhost:3000",
"https://imex.online", "https://imex.online",
"https://www.imex.online", "https://www.imex.online",
"https://beta.test.imex.online",
"https://www.beta.test.imex.online",
"https://beta.imex.online",
"https://www.beta.imex.online",
], ],
methods: ["GET", "POST"], methods: ["GET", "POST"],
credentials: true, credentials: true,
@@ -224,6 +228,7 @@ app.post("/qbo/payments", fb.validateFirebaseIdToken, qbo.payments);
var data = require("./server/data/data"); var data = require("./server/data/data");
app.post("/data/ah", data.autohouse); app.post("/data/ah", data.autohouse);
app.post("/data/cc", data.claimscorp); app.post("/data/cc", data.claimscorp);
app.post("/data/kaizen", data.kaizen);
app.post("/record-handler/arms", data.arms); app.post("/record-handler/arms", data.arms);
var taskHandler = require("./server/tasks/tasks"); var taskHandler = require("./server/tasks/tasks");

View File

@@ -507,7 +507,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
Body: repairCosts.BodyLaborTotalCost.toFormat(CCDineroFormat), Body: repairCosts.BodyLaborTotalCost.toFormat(CCDineroFormat),
Paint: repairCosts.RefinishLaborTotalCost.toFormat(CCDineroFormat), Paint: repairCosts.RefinishLaborTotalCost.toFormat(CCDineroFormat),
Prep: Dinero().toFormat(CCDineroFormat), Prep: Dinero().toFormat(CCDineroFormat),
Frame: Dinero(job.job_totals.rates.laf.total).toFormat(CCDineroFormat), Frame: repairCosts.FrameLaborTotalCost.toFormat(CCDineroFormat),
Mech: repairCosts.MechanicalLaborTotalCost.toFormat(CCDineroFormat), Mech: repairCosts.MechanicalLaborTotalCost.toFormat(CCDineroFormat),
Glass: repairCosts.GlassLaborTotalCost.toFormat(CCDineroFormat), Glass: repairCosts.GlassLaborTotalCost.toFormat(CCDineroFormat),
Elec: repairCosts.ElectricalLaborTotalCost.toFormat(CCDineroFormat), Elec: repairCosts.ElectricalLaborTotalCost.toFormat(CCDineroFormat),

View File

@@ -1,3 +1,4 @@
exports.arms = require("./arms").default;
exports.autohouse = require("./autohouse").default; exports.autohouse = require("./autohouse").default;
exports.claimscorp = require("./claimscorp").default; exports.claimscorp = require("./claimscorp").default;
exports.arms = require("./arms").default; exports.kaizen = require("./kaizen").default;

837
server/data/kaizen.js Normal file
View File

@@ -0,0 +1,837 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment-timezone");
var builder = require("xmlbuilder2");
const _ = require("lodash");
const logger = require("../utils/logger");
const fs = require("fs");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;
const { sendServerEmail } = require("../email/sendemail");
const DineroFormat = "0,0.00";
const DateFormat = "MM/DD/YYYY";
const repairOpCodes = ["OP4", "OP9", "OP10"];
const replaceOpCodes = ["OP2", "OP5", "OP11", "OP12"];
const ftpSetup = {
host: process.env.KAIZEN_HOST,
port: process.env.KAIZEN_PORT,
username: process.env.KAIZEN_USER,
password: process.env.KAIZEN_PASSWORD,
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
algorithms: {
serverHostKey: [
"ssh-rsa",
"ssh-dss",
"rsa-sha2-256",
"rsa-sha2-512",
"ecdsa-sha2-nistp256",
"ecdsa-sha2-nistp384",
],
},
};
exports.default = async (req, res) => {
//Query for the List of Bodyshop Clients.
logger.log("kaizen-start", "DEBUG", "api", null, null);
const kaizenShopsIDs = ["SUMMIT", "STRATHMORE", "SUNRIDGE"];
const { bodyshops } = await client.request(queries.GET_KAIZEN_SHOPS, {
imexshopid: kaizenShopsIDs,
});
const specificShopIds = req.body.bodyshopIds; // ['uuid]
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
res.sendStatus(401);
return;
}
const allxmlsToUpload = [];
const allErrors = [];
try {
for (const bodyshop of specificShopIds
? bodyshops.filter((b) => specificShopIds.includes(b.id))
: bodyshops) {
logger.log("kaizen-start-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname,
});
const erroredJobs = [];
try {
const { jobs, bodyshops_by_pk } = await client.request(
queries.KAIZEN_QUERY,
{
bodyshopid: bodyshop.id,
start: start
? moment(start).startOf("hours")
: moment().subtract(2, "hours").startOf("hour"),
...(end && { end: moment(end).endOf("hours") }),
}
);
const kaizenObject = {
DataFeed: {
ShopInfo: {
ShopName: bodyshops_by_pk.shopname,
Jobs: jobs.map((j) =>
CreateRepairOrderTag(
{ ...j, bodyshop: bodyshops_by_pk },
function ({ job, error }) {
erroredJobs.push({ job: job, error: error.toString() });
}
)
),
},
},
};
if (erroredJobs.length > 0) {
logger.log("kaizen-failed-jobs", "ERROR", "api", bodyshop.id, {
count: erroredJobs.length,
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)),
});
}
var ret = builder
.create(
{
// version: "1.0",
// encoding: "UTF-8",
//keepNullNodes: true,
},
kaizenObject
)
.end({ allowEmptyTags: true });
allxmlsToUpload.push({
count: kaizenObject.DataFeed.ShopInfo.Jobs.length,
xml: ret,
filename: `${bodyshop.shopname}-${moment().format(
"YYYYMMDDTHHMMss"
)}.xml`,
});
logger.log("kaizen-end-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname,
});
} catch (error) {
//Error at the shop level.
logger.log("kaizen-error-shop", "ERROR", "api", bodyshop.id, {
...error,
});
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
shopname: bodyshop.shopname,
fatal: true,
errors: [error.toString()],
});
} finally {
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
shopname: bodyshop.shopname,
errors: erroredJobs.map((ej) => ({
ro_number: ej.job?.ro_number,
jobid: ej.job?.id,
error: ej.error,
})),
});
}
}
if (skipUpload) {
for (const xmlObj of allxmlsToUpload) {
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
}
res.json(allxmlsToUpload);
sendServerEmail({
subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
Uploaded: ${JSON.stringify(
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
null,
2
)}
`,
});
return;
}
let sftp = new Client();
sftp.on("error", (errors) =>
logger.log("kaizen-sftp-error", "ERROR", "api", null, {
...errors,
})
);
try {
//Connect to the FTP and upload all.
await sftp.connect(ftpSetup);
for (const xmlObj of allxmlsToUpload) {
logger.log("kaizen-sftp-upload", "DEBUG", "api", null, {
filename: xmlObj.filename,
});
const uploadResult = await sftp.put(
Buffer.from(xmlObj.xml),
`/${xmlObj.filename}`
);
logger.log("kaizen-sftp-upload-result", "DEBUG", "api", null, {
uploadResult,
});
}
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
} catch (error) {
logger.log("kaizen-sftp-error", "ERROR", "api", null, {
...error,
});
} finally {
sftp.end();
}
sendServerEmail({
subject: `Kaizen Report ${moment().format("MM-DD-YY")}`,
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
Uploaded: ${JSON.stringify(
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
null,
2
)}
`,
});
res.sendStatus(200);
} catch (error) {
res.status(200).json(error);
}
};
const CreateRepairOrderTag = (job, errorCallback) => {
//Level 2
if (!job.job_totals) {
errorCallback({
jobid: job.id,
job: job,
ro_number: job.ro_number,
error: { toString: () => "No job totals for RO." },
});
return {};
}
const repairCosts = CreateCosts(job);
try {
const ret = {
JobID: job.id,
RoNumber: job.ro_number,
JobStatus: job.tlos_ind
? "Total Loss"
: job.ro_number
? job.status
: "Estimate",
Customer: {
CompanyName: job.ownr_co_nm?.trim() || "",
FirstName: job.ownr_fn?.trim() || "",
LastName: job.ownr_ln?.trim() || "",
Address1: job.ownr_addr1?.trim() || "",
Address2: job.ownr_addr2?.trim() || "",
City: job.ownr_city?.trim() || "",
State: job.ownr_st?.trim() || "",
Zip: job.ownr_zip?.trim() || "",
},
Vehicle: {
Year: job.v_model_yr
? parseInt(job.v_model_yr.match(/\d/g))
? parseInt(job.v_model_yr.match(/\d/g).join(""), 10)
: ""
: "",
Make: job.v_make_desc || "",
Model: job.v_model_desc || "",
BodyStyle: job.vehicle?.v_bstyle || "",
Color: job.v_color || "",
VIN: job.v_vin || "",
PlateNo: job.plate_no || "",
},
InsuranceCompany: job.ins_co_nm || "",
Claim: job.clm_no || "",
Contacts: {
CSR: job.employee_csr_rel
? `${
job.employee_csr_rel.last_name
? job.employee_csr_rel.last_name
: ""
}${job.employee_csr_rel.last_name ? ", " : ""}${
job.employee_csr_rel.first_name
? job.employee_csr_rel.first_name
: ""
}`
: "",
Estimator: `${job.est_ct_ln ? job.est_ct_ln : ""}${
job.est_ct_ln ? ", " : ""
}${job.est_ct_fn ? job.est_ct_fn : ""}`,
},
Dates: {
DateEstimated:
(job.date_estimated &&
moment(job.date_estimated).format(DateFormat)) ||
"",
DateOpened:
(job.date_opened && moment(job.date_opened).format(DateFormat)) || "",
DateScheduled:
(job.scheduled_in &&
moment(job.scheduled_in)
.tz(job.bodyshop.timezone)
.format(DateFormat)) ||
"",
DateArrived:
(job.actual_in &&
moment(job.actual_in)
.tz(job.bodyshop.timezone)
.format(DateFormat)) ||
"",
DateStart: job.date_repairstarted
? (job.date_repairstarted &&
moment(job.date_repairstarted)
.tz(job.bodyshop.timezone)
.format(DateFormat)) ||
""
: (job.actual_in &&
moment(job.actual_in)
.tz(job.bodyshop.timezone)
.format(DateFormat)) ||
"",
DateScheduledCompletion:
(job.scheduled_completion &&
moment(job.scheduled_completion)
.tz(job.bodyshop.timezone)
.format(DateFormat)) ||
"",
DateCompleted:
(job.actual_completion &&
moment(job.actual_completion)
.tz(job.bodyshop.timezone)
.format(DateFormat)) ||
"",
DateScheduledDelivery:
(job.scheduled_delivery &&
moment(job.scheduled_delivery)
.tz(job.bodyshop.timezone)
.format(DateFormat)) ||
"",
DateDelivered:
(job.actual_delivery &&
moment(job.actual_delivery)
.tz(job.bodyshop.timezone)
.format(DateFormat)) ||
"",
DateInvoiced:
(job.date_invoiced &&
moment(job.date_invoiced)
.tz(job.bodyshop.timezone)
.format(DateFormat)) ||
"",
DateExported:
(job.date_exported &&
moment(job.date_exported)
.tz(job.bodyshop.timezone)
.format(DateFormat)) ||
"",
},
Sales: {
Labour: {
Aluminum: Dinero(job.job_totals.rates.laa.total).toFormat(
DineroFormat
),
Body: Dinero(job.job_totals.rates.lab.total).toFormat(DineroFormat),
Diagnostic: Dinero(job.job_totals.rates.lad.total).toFormat(
DineroFormat
),
Electrical: Dinero(job.job_totals.rates.lae.total).toFormat(
DineroFormat
),
Frame: Dinero(job.job_totals.rates.laf.total).toFormat(DineroFormat),
Glass: Dinero(job.job_totals.rates.lag.total).toFormat(DineroFormat),
Mechanical: Dinero(job.job_totals.rates.lam.total).toFormat(
DineroFormat
),
OtherLabour: Dinero(job.job_totals.rates.la1.total)
.add(Dinero(job.job_totals.rates.la2.total))
.add(Dinero(job.job_totals.rates.la3.total))
.add(Dinero(job.job_totals.rates.la4.total))
.add(Dinero(job.job_totals.rates.lau.total))
.toFormat(DineroFormat),
Refinish: Dinero(job.job_totals.rates.lar.total).toFormat(
DineroFormat
),
Structural: Dinero(job.job_totals.rates.las.total).toFormat(
DineroFormat
),
},
Materials: {
Body: Dinero(job.job_totals.rates.mash.total).toFormat(DineroFormat),
Refinish: Dinero(job.job_totals.rates.mapa.total).toFormat(
DineroFormat
),
},
Parts: {
Aftermarket: Dinero(
job.job_totals.parts.parts.list.PAA &&
job.job_totals.parts.parts.list.PAA.total
).toFormat(DineroFormat),
LKQ: Dinero(
job.job_totals.parts.parts.list.PAL &&
job.job_totals.parts.parts.list.PAL.total
).toFormat(DineroFormat),
OEM: Dinero(
job.job_totals.parts.parts.list.PAN &&
job.job_totals.parts.parts.list.PAN.total
)
.add(
Dinero(
job.job_totals.parts.parts.list.PAP &&
job.job_totals.parts.parts.list.PAP.total
)
)
.toFormat(DineroFormat),
OtherParts: Dinero(
job.job_totals.parts.parts.list.PAO &&
job.job_totals.parts.parts.list.PAO.total
).toFormat(DineroFormat),
Reconditioned: Dinero(
job.job_totals.parts.parts.list.PAM &&
job.job_totals.parts.parts.list.PAM.total
).toFormat(DineroFormat),
TotalParts: Dinero(
job.job_totals.parts.parts.list.PAA &&
job.job_totals.parts.parts.list.PAA.total
)
.add(
Dinero(
job.job_totals.parts.parts.list.PAL &&
job.job_totals.parts.parts.list.PAL.total
)
)
.add(
Dinero(
job.job_totals.parts.parts.list.PAN &&
job.job_totals.parts.parts.list.PAN.total
)
)
.add(
Dinero(
job.job_totals.parts.parts.list.PAO &&
job.job_totals.parts.parts.list.PAO.total
)
)
.add(
Dinero(
job.job_totals.parts.parts.list.PAM &&
job.job_totals.parts.parts.list.PAM.total
)
)
.toFormat(DineroFormat),
},
OtherSales: Dinero(job.job_totals.additional.storage).toFormat(
DineroFormat
),
Sublet: Dinero(job.job_totals.parts.sublets.total).toFormat(
DineroFormat
),
Towing: Dinero(job.job_totals.additional.towing).toFormat(DineroFormat),
ATS:
job.job_totals.additional.additionalCostItems.includes(
"ATS Amount"
) === true
? Dinero(
job.job_totals.additional.additionalCostItems[
job.job_totals.additional.additionalCostItems.indexOf(
"ATS Amount"
)
].total
).toFormat(DineroFormat)
: Dinero().toFormat(DineroFormat),
SaleSubtotal: Dinero(job.job_totals.totals.subtotal).toFormat(
DineroFormat
),
Tax: Dinero(job.job_totals.totals.local_tax)
.add(Dinero(job.job_totals.totals.state_tax))
.add(Dinero(job.job_totals.totals.federal_tax))
.add(Dinero(job.job_totals.additional.pvrt))
.toFormat(DineroFormat),
SaleTotal: Dinero(job.job_totals.totals.total_repairs).toFormat(
DineroFormat
),
},
SaleHours: {
Aluminum: job.job_totals.rates.laa.hours.toFixed(2),
Body: job.job_totals.rates.lab.hours.toFixed(2),
Diagnostic: job.job_totals.rates.lad.hours.toFixed(2),
Electrical: job.job_totals.rates.lae.hours.toFixed(2),
Frame: job.job_totals.rates.laf.hours.toFixed(2),
Glass: job.job_totals.rates.lag.hours.toFixed(2),
Mechanical: job.job_totals.rates.lam.hours.toFixed(2),
Other: (
job.job_totals.rates.la1.hours +
job.job_totals.rates.la2.hours +
job.job_totals.rates.la3.hours +
job.job_totals.rates.la4.hours +
job.job_totals.rates.lau.hours
).toFixed(2),
Refinish: job.job_totals.rates.lar.hours.toFixed(2),
Structural: job.job_totals.rates.las.hours.toFixed(2),
TotalHours: job.joblines
.reduce((acc, val) => acc + val.mod_lb_hrs, 0)
.toFixed(2),
},
Costs: {
Labour: {
Aluminum: repairCosts.AluminumLabourTotalCost.toFormat(DineroFormat),
Body: repairCosts.BodyLabourTotalCost.toFormat(DineroFormat),
Diagnostic:
repairCosts.DiagnosticLabourTotalCost.toFormat(DineroFormat),
Electrical:
repairCosts.ElectricalLabourTotalCost.toFormat(DineroFormat),
Frame: repairCosts.FrameLabourTotalCost.toFormat(DineroFormat),
Glass: repairCosts.GlassLabourTotalCost.toFormat(DineroFormat),
Mechancial:
repairCosts.MechanicalLabourTotalCost.toFormat(DineroFormat),
OtherLabour: repairCosts.LabourMiscTotalCost.toFormat(DineroFormat),
Refinish: repairCosts.RefinishLabourTotalCost.toFormat(DineroFormat),
Structural:
repairCosts.StructuralLabourTotalCost.toFormat(DineroFormat),
TotalLabour: repairCosts.LabourTotalCost.toFormat(DineroFormat),
},
Materials: {
Body: repairCosts.BMTotalCost.toFormat(DineroFormat),
Refinish: repairCosts.PMTotalCost.toFormat(DineroFormat),
},
Parts: {
Aftermarket: repairCosts.PartsAMCost.toFormat(DineroFormat),
LKQ: repairCosts.PartsRecycledCost.toFormat(DineroFormat),
OEM: repairCosts.PartsOemCost.toFormat(DineroFormat),
OtherCost: repairCosts.PartsOtherCost.toFormat(DineroFormat),
Reconditioned:
repairCosts.PartsReconditionedCost.toFormat(DineroFormat),
TotalParts: repairCosts.PartsAMCost.add(repairCosts.PartsRecycledCost)
.add(repairCosts.PartsReconditionedCost)
.add(repairCosts.PartsOemCost)
.add(repairCosts.PartsOtherCost)
.toFormat(DineroFormat),
},
Sublet: repairCosts.SubletTotalCost.toFormat(DineroFormat),
Towing: repairCosts.TowingTotalCost.toFormat(DineroFormat),
ATS: Dinero().toFormat(DineroFormat),
Storage: repairCosts.StorageTotalCost.toFormat(DineroFormat),
CostTotal: repairCosts.TotalCost.toFormat(DineroFormat),
},
CostHours: {
Aluminum: repairCosts.AluminumLabourTotalHrs.toFixed(2),
Body: repairCosts.BodyLabourTotalHrs.toFixed(2),
Diagnostic: repairCosts.DiagnosticLabourTotalHrs.toFixed(2),
Refinish: repairCosts.RefinishLabourTotalHrs.toFixed(2),
Frame: repairCosts.FrameLabourTotalHrs.toFixed(2),
Mechanical: repairCosts.MechanicalLabourTotalHrs.toFixed(2),
Glass: repairCosts.GlassLabourTotalHrs.toFixed(2),
Electrical: repairCosts.ElectricalLabourTotalHrs.toFixed(2),
Structural: repairCosts.StructuralLabourTotalHrs.toFixed(2),
Other: repairCosts.LabourMiscTotalHrs.toFixed(2),
CostTotalHours: repairCosts.TotalHrs.toFixed(2),
},
};
return ret;
} catch (error) {
logger.log("kaizen-job-calculate-error", "ERROR", "api", null, {
error,
});
errorCallback({ jobid: job.id, ro_number: job.ro_number, error });
}
};
const CreateCosts = (job) => {
//Create a mapping based on AH Requirements
//For DMS, the keys in the object below are the CIECA part types.
const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
//At the bill level.
bill_val.billlines.map((line_val) => {
//At the bill line level.
if (!bill_acc[line_val.cost_center])
bill_acc[line_val.cost_center] = Dinero();
bill_acc[line_val.cost_center] = bill_acc[line_val.cost_center].add(
Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1)
);
return null;
});
return bill_acc;
}, {});
//If the hourly rates for job costing are set, add them in.
if (
job.bodyshop.jc_hourly_rates &&
(job.bodyshop.jc_hourly_rates.mapa ||
typeof job.bodyshop.jc_hourly_rates.mapa === "number" ||
isNaN(job.bodyshop.jc_hourly_rates.mapa) === false)
) {
if (
!billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
]
)
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = Dinero();
if (job.bodyshop.use_paint_scale_data === true) {
if (job.mixdata.length > 0) {
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = Dinero({
amount: Math.round(
((job.mixdata[0] && job.mixdata[0].totalliquidcost) || 0) * 100
),
});
} else {
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
].add(
Dinero({
amount: Math.round(
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mapa * 100) ||
0
),
}).multiply(job.job_totals.rates.mapa.hours)
);
}
} else {
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MAPA
].add(
Dinero({
amount: Math.round(
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mapa * 100) ||
0
),
}).multiply(job.job_totals.rates.mapa.hours)
);
}
}
if (job.bodyshop.jc_hourly_rates && job.bodyshop.jc_hourly_rates.mash) {
if (
!billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
]
)
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
] = Dinero();
billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
] = billTotalsByCostCenters[
job.bodyshop.md_responsibility_centers.defaults.costs.MASH
].add(
Dinero({
amount: Math.round(
(job.bodyshop.jc_hourly_rates &&
job.bodyshop.jc_hourly_rates.mash * 100) ||
0
),
}).multiply(job.job_totals.rates.mash.hours)
);
}
//Uses CIECA Labour types.
const ticketTotalsByCostCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => {
//At the invoice level.
if (!ticket_acc[ticket_val.cost_center])
ticket_acc[ticket_val.cost_center] = Dinero();
ticket_acc[ticket_val.cost_center] = ticket_acc[
ticket_val.cost_center
].add(
Dinero({
amount: Math.round((ticket_val.rate || 0) * 100),
}).multiply(
(ticket_val.flat_rate
? ticket_val.productivehrs
: ticket_val.actualhrs) || 0
)
);
return ticket_acc;
},
{}
);
const ticketHrsByCostCenter = job.timetickets.reduce(
(ticket_acc, ticket_val) => {
//At the invoice level.
if (!ticket_acc[ticket_val.cost_center])
ticket_acc[ticket_val.cost_center] = 0;
ticket_acc[ticket_val.cost_center] =
ticket_acc[ticket_val.cost_center] +
(ticket_val.flat_rate
? ticket_val.productivehrs
: ticket_val.actualhrs) || 0;
return ticket_acc;
},
{}
);
//CIECA STANDARD MAPPING OBJECT.
const ciecaObj = {
ATS: "ATS",
LA1: "LA1",
LA2: "LA2",
LA3: "LA3",
LA4: "LA4",
LAA: "LAA",
LAB: "LAB",
LAD: "LAD",
LAE: "LAE",
LAF: "LAF",
LAG: "LAG",
LAM: "LAM",
LAR: "LAR",
LAS: "LAS",
LAU: "LAU",
PAA: "PAA",
PAC: "PAC",
PAG: "PAG",
PAL: "PAL",
PAM: "PAM",
PAN: "PAN",
PAO: "PAO",
PAP: "PAP",
PAR: "PAR",
PAS: "PAS",
TOW: "TOW",
MAPA: "MAPA",
MASH: "MASH",
PASL: "PASL",
};
const defaultCosts =
job.bodyshop.cdk_dealerid || job.bodyshop.pbs_serialnumber
? ciecaObj
: job.bodyshop.md_responsibility_centers.defaults.costs;
return {
PartsTotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
if (
key !== defaultCosts.PAS &&
key !== defaultCosts.PASL &&
key !== defaultCosts.MAPA &&
key !== defaultCosts.MASH &&
key !== defaultCosts.TOW
)
return acc.add(billTotalsByCostCenters[key]);
return acc;
}, Dinero()),
PartsOemCost: (billTotalsByCostCenters[defaultCosts.PAN] || Dinero()).add(
billTotalsByCostCenters[defaultCosts.PAP] || Dinero()
),
PartsAMCost: billTotalsByCostCenters[defaultCosts.PAA] || Dinero(),
PartsReconditionedCost:
billTotalsByCostCenters[defaultCosts.PAM] || Dinero(),
PartsRecycledCost: billTotalsByCostCenters[defaultCosts.PAL] || Dinero(),
PartsOtherCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
SubletTotalCost:
billTotalsByCostCenters[defaultCosts.PAS] ||
Dinero(billTotalsByCostCenters[defaultCosts.PASL] || Dinero()),
AluminumLabourTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAA] || Dinero(),
AluminumLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAA] || 0,
BodyLabourTotalCost: ticketTotalsByCostCenter[defaultCosts.LAB] || Dinero(),
BodyLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAB] || 0,
DiagnosticLabourTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAD] || Dinero(),
DiagnosticLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAD] || 0,
ElectricalLabourTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAE] || Dinero(),
ElectricalLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAE] || 0,
FrameLabourTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAF] || Dinero(),
FrameLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAF] || 0,
GlassLabourTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAG] || Dinero(),
GlassLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAG] || 0,
LabourMiscTotalCost: (
ticketTotalsByCostCenter[defaultCosts.LA1] || Dinero()
)
.add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA2] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA3] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LA4] || Dinero())
.add(ticketTotalsByCostCenter[defaultCosts.LAU] || Dinero()),
LabourMiscTotalHrs:
(ticketHrsByCostCenter[defaultCosts.LA1] || 0) +
(ticketHrsByCostCenter[defaultCosts.LA2] || 0) +
(ticketHrsByCostCenter[defaultCosts.LA3] || 0) +
(ticketHrsByCostCenter[defaultCosts.LA4] || 0) +
(ticketHrsByCostCenter[defaultCosts.LAU] || 0),
MechanicalLabourTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAM] || Dinero(),
MechanicalLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAM] || 0,
RefinishLabourTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAR] || Dinero(),
RefinishLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAR] || 0,
StructuralLabourTotalCost:
ticketTotalsByCostCenter[defaultCosts.LAS] || Dinero(),
StructuralLabourTotalHrs: ticketHrsByCostCenter[defaultCosts.LAS] || 0,
PMTotalCost: billTotalsByCostCenters[defaultCosts.MAPA] || Dinero(),
BMTotalCost: billTotalsByCostCenters[defaultCosts.MASH] || Dinero(),
MiscTotalCost: billTotalsByCostCenters[defaultCosts.PAO] || Dinero(),
TowingTotalCost: billTotalsByCostCenters[defaultCosts.TOW] || Dinero(),
StorageTotalCost: Dinero(),
DetailTotal: Dinero(),
DetailTotalCost: Dinero(),
SalesTaxTotalCost: Dinero(),
LabourTotalCost: Object.keys(ticketTotalsByCostCenter).reduce(
(acc, key) => {
return acc.add(ticketTotalsByCostCenter[key]);
},
Dinero()
),
TotalCost: Object.keys(billTotalsByCostCenters).reduce((acc, key) => {
return acc.add(billTotalsByCostCenters[key]);
}, Dinero()),
TotalHrs: job.timetickets.reduce((acc, ticket_val) => {
return (
acc +
(ticket_val.flat_rate
? ticket_val.productivehrs
: ticket_val.actualhrs) || 0
);
}, 0),
};
};

View File

@@ -50,7 +50,7 @@ exports.createUser = async (req, res) => {
`, `,
{ {
user: { user: {
email, email: email.toLowerCase(),
authid: userRecord.uid, authid: userRecord.uid,
associations: { associations: {
data: [{ shopid, authlevel, active: true }], data: [{ shopid, authlevel, active: true }],

View File

@@ -1070,6 +1070,183 @@ query ENTEGRAL_EXPORT($bodyshopid: uuid!) {
} }
}`; }`;
exports.KAIZEN_QUERY = `query KAIZEN_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) {
bodyshops_by_pk(id: $bodyshopid){
id
shopname
address1
city
state
zip_post
country
phone
last_name_first
md_ro_statuses
md_order_statuses
md_responsibility_centers
jc_hourly_rates
cdk_dealerid
pbs_serialnumber
use_paint_scale_data
timezone
}
jobs(where: {_and: [{updated_at: {_gt: $start}}, {updated_at: {_lte: $end}}, {shopid: {_eq: $bodyshopid}}]}) {
actual_completion
actual_delivery
actual_in
asgn_date
bills {
billlines {
actual_cost
cost_center
id
quantity
}
federal_tax_rate
id
is_credit_memo
local_tax_rate
state_tax_rate
}
created_at
clm_no
date_estimated
date_exported
date_invoiced
date_open
date_repairstarted
employee_body_rel {
first_name
last_name
employee_number
id
}
employee_csr_rel {
first_name
last_name
employee_number
id
}
employee_prep_rel {
first_name
last_name
employee_number
id
}
employee_refinish_rel {
first_name
last_name
employee_number
id
}
est_ct_fn
est_ct_ln
id
ins_co_nm
joblines(where: {removed: {_eq: false}}) {
act_price
billlines(order_by: {bill: {date: desc_nulls_last}} limit: 1) {
actual_cost
actual_price
quantity
bill {
vendor {
name
}
invoice_number
date
}
}
db_price
id
lbr_op
line_desc
line_ind
line_no
mod_lb_hrs
mod_lbr_ty
parts_order_lines(order_by: {parts_order: {order_date: desc_nulls_last}} limit: 1){
parts_order{
id
order_date
}
}
part_qty
part_type
profitcenter_part
profitcenter_labor
prt_dsmk_m
prt_dsmk_p
oem_partno
status
}
job_totals
loss_date
mixdata(limit: 1, order_by: {updated_at: desc}) {
jobid
totalliquidcost
}
ownr_addr1
ownr_addr2
ownr_city
ownr_co_nm
ownr_fn
ownr_ln
ownr_st
ownr_zip
parts_orders(limit: 1, order_by: {created_at: desc}) {
created_at
}
parts_tax_rates
plate_no
rate_la1
rate_la2
rate_la3
rate_la4
rate_laa
rate_lab
rate_lad
rate_lae
rate_laf
rate_lag
rate_lam
rate_lar
rate_las
rate_lau
rate_ma2s
rate_ma2t
rate_ma3s
rate_mabl
rate_macs
rate_mahw
rate_matd
rate_mapa
rate_mash
ro_number
scheduled_completion
scheduled_delivery
scheduled_in
status
timetickets {
id
rate
cost_center
actualhrs
productivehrs
flat_rate
}
tlos_ind
v_color
v_model_yr
v_model_desc
v_make_desc
v_vin
vehicle {
v_bstyle
}
}
}`;
exports.UPDATE_JOB = ` exports.UPDATE_JOB = `
mutation UPDATE_JOB($jobId: uuid!, $job: jobs_set_input!) { mutation UPDATE_JOB($jobId: uuid!, $job: jobs_set_input!) {
update_jobs(where: { id: { _eq: $jobId } }, _set: $job) { update_jobs(where: { id: { _eq: $jobId } }, _set: $job) {
@@ -1542,7 +1719,7 @@ exports.GET_CLAIMSCORP_SHOPS = `query GET_CLAIMSCORP_SHOPS {
} }
}`; }`;
exports.GET_ENTEGRAL_SHOPS = `query GET_AUTOHOUSE_SHOPS { exports.GET_ENTEGRAL_SHOPS = `query GET_ENTEGRAL_SHOPS {
bodyshops(where: {entegral_id: {_is_null: false}, _or: {entegral_id: {_neq: ""}}}){ bodyshops(where: {entegral_id: {_is_null: false}, _or: {entegral_id: {_neq: ""}}}){
id id
shopname shopname
@@ -1562,6 +1739,26 @@ exports.GET_ENTEGRAL_SHOPS = `query GET_AUTOHOUSE_SHOPS {
} }
}`; }`;
exports.GET_KAIZEN_SHOPS = `query GET_KAIZEN_SHOPS($imexshopid: [String]) {
bodyshops(where: {imexshopid: {_in: $imexshopid}}){
id
shopname
address1
city
state
zip_post
country
phone
md_ro_statuses
md_order_statuses
autohouseid
md_responsibility_centers
jc_hourly_rates
imexshopid
timezone
}
}`;
exports.DELETE_ALL_DMS_VEHICLES = `mutation DELETE_ALL_DMS_VEHICLES{ exports.DELETE_ALL_DMS_VEHICLES = `mutation DELETE_ALL_DMS_VEHICLES{
delete_dms_vehicles(where: {}) { delete_dms_vehicles(where: {}) {
affected_rows affected_rows

View File

@@ -132,6 +132,7 @@ exports.payment_refund = async (req, res) => {
exports.generate_payment_url = async (req, res) => { exports.generate_payment_url = async (req, res) => {
logger.log("intellipay-payment-url", "DEBUG", req.user?.email, null, null); logger.log("intellipay-payment-url", "DEBUG", req.user?.email, null, null);
const shopCredentials = await getShopCredentials(req.body.bodyshop); const shopCredentials = await getShopCredentials(req.body.bodyshop);
try { try {
const options = { const options = {
method: "POST", method: "POST",
@@ -139,7 +140,12 @@ exports.generate_payment_url = async (req, res) => {
//TODO: Move these to environment variables/database. //TODO: Move these to environment variables/database.
data: qs.stringify({ data: qs.stringify({
...shopCredentials, ...shopCredentials,
...req.body, //...req.body,
amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat(
"0.00"
),
account: req.body.account,
invoice: req.body.invoice,
createshorturl: true, createshorturl: true,
//The postback URL is set at the CP teller global terminal settings page. //The postback URL is set at the CP teller global terminal settings page.
}), }),

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