Compare commits

...

235 Commits

Author SHA1 Message Date
Patrick Fic
774e4cdf94 IO-1430 Add note on last contacted update. 2021-10-07 16:26:48 -07:00
Patrick Fic
65ade5cab8 IO-1428 add ph2 to global search. 2021-10-07 16:00:35 -07:00
Patrick Fic
bf21a073fb IO-1415 Update cache on line delete. 2021-10-07 15:48:29 -07:00
Patrick Fic
51f3b5927b IO-1428 IO-1338 Add ownr_ph2. 2021-10-07 09:47:10 -07:00
Patrick Fic
42c779f368 IO-1415 Default tax part to true. 2021-10-07 09:19:37 -07:00
Patrick Fic
0b2efa31b5 IO-1375 IO-1383 CDK Tax Addition 2021-10-06 13:37:14 -07:00
Patrick Fic
17baa8fcb2 IO-1404 Rates in shop info minimums 2021-10-05 16:36:53 -07:00
Patrick Fic
fd53eb92e6 Merge branch 'test' into release/2021-10-08 2021-10-05 16:19:41 -07:00
Patrick Fic
6521c0bfb8 IO-1413 Change color of kanban background 2021-10-05 14:22:45 -07:00
Patrick Fic
50489dd682 IO-1415 Add tax part & refresh line ticket on line edit 2021-10-05 14:15:21 -07:00
Patrick Fic
220afa5add IO-1405 resolve shift label issues. 2021-10-05 14:05:16 -07:00
Patrick Fic
9d549b02fe IO-1410 Remove ded from lbr lines on reconciliation. 2021-10-05 13:59:43 -07:00
Patrick Fic
c423e61ce8 IO-1422 Resolve send email with attachments. 2021-10-05 11:47:37 -07:00
Patrick Fic
65550c7bf4 IO-1436 App tax part fix to all regions. 2021-10-05 11:36:21 -07:00
Patrick Fic
cdb4da9e5f IO-1412 Resolve production list saving and loading. 2021-10-05 09:40:25 -07:00
Patrick Fic
b59a5303c6 IO-1430 add Last Contacted date to job. 2021-10-05 09:12:44 -07:00
Patrick Fic
9a72abbed0 IO-1426 Add time to date detail card. 2021-10-05 08:53:20 -07:00
Patrick Fic
762a5ff01b IO-1432 Add offset for speed print. 2021-10-05 08:49:38 -07:00
Patrick Fic
2f6571e703 Package updates. 2021-10-05 07:59:51 -07:00
Patrick Fic
80c90f1819 IO-1399 Parts Order Modal Updates 2021-10-04 16:13:33 -07:00
Patrick Fic
4b0c2c60a2 IO-1398 Parts Order Mark Duplication 2021-10-04 16:00:05 -07:00
Patrick Fic
ee7d7d2f6a IO-1397 Preview cards consistency updates. 2021-10-04 15:55:43 -07:00
Patrick Fic
0ad670013b IO-1395 Add missing info to job preview. 2021-10-04 15:48:28 -07:00
Patrick Fic
064a66aa66 IO-1329 Filtering on bill line posting 2021-10-04 14:37:37 -07:00
Patrick Fic
f46c19b152 Merged in release/2021-10-08 (pull request #239)
release/2021-10-08

Approved-by: Patrick Fic
2021-10-04 20:35:21 +00:00
Patrick Fic
ea2c583b26 IO-1419 Resolve hsaura migration issues with new field. 2021-10-04 13:20:35 -07:00
Patrick Fic
7bd2a90141 Merged in release/2021-10-08 (pull request #238)
release/2021-10-08

Approved-by: Patrick Fic
2021-10-04 19:11:34 +00:00
Patrick Fic
632549dd9d IO-1406 Parts Order label update 2021-10-04 12:08:47 -07:00
Patrick Fic
70ef274821 IO-1404 IO-1403 Remove negative value options for 2021-10-04 12:08:29 -07:00
Patrick Fic
7d9759fda4 IO-1419 Added comments to parts orders. 2021-10-04 11:59:33 -07:00
Patrick Fic
0fd06d5e4e IO-1421 Add day of week to smart scheduling buttons. 2021-10-04 11:48:55 -07:00
Patrick Fic
4995e44e06 IO-1424 Add other referral source 2021-10-04 11:43:33 -07:00
Patrick Fic
96f292f61c JSR CHange 2021-10-04 07:57:14 -07:00
Patrick Fic
c62f5e3911 Merged in release/2021-10-08 (pull request #235)
Resolve CI.

Approved-by: Patrick Fic
2021-10-01 22:56:48 +00:00
Patrick Fic
9f1d184081 Resolve CI. 2021-10-01 15:56:24 -07:00
Patrick Fic
29e596eedb Merged in release/2021-10-08 (pull request #234)
release/2021-10-08

Approved-by: Patrick Fic
2021-10-01 22:41:36 +00:00
Patrick Fic
6e28afda67 IO-1417 Resolve custom templates printing. 2021-10-01 15:41:17 -07:00
Patrick Fic
eca7e9fba1 Merged in release/2021-10-08 (pull request #233)
IO-1392 Add IOU Form to Print Center

Approved-by: Patrick Fic
2021-10-01 16:25:04 +00:00
Patrick Fic
aa2ac2b296 IO-1392 Add IOU Form to Print Center 2021-10-01 09:24:37 -07:00
Patrick Fic
ce57752b95 Merged in release/2021-10-08 (pull request #232)
release/2021-10-08

Approved-by: Patrick Fic
2021-10-01 00:49:16 +00:00
Patrick Fic
63163c6459 IO1387 Add tax part override for non-R&R lines & Package Updates 2021-09-30 17:48:31 -07:00
Patrick Fic
2712ee5c0b IO-1416 Add missing LAA to resp center setup. 2021-09-30 09:44:15 -07:00
Patrick Fic
4179943df6 Merged in release/2021-10-01 (pull request #230)
Release/2021 10 01
2021-09-29 22:55:42 +00:00
Patrick Fic
8bb8eee384 Remove open replay tracker. 2021-09-29 14:38:40 -07:00
Patrick Fic
85d79a8d7f Remove extra console logs. 2021-09-29 12:03:21 -07:00
Patrick Fic
d38dd67c2f Merged in release/2021-10-01 (pull request #229)
release/2021-10-01

Approved-by: Patrick Fic
2021-09-29 15:50:34 +00:00
Patrick Fic
e73d082eab IO-1349 Resolve manual job creation. 2021-09-29 08:49:55 -07:00
Patrick Fic
2637538d9a IO-256 QBO Payables 2021-09-29 08:43:15 -07:00
Patrick Fic
0eacf9a840 Merged in release/2021-10-01 (pull request #228)
release/2021-10-01

Approved-by: Patrick Fic
2021-09-28 20:19:33 +00:00
Patrick Fic
eb58274f90 IO-1391 Scoreboard Improvements 2021-09-28 13:19:02 -07:00
Patrick Fic
aa410d6847 IO-1283 IO-1371 Updates to add to scoreboard button. 2021-09-28 11:45:12 -07:00
Patrick Fic
97f1be9d6f IO-1390 Resolve breaking import changes from Hasura. 2021-09-28 10:59:18 -07:00
Patrick Fic
a7c9dde5e3 Merged in release/2021-10-01 (pull request #227)
release/2021-10-01

Approved-by: Patrick Fic
2021-09-27 23:52:21 +00:00
Patrick Fic
8ec5bc049b Resolve Qb Export not separating for QBO. 2021-09-27 16:50:37 -07:00
Patrick Fic
1f2bec06ef IO-1381 Updates to mark up calculations process. 2021-09-27 16:34:11 -07:00
Patrick Fic
1cad57bec2 Merge branch 'release/2021-10-01' of https://bitbucket.org/snaptsoft/bodyshop into release/2021-10-01 2021-09-27 15:44:21 -07:00
Patrick Fic
10f3c01677 IO-1375 SGI Add Sublet Labor Lines Calculation 2021-09-27 15:44:17 -07:00
Patrick Fic
fc68b669db Add GST to PVRT on QBO. 2021-09-27 14:49:33 -07:00
Patrick Fic
6e22091b81 IO-1382 Add ownr_co_nm to queries that were missing it. 2021-09-27 11:38:48 -07:00
Patrick Fic
545db54c14 IO-653 IO-654 Add SGI documents & regions filtering on print center. 2021-09-27 10:36:12 -07:00
Patrick Fic
aa69fef9ba IO-1379 Updated CSI key for actions menu 2021-09-27 10:22:47 -07:00
Patrick Fic
1e30642d28 Package updates for Tracker. 2021-09-27 09:49:08 -07:00
Patrick Fic
c1dfba949e IO-1349 Update Hasura to 2.0.9. 2021-09-27 09:17:34 -07:00
Patrick Fic
dfa9592755 IO-256 Add PVRT handling for QBO. 2021-09-24 17:17:23 -07:00
Patrick Fic
8f2b1f0f78 IO-256 Export receivables for QBO 2021-09-24 16:43:57 -07:00
Patrick Fic
cf91ec14c0 Merge branch 'feature/qbo' into test 2021-09-24 14:33:07 -07:00
Patrick Fic
dfe0f99bea Merged in release/2021-09-24 (pull request #225)
Revert Node Version

Approved-by: Patrick Fic
2021-09-24 17:44:20 +00:00
Patrick Fic
591022c097 Revert Node Version 2021-09-24 10:43:28 -07:00
Patrick Fic
c76da54b93 IO-256 First succesful basic post of receivables. 2021-09-23 16:53:41 -07:00
Patrick Fic
186f6101ff IO-256 Begin QBO Changes for Individual code mapping on receivables. 2021-09-23 16:22:59 -07:00
Patrick Fic
e95cf0a026 Merge branch 'test' into feature/qbo 2021-09-23 11:48:56 -07:00
Patrick Fic
3eaa3a0189 Merged in release/2021-09-24 (pull request #223)
release/2021-09-24

Approved-by: Patrick Fic
2021-09-23 17:42:34 +00:00
Patrick Fic
0ba445aad2 IO-1228 Report Center grouping change. 2021-09-23 10:38:22 -07:00
Patrick Fic
637c718b05 IO-1227 Add sold status to filter for CC 2021-09-23 10:36:27 -07:00
Patrick Fic
a01019b1b3 IO-1366 Additional Audit Trail Functionality 2021-09-23 10:29:35 -07:00
Patrick Fic
f6e2393a40 Merged in release/2021-09-24 (pull request #222)
release/2021-09-24

Approved-by: Patrick Fic
2021-09-22 23:11:31 +00:00
Patrick Fic
715857a587 IO-1370 Remove filtering on Other Part Type 2021-09-22 08:20:25 -07:00
Patrick Fic
186ad2d7a2 IO-1365 Update Messaging Issues. 2021-09-21 17:33:39 -07:00
Patrick Fic
c64c49ab6e IO-1103 Include removed lines for bill line select. 2021-09-21 15:56:07 -07:00
Patrick Fic
3e067e2103 Merged in release/2021-09-24 (pull request #221)
release/2021-09-24

Approved-by: Patrick Fic
2021-09-21 16:13:56 +00:00
Patrick Fic
4cc1f77b14 IO-1373 Resolve rounding issue on time tickets. 2021-09-21 08:29:16 -07:00
Patrick Fic
334dd5fe94 Merged in release/2021-09-24 (pull request #220)
release/2021-09-24

Approved-by: Patrick Fic
2021-09-21 00:22:47 +00:00
Patrick Fic
d73f37cacc IO-952 add confirm to bill enter with discrepancy. 2021-09-20 17:22:13 -07:00
Patrick Fic
c6a7e3dcb1 IO-1299 Resolve tax ratds not saving on bill enter save and new 2021-09-20 17:11:45 -07:00
Patrick Fic
4bdc02d22c IO-1358 IO-1372 Add Timestamp to report center. 2021-09-20 16:53:51 -07:00
Patrick Fic
afa8e04008 IO-1308 Remove pagination on job lines. 2021-09-20 16:41:59 -07:00
Patrick Fic
990c1bb2bf IO-1272 Improve reconciliation table UI 2021-09-20 16:39:17 -07:00
Patrick Fic
a5a59c526c IO-1369 Update jobs query to include name. 2021-09-20 16:18:39 -07:00
Patrick Fic
2d5bef4b7b IO-1370 Add glass to parts filter list. 2021-09-20 16:12:12 -07:00
Patrick Fic
5893b1e3b3 IO-1335 Add Subject with RO to Parts Order Email 2021-09-20 14:17:18 -07:00
Patrick Fic
0af452b8a4 IO-1292 Vendor Discount Step. 2021-09-20 14:09:27 -07:00
Patrick Fic
3d82198c90 IO-1227 Sold Courtesy Car Status. 2021-09-20 14:09:09 -07:00
Patrick Fic
01bca360c7 IO-1228 IO-1282 RC Categories & Searching 2021-09-20 12:48:37 -07:00
Patrick Fic
59e994ac29 IO-1367 Open Replay Tracking Improvements 2021-09-20 12:16:37 -07:00
Patrick Fic
d5f3105341 IO-1254 Add PO Number 2021-09-20 12:02:37 -07:00
Patrick Fic
92920f69d4 IO-1191 Add CSI to reports. 2021-09-20 11:34:05 -07:00
Patrick Fic
61ac520192 IO-1364 Update profile changes for new firebase package. 2021-09-20 11:31:13 -07:00
Patrick Fic
45176cc2e2 Merged in feature/cdk-cert (pull request #219)
IO-233 Add refetch.
2021-09-17 17:22:56 +00:00
Patrick Fic
6aacce5a58 IO-233 Add refetch. 2021-09-17 10:22:20 -07:00
Patrick Fic
7a515c35d2 Merge branch 'test' into feature/qbo 2021-09-16 17:04:57 -07:00
Patrick Fic
80dc04669c Merged in feature/cdk-cert (pull request #218)
feature/cdk-cert

Approved-by: Patrick Fic
2021-09-16 23:11:17 +00:00
Patrick Fic
a702c87986 io-233 Remote ITC Settings 2021-09-16 16:08:32 -07:00
Patrick Fic
1942103985 IO-256 Start add QBO Payables 2021-09-15 16:14:01 -07:00
Patrick Fic
fd161fa9eb IO-233 Add missing MAPA/MASH/Discount calulcations. 2021-09-15 08:28:54 -07:00
Patrick Fic
44a18f4ace Merged in feature/qbo (pull request #217)
feature/qbo

Approved-by: Patrick Fic
2021-09-14 21:10:15 +00:00
Patrick Fic
3269c4f602 Merged in feature/cdk-cert (pull request #216)
feature/cdk-cert

Approved-by: Patrick Fic
2021-09-14 21:09:10 +00:00
Patrick Fic
43cbf0084a Merged in release/2021-09-17 (pull request #215)
release/2021-09-17

Approved-by: Patrick Fic
2021-09-14 21:03:40 +00:00
Patrick Fic
28b1356f76 Merge branch 'release/2021-09-17' into feature/cdk-cert 2021-09-14 14:03:11 -07:00
Patrick Fic
38456cb213 Merge branch 'release/2021-09-17' into feature/qbo 2021-09-14 14:02:18 -07:00
Patrick Fic
8c826eaaed Update to latest node. 2021-09-14 14:01:55 -07:00
Patrick Fic
f047556ab5 Merge branch 'release/2021-09-17' into feature/qbo 2021-09-14 13:59:06 -07:00
Patrick Fic
057b335e82 Merge branch 'release/2021-09-17' into feature/cdk-cert 2021-09-14 13:58:48 -07:00
Patrick Fic
30c7da2bf9 Merged in hotfix-2021-09-14 (pull request #213)
IO-43 Resolve issue when no vehicle associated.

Approved-by: Patrick Fic
2021-09-14 16:41:07 +00:00
Patrick Fic
82fb4fc74c IO-43 Resolve issue when no vehicle associated. 2021-09-14 09:40:08 -07:00
Patrick Fic
151f122b8c Package updates. 2021-09-14 09:12:58 -07:00
Patrick Fic
b198ca5051 IO-233 Revert ITC Calculations. 2021-09-13 14:56:54 -07:00
Patrick Fic
d9abe84949 Merge release & add time ticket calculations. 2021-09-13 14:47:09 -07:00
Patrick Fic
17f4d69e30 Merged in release/2021-09-10 (pull request #211)
Resolve translation.

Approved-by: Patrick Fic
2021-09-13 17:09:49 +00:00
Patrick Fic
77d3fc359d Resolve translation. 2021-09-13 10:09:26 -07:00
Patrick Fic
86151f3337 Merge branch 'release/2021-09-10' into feature/cdk-cert 2021-09-13 10:08:15 -07:00
Patrick Fic
ae4b5bca33 Merged in release/2021-09-10 (pull request #210)
Add missing translations.

Approved-by: Patrick Fic
2021-09-13 16:50:13 +00:00
Patrick Fic
eb120a264e Add missing translations. 2021-09-13 09:49:38 -07:00
Patrick Fic
7e3f496ea1 IO-233 CDK WIP customer changes. 2021-09-13 09:43:41 -07:00
Patrick Fic
4d33f16f13 Merged in release/2021-09-10 (pull request #209)
release/2021-09-10

Approved-by: Patrick Fic
2021-09-13 15:09:19 +00:00
Patrick Fic
d7ebefe7ab Resolve missing query. 2021-09-13 08:08:43 -07:00
Patrick Fic
8dc2197677 IO-1353 Add missing default resp. centers. 2021-09-10 15:12:00 -07:00
Patrick Fic
fe993cba73 IO-233 CDK Reformatting. First successful post. 2021-09-10 14:58:09 -07:00
Patrick Fic
ef6cdf07d8 IO-1352 Remove positive hrs check on receivables export. 2021-09-10 13:09:21 -07:00
Patrick Fic
cdbde6f5fa Merged in release/2021-09-10 (pull request #207)
IO-43 Updated related ROs approach.

Approved-by: Patrick Fic
2021-09-09 22:35:46 +00:00
Patrick Fic
4059aa9875 IO-43 Updated related ROs approach. 2021-09-09 15:35:24 -07:00
Patrick Fic
22e30ae5cb IO-233 WIP CDK 2021-09-09 15:18:43 -07:00
Patrick Fic
78c883743f Merged in release/2021-09-10 (pull request #206)
IO-1350 Audatex import issues.
2021-09-09 20:10:20 +00:00
Patrick Fic
e46307e715 IO-1350 Audatex import issues. 2021-09-09 13:09:05 -07:00
Patrick Fic
f3e078b481 IO-233 Additional CDK Posting 2021-09-08 15:11:24 -07:00
Patrick Fic
c03f54b3eb Merge branch 'release/2021-09-10' into feature/cdk-cert 2021-09-08 08:28:54 -07:00
Patrick Fic
bb0234fb7d Merge branch 'test' into feature/cdk-cert 2021-09-08 08:28:27 -07:00
Patrick Fic
03f25c8c0e Merged in release/2021-09-10 (pull request #205)
release/2021-09-10

Approved-by: Patrick Fic
2021-09-07 23:53:40 +00:00
Patrick Fic
8e5005daa0 IO-1345 IO-43 Related Job linking 2021-09-07 16:27:34 -07:00
Patrick Fic
259458eec3 IO-1343 Updated tax rate check on import. 2021-09-07 09:53:55 -07:00
Patrick Fic
59e57aa274 Package updates. 2021-09-07 09:12:35 -07:00
Patrick Fic
de33bcd72b IO-1342 Revert IO-554 and add validation on jobline upsert. 2021-09-07 09:12:31 -07:00
Patrick Fic
d72472ccc3 Merged in hotifx/2021-09-06 (pull request #203)
hotifx/2021-09-06

Approved-by: Patrick Fic
2021-09-06 22:50:14 +00:00
Patrick Fic
91efd170c8 Merged in hotifx/2021-09-06 (pull request #202)
IO-1346 Resolve delivery checklist freezing.

Approved-by: Patrick Fic
2021-09-06 22:43:24 +00:00
Patrick Fic
6f1ddd51fd IO-1346 Resolve delivery checklist freezing. 2021-09-06 15:42:25 -07:00
Patrick Fic
9f48a91a29 Merged in release/2021-09-03 (pull request #201)
Remove assist tracker again.

Approved-by: Patrick Fic
2021-09-03 20:01:07 +00:00
Patrick Fic
a7e2548e14 Merged in release/2021-09-03 (pull request #200)
Remove assist tracker again.

Approved-by: Patrick Fic
2021-09-03 20:00:43 +00:00
Patrick Fic
3294faaeaa Remove assist tracker again. 2021-09-03 13:00:02 -07:00
Patrick Fic
06480159e3 Merge branch 'release/2021-09-03' into feature/qbo 2021-09-03 11:24:42 -07:00
Patrick Fic
6ce06ed5c0 Merge branch 'release/2021-09-03' into feature/cdk-cert 2021-09-03 11:24:16 -07:00
Patrick Fic
71e535388a Merged in release/2021-09-03 (pull request #199)
Release/2021 09 03
2021-09-03 18:19:02 +00:00
Patrick Fic
dbd265d368 Merged in release/2021-09-03 (pull request #198)
IO-1342 Resolve negative parts discount for negatives.

Approved-by: Patrick Fic
2021-09-02 23:31:38 +00:00
Patrick Fic
c11f182f83 IO-1342 Resolve negative parts discount for negatives. 2021-09-02 16:25:07 -07:00
Patrick Fic
5f70cfd585 Merged in release/2021-09-03 (pull request #197)
release/2021-09-03

Approved-by: Patrick Fic
2021-09-02 22:50:50 +00:00
Patrick Fic
6e0675f28b IO-1342 Resolve negative parts discount crash on job totals. 2021-09-02 15:48:54 -07:00
Patrick Fic
96f45d2c80 IO-233 Begin Trans Headers 2021-09-02 15:30:03 -07:00
Patrick Fic
091e44f471 Merge branch 'release/2021-09-03' into feature/cdk-cert 2021-09-02 15:27:22 -07:00
Patrick Fic
00549d6a88 Revert "Remove assist tracker."
This reverts commit d940e0ee78.
2021-09-01 18:10:44 -07:00
Patrick Fic
d940e0ee78 Remove assist tracker. 2021-09-01 15:06:52 -07:00
Patrick Fic
39a38d46ee Merged in release/2021-09-03 (pull request #196)
release/2021-09-03

Approved-by: Patrick Fic
2021-09-01 21:14:34 +00:00
Patrick Fic
71435ed75a Added patch for peerjs. 2021-09-01 14:14:20 -07:00
Patrick Fic
e7ec408b98 Merged in release/2021-09-03 (pull request #195)
Openrelay tracking change.

Approved-by: Patrick Fic
2021-09-01 20:39:45 +00:00
Patrick Fic
9306064420 Openrelay tracking change. 2021-09-01 13:38:35 -07:00
Patrick Fic
37a16edfb4 IO-233 Updated translations. 2021-09-01 13:24:19 -07:00
Patrick Fic
9ad5b9547f Merged in release/2021-09-03 (pull request #194)
release/2021-09-03

Approved-by: Patrick Fic
2021-09-01 16:19:58 +00:00
Patrick Fic
47edb0bdf4 IO-1332 Resolve checklist prod note. 2021-09-01 09:19:11 -07:00
Patrick Fic
5db47e879c IO-1224 Adjust scoreboard Entry 2021-09-01 08:43:37 -07:00
Patrick Fic
54b46dd25e IO-256 WIP QBO. 2021-09-01 08:31:24 -07:00
Patrick Fic
4cb92c8508 Merged in release/2021-09-03 (pull request #193)
IO-1336 Resolve towing/storage missing on export.

Approved-by: Patrick Fic
2021-08-31 00:04:22 +00:00
Patrick Fic
cb76e2dcde IO-1336 Resolve issues with QB Export of towing. 2021-08-30 16:59:15 -07:00
Patrick Fic
901c64ed85 IO-1336 Resolve towing/storage missing on export. 2021-08-30 16:38:45 -07:00
Patrick Fic
f6f90d68fa Merged in release/2021-09-03 (pull request #192)
release/2021-09-03

Approved-by: Patrick Fic
2021-08-30 22:43:40 +00:00
Patrick Fic
05e295fcac IO-1334 Added shop info RBAC. 2021-08-30 15:43:10 -07:00
Patrick Fic
c97df6dc61 IO-1333 Resolve dashboard component name issue. 2021-08-30 15:34:19 -07:00
Patrick Fic
61406aafa6 IO-1302 Remove 2nd message on job close. 2021-08-30 15:28:22 -07:00
Patrick Fic
03210db711 IO-1332 Resolve production note save on intake. 2021-08-30 15:25:04 -07:00
Patrick Fic
b3a34c109a IO-539 Fix hanging confirmation. 2021-08-30 15:20:08 -07:00
Patrick Fic
81daad35d8 Package updates and firebase refactor to SDK 9. 2021-08-30 15:09:11 -07:00
Patrick Fic
529eb24d76 Merge branch 'feature/qbo' into release/2021-09-03 2021-08-30 12:57:38 -07:00
Patrick Fic
e81bf7b561 Merge in CDK Branches WIP. 2021-08-30 12:57:13 -07:00
Patrick Fic
7a35dc9b38 IO-233 Add Vehicle History 2021-08-30 12:54:43 -07:00
Patrick Fic
c72ef97b82 IO-256 Further work on QBO Receivables. 2021-08-30 10:50:56 -07:00
Patrick Fic
5284ee2ef9 IO-256 Authorization and Basic Calls 2021-08-27 15:42:32 -07:00
Patrick Fic
724c097d52 IO-256 QBO Authorization Flow. 2021-08-26 15:48:10 -07:00
Patrick Fic
3c3da178ba Merged in release/2021-08-27 (pull request #191)
Release/2021 08 27
2021-08-25 21:59:23 +00:00
Patrick Fic
db4e5d48af Merge branch 'feature/cdk-cert' into feature/qbo 2021-08-25 12:08:06 -07:00
Patrick Fic
a7cf081ed5 IO-233 Begin Wip Header Creation 2021-08-24 18:48:02 -07:00
Patrick Fic
db5b11f6d3 IO-233 Vehicle and Customer posting unit testing completed. 2021-08-24 18:14:41 -07:00
Patrick Fic
8d3d52485f Merged in release/2021-08-27 (pull request #190)
IO-1330 Resolve postal code on qb export.

Approved-by: Patrick Fic
2021-08-24 23:40:02 +00:00
Patrick Fic
edb58ebc81 IO-1330 Resolve postal code on qb export. 2021-08-24 16:39:42 -07:00
Patrick Fic
971b518e8f Merged in release/2021-08-27 (pull request #189)
IO-539 Resolve tax import for SGI.

Approved-by: Patrick Fic
2021-08-24 23:35:15 +00:00
Patrick Fic
8d9507dce1 IO-539 Resolve tax import for SGI. 2021-08-24 16:32:05 -07:00
Patrick Fic
748f8f472d IO-233 Insert DMS Vehicle 2021-08-24 15:56:10 -07:00
Patrick Fic
db2b4739c2 IO-233 Add insert Job. 2021-08-24 08:05:24 -07:00
Patrick Fic
6c12e5cb03 IO-233 Export Job Refactor 2021-08-23 19:02:05 -07:00
Patrick Fic
140e57a123 Merged in release/2021-08-27 (pull request #188)
IO-1330 Update QB Information

Approved-by: Patrick Fic
2021-08-23 21:17:06 +00:00
Patrick Fic
3ca6791939 IO-233 Added vehicle search & selection on form. 2021-08-23 10:40:10 -07:00
Patrick Fic
1c473c95a2 IO-1330 Update QB Information 2021-08-23 09:50:01 -07:00
Patrick Fic
7e145bdec7 Merged in release/2021-08-27 (pull request #187)
Remove tracker assist for CI.

Approved-by: Patrick Fic
2021-08-23 15:43:39 +00:00
Patrick Fic
44c17bd7a2 Remove tracker assist for CI. 2021-08-23 08:43:14 -07:00
Patrick Fic
c493f6e31e Merged in release/2021-08-27 (pull request #186)
Release/2021 08 27

Approved-by: Patrick Fic
2021-08-23 15:34:50 +00:00
Patrick Fic
5213b0b315 Merge branch 'test' into release/2021-08-27 2021-08-23 08:34:25 -07:00
Patrick Fic
8bfd0a1c16 IO-233 Insert DMS Vehicles 2021-08-23 08:30:30 -07:00
Patrick Fic
0541167cc5 Addition of Open Replay Tracker 2021-08-20 14:38:33 -07:00
Patrick Fic
834966ae96 Merged in release/2021-08-20 (pull request #185)
Release/2021 08 20
2021-08-20 21:01:17 +00:00
Patrick Fic
e7d813c3f3 Merged in test (pull request #184)
Merge WIP items back into CDK CErt.
2021-08-20 16:37:44 +00:00
Patrick Fic
26e47ff203 Merged in release/2021-08-20 (pull request #183)
IO-1327 Resolve dinero error on bill posting

Approved-by: Patrick Fic
2021-08-20 15:07:38 +00:00
Patrick Fic
5a4d6d3e8c IO-1269 2021-08-19 17:31:32 -07:00
Patrick Fic
61a5e180f4 IO-1323 Add unit number to contracts list. 2021-08-19 14:43:38 -07:00
Patrick Fic
d5b8ea3ac5 IO-1317 2021-08-19 14:24:12 -07:00
Patrick Fic
4e87ef179b Resolve craco configuration. 2021-08-19 13:45:37 -07:00
Patrick Fic
3b7c31626d IO-539 Adjust Threshold Totals 2021-08-19 08:48:30 -07:00
Patrick Fic
a57e35354d Add sentry error logging. 2021-08-18 15:57:29 -07:00
Patrick Fic
a269fd3ad8 IO-1316 Add flat_rate to time tickets & resolve issues in job costing calculations. 2021-08-18 15:39:37 -07:00
Patrick Fic
86bee9ad0d IO-1321 Mark PO Quantity as required. 2021-08-18 15:38:54 -07:00
Patrick Fic
f8b57ca9bd IO-1297 Adjust Job Costing after Invoicing 2021-08-18 14:13:11 -07:00
Patrick Fic
5f7b780195 Merged in release/2021-08-20 (pull request #182)
release/2021-08-20

Approved-by: Patrick Fic
2021-08-18 20:29:06 +00:00
Patrick Fic
db64d9b69f IO-233 WIP CDK 2021-08-17 16:22:10 -07:00
Patrick Fic
0d12ab36a9 Merged in release/2021-08-20 (pull request #181)
release/2021-08-20

Approved-by: Patrick Fic
2021-08-13 23:38:45 +00:00
Patrick Fic
d6fbf16272 Merged in feature/2021-08-13 (pull request #180)
Feature/2021 08 13
2021-08-13 19:09:00 +00:00
Patrick Fic
2f9d025fbe Merged in hotfix/2021-08-12 (pull request #178)
hotfix/2021-08-12

Approved-by: Patrick Fic
2021-08-12 20:43:16 +00:00
Patrick Fic
9c1ffaba17 Merged in hotfix/2021-08-12 (pull request #177)
hotfix/2021-08-12
2021-08-12 20:42:55 +00:00
Patrick Fic
142df39cd2 Merged in hotfix/2021-08-12 (pull request #176)
Additional Checklist resolutions.

Approved-by: Patrick Fic
2021-08-12 20:21:34 +00:00
Patrick Fic
6d2d96be5c Merged in hotfix/2021-08-12 (pull request #175)
Resolve delete checklist item.

Approved-by: Patrick Fic
2021-08-12 20:16:47 +00:00
Patrick Fic
b793aa3394 Merged in hotfix/2021-08-12 (pull request #174)
hotfix/2021-08-12

Approved-by: Patrick Fic
2021-08-12 20:02:48 +00:00
Patrick Fic
e34084b146 Merged in feature/2021-08-13 (pull request #173)
feature/2021-08-13

Approved-by: Patrick Fic
2021-08-12 16:53:13 +00:00
Patrick Fic
c1d7168260 Resolve CI issue. 2021-08-12 09:52:44 -07:00
Patrick Fic
b3a3709b72 Merged in feature/2021-08-13 (pull request #172)
IO-1286 Retry Supplement Importing fix.

Approved-by: Patrick Fic
2021-08-12 16:47:27 +00:00
Patrick Fic
dd59f3d026 IO-1286 Retry Supplement Importing fix. 2021-08-12 09:46:50 -07:00
Patrick Fic
ccb00ff391 Merged in feature/2021-08-13 (pull request #171)
feature/2021-08-13

Approved-by: Patrick Fic
2021-08-11 20:51:19 +00:00
Patrick Fic
b37970a6df IO-1300 Line Desc Null for Job costing 2021-08-11 13:50:37 -07:00
Patrick Fic
2cd7fcfbd8 Merged in hotfix/2021-08-11 (pull request #170)
hotfix/2021-08-11

Approved-by: Patrick Fic
2021-08-11 20:03:58 +00:00
Patrick Fic
b2362a85fa Merged in hotfix/2021-08-11 (pull request #167)
hotfix/2021-08-11

Approved-by: Patrick Fic
2021-08-11 19:58:52 +00:00
Patrick Fic
76826c1e80 Merged in feature/2021-08-13 (pull request #165)
Include sentry username

Approved-by: Patrick Fic
2021-08-11 18:16:43 +00:00
Patrick Fic
d1952dfc25 Merged in feature/2021-08-13 (pull request #164)
IO-1300 Resolve null line desc.

Approved-by: Patrick Fic
2021-08-11 18:10:01 +00:00
Patrick Fic
0273255c2c Merged in feature/2021-08-13 (pull request #163)
Feature/2021 08 13
2021-08-11 15:09:36 +00:00
460 changed files with 53343 additions and 7333 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,25 @@
// craco.config.js
const TerserPlugin = require("terser-webpack-plugin");
const CracoLessPlugin = require("craco-less");
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
module.exports = {
plugins: [
{
plugin: SentryWebpackPlugin,
options: {
// sentry-cli configuration
authToken:
"6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
org: "snapt-software",
project: "imexonline",
release: process.env.REACT_APP_GIT_SHA,
// webpack-specific configuration
include: ".",
ignore: ["node_modules", "webpack.config.js"],
},
},
{
plugin: CracoLessPlugin,
options: {
@@ -53,4 +69,5 @@ module.exports = {
},
}),
},
devtool: "source-map",
};

View File

@@ -4,81 +4,87 @@
"private": true,
"proxy": "http://localhost:5000",
"dependencies": {
"@apollo/client": "^3.3.21",
"@craco/craco": "^6.2.0",
"@fingerprintjs/fingerprintjs": "^3.2.0",
"@apollo/client": "^3.4.16",
"@craco/craco": "^6.3.0",
"@fingerprintjs/fingerprintjs": "^3.3.0",
"@lourenci/react-kanban": "^2.1.0",
"@sentry/react": "^6.10.0",
"@sentry/tracing": "^6.10.0",
"@stripe/react-stripe-js": "^1.4.0",
"@stripe/stripe-js": "^1.16.0",
"@tanem/react-nprogress": "^3.0.74",
"antd": "^4.16.8",
"@openreplay/tracker": "^3.4.4",
"@openreplay/tracker-assist": "^3.4.3",
"@openreplay/tracker-graphql": "^3.0.0",
"@openreplay/tracker-redux": "^3.0.0",
"@sentry/react": "^6.13.2",
"@sentry/tracing": "^6.13.2",
"@stripe/react-stripe-js": "^1.5.0",
"@stripe/stripe-js": "^1.19.1",
"@tanem/react-nprogress": "^3.0.79",
"antd": "^4.16.13",
"apollo-link-logger": "^2.0.0",
"axios": "^0.21.1",
"craco-less": "^1.18.0",
"axios": "^0.22.0",
"craco-less": "^1.20.0",
"dinero.js": "^1.9.0",
"dotenv": "^10.0.0",
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0",
"exifr": "^7.1.2",
"firebase": "^8.7.1",
"graphql": "^15.5.1",
"i18next": "^20.3.4",
"exifr": "^7.1.3",
"firebase": "^9.1.1",
"graphql": "^15.6.0",
"i18next": "^21.2.4",
"i18next-browser-languagedetector": "^6.1.2",
"jsoneditor": "^9.5.2",
"jsoneditor": "^9.5.6",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.9.22",
"logrocket": "^1.2.0",
"markerjs2": "^2.9.0",
"libphonenumber-js": "^1.9.36",
"logrocket": "^2.0.0",
"markerjs2": "^2.13.0",
"moment-business-days": "^1.2.0",
"phone": "^3.1.2",
"phone": "^3.1.8",
"preval.macro": "^5.0.0",
"prop-types": "^15.7.2",
"query-string": "^7.0.1",
"rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6",
"react": "^17.0.1",
"react-big-calendar": "^0.33.2",
"react-big-calendar": "^0.36.1",
"react-color": "^2.19.3",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.1",
"react-drag-listview": "^0.1.8",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.2.5",
"react-i18next": "^11.11.3",
"react-icons": "^4.2.0",
"react-number-format": "^4.6.4",
"react-redux": "^7.2.4",
"react-grid-layout": "^1.3.0",
"react-i18next": "^11.12.0",
"react-icons": "^4.3.1",
"react-number-format": "^4.7.3",
"react-redux": "^7.2.5",
"react-resizable": "^3.0.4",
"react-router-dom": "^5.2.0",
"react-router-dom": "^5.3.0",
"react-scripts": "^4.0.3",
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3",
"recharts": "^2.0.10",
"redux": "^4.1.0",
"recharts": "^2.1.4",
"redux": "^4.1.1",
"redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.2",
"reselect": "^4.0.0",
"sass": "^1.35.2",
"socket.io-client": "^4.1.3",
"styled-components": "^5.3.0",
"sass": "^1.42.1",
"socket.io-client": "^4.2.0",
"styled-components": "^5.3.1",
"subscriptions-transport-ws": "^0.9.18",
"web-vitals": "^2.1.0",
"workbox-background-sync": "^6.1.5",
"workbox-broadcast-update": "^6.1.5",
"workbox-cacheable-response": "^6.1.5",
"workbox-core": "^6.1.5",
"workbox-expiration": "^6.1.5",
"workbox-google-analytics": "^6.1.5",
"workbox-navigation-preload": "^6.1.5",
"workbox-precaching": "^6.1.5",
"workbox-range-requests": "^6.1.5",
"workbox-routing": "^6.1.5",
"workbox-strategies": "^6.1.5",
"workbox-streams": "^6.1.5"
"workbox-background-sync": "^6.3.0",
"workbox-broadcast-update": "^6.3.0",
"workbox-cacheable-response": "^6.3.0",
"workbox-core": "^6.3.0",
"workbox-expiration": "^6.3.0",
"workbox-google-analytics": "^6.3.0",
"workbox-navigation-preload": "^6.3.0",
"workbox-precaching": "^6.3.0",
"workbox-range-requests": "^6.3.0",
"workbox-routing": "^6.3.0",
"workbox-strategies": "^6.3.0",
"workbox-streams": "^6.3.0"
},
"scripts": {
"postinstall": "patch-package",
"analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "craco start",
"build": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
@@ -108,6 +114,8 @@
]
},
"devDependencies": {
"@sentry/webpack-plugin": "^1.17.2",
"patch-package": "^6.4.7",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.2"
}

File diff suppressed because one or more lines are too long

View File

@@ -8,8 +8,32 @@ import { useTranslation } from "react-i18next";
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import client from "../utils/GraphQLClient";
import App from "./App";
import trackerGraphQL from "@openreplay/tracker-graphql";
//import trackerRedux from "@openreplay/tracker-redux";
import Tracker from "@openreplay/tracker";
//import trackerAssist from "@openreplay/tracker-assist";
import { getCurrentUser } from "../firebase/firebase.utils";
moment.locale("en-US");
export const tracker = new Tracker({
projectKey: "trDmOZlEXUpjGsMtHroA",
ingestPoint: "https://replay.imex.online/ingest",
...(process.env.NODE_ENV === null || process.env.NODE_ENV === "development"
? { __DISABLE_SECURE_MODE: true }
: {}),
// beaconSize: 10485760,
onStart: async ({ sessionID }) => {
const user = await getCurrentUser();
if (user) tracker.setUserID(user.email);
console.log("ORS SESSION ", sessionID, user && user.email);
},
});
// tracker.use(
// trackerAssist({ confirmText: "Technical support is about to assist you." })
// ); // check the list of available options below
export const recordGraphQL = tracker.use(trackerGraphQL());
tracker.start();
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
export default function AppContainer() {

View File

@@ -124,3 +124,7 @@
z-index: 2 !important;
}
}
.react-kanban-column {
background-color: #ddd !important;
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 51 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 51 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -1,25 +1,9 @@
import Axios from "axios";
import React from "react";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
export default function Test() {
const handleQbSignIn = async () => {
const result = await Axios.post("/qbo/authorize", { userId: "1234" });
console.log("handleQbSignIn -> result", result.data);
// window.open(result.data, "_blank", "toolbar=0,location=0,menubar=0");
var parameters = "location=1,width=800,height=650";
parameters +=
",left=" +
(window.screen.width - 800) / 2 +
",top=" +
(window.screen.height - 650) / 2;
// Launch Popup
window.open(result.data, "connectPopup", parameters);
};
return (
<div>
<button onClick={handleQbSignIn}>Sign Into Qb.</button>
<QboAuthorizeComponent />
</div>
);
}

View File

@@ -9,8 +9,25 @@ import PayableExportAll from "../payable-export-all-button/payable-export-all-bu
import { DateFormatter } from "../../utils/DateFormatter";
import queryString from "query-string";
import { logImEXEvent } from "../../firebase/firebase.utils";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
export default function AccountingPayablesTableComponent({ loading, bills }) {
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(AccountingPayablesTableComponent);
export function AccountingPayablesTableComponent({ bodyshop, loading, bills }) {
const { t } = useTranslation();
const [selectedBills, setSelectedBills] = useState([]);
const [transInProgress, setTransInProgress] = useState(false);
@@ -166,6 +183,9 @@ export default function AccountingPayablesTableComponent({ loading, bills }) {
loadingCallback={setTransInProgress}
completedCallback={setSelectedBills}
/>
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
<QboAuthorizeComponent />
)}
<Input
value={state.search}
onChange={handleSearch}

View File

@@ -8,7 +8,26 @@ import { alphaSort } from "../../utils/sorters";
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
export default function AccountingReceivablesTableComponent({ loading, jobs }) {
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(AccountingReceivablesTableComponent);
export function AccountingReceivablesTableComponent({
bodyshop,
loading,
jobs,
}) {
const { t } = useTranslation();
const [selectedJobs, setSelectedJobs] = useState([]);
const [transInProgress, setTransInProgress] = useState(false);
@@ -180,12 +199,17 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) {
<Card
extra={
<Space wrap>
<JobsExportAllButton
jobIds={selectedJobs}
disabled={transInProgress || selectedJobs.length === 0}
loadingCallback={setTransInProgress}
completedCallback={setSelectedJobs}
/>
{!bodyshop.cdk_dealerid && (
<JobsExportAllButton
jobIds={selectedJobs}
disabled={transInProgress || selectedJobs.length === 0}
loadingCallback={setTransInProgress}
completedCallback={setSelectedJobs}
/>
)}
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
<QboAuthorizeComponent />
)}
<Input.Search
value={state.search}
onChange={handleSearch}

View File

@@ -18,8 +18,10 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import BillFormContainer from "../bill-form/bill-form.container";
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
import { handleUpload } from "../documents-upload/documents-upload.utility";
const mapStateToProps = createStructuredSelector({
@@ -48,7 +50,31 @@ function BillEnterModalContainer({
const [loading, setLoading] = useState(false);
const client = useApolloClient();
const formValues = useMemo(() => {
return {
...billEnterModal.context.bill,
jobid:
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
federal_tax_rate:
(bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.federal_tax_rate) ||
0,
state_tax_rate:
(bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.state_tax_rate) ||
0,
local_tax_rate:
(bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.local_tax_rate) ||
0,
};
}, [billEnterModal, bodyshop]);
const handleFinish = async (values) => {
let totals = CalculateBillTotal(values);
if (totals.discrepancy.getAmount() !== 0) {
if (!(await confirmDialog(t("bills.labels.savewithdiscrepancy")))) {
return;
}
}
setLoading(true);
const { upload, location, ...remainingValues } = values;
@@ -190,7 +216,7 @@ function BillEnterModalContainer({
if (enterAgain) {
form.resetFields();
form.setFieldsValue({ billlines: [] });
form.setFieldsValue(formValues);
} else {
toggleModalVisible();
}
@@ -208,23 +234,6 @@ function BillEnterModalContainer({
if (enterAgain) form.submit();
}, [enterAgain, form]);
const formValues = useMemo(() => {
return {
...billEnterModal.context.bill,
jobid:
(billEnterModal.context.job && billEnterModal.context.job.id) || null,
federal_tax_rate:
(bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.federal_tax_rate) ||
0,
state_tax_rate:
(bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.state_tax_rate) ||
0,
local_tax_rate:
(bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.local_tax_rate) ||
0,
};
}, [billEnterModal, bodyshop]);
useEffect(() => {
if (billEnterModal.visible) {
form.setFieldsValue(formValues);

View File

@@ -12,7 +12,19 @@ const BillLineSearchSelect = ({ options, disabled, ...restProps }, ref) => {
disabled={disabled}
ref={ref}
showSearch
optionFilterProp="line_desc"
// optionFilterProp="line_desc"
filterOption={(inputValue, option) => {
console.log(inputValue);
return (
(option.line_desc &&
option.line_desc
.toLowerCase()
.includes(inputValue.toLowerCase())) ||
(option.oem_partno &&
option.oem_partno.toLowerCase().includes(inputValue.toLowerCase()))
);
}}
notFoundContent={"Removed."}
{...restProps}
>
<Select.Option key={null} value={"noline"} cost={0} line_desc={""}>
@@ -21,14 +33,19 @@ const BillLineSearchSelect = ({ options, disabled, ...restProps }, ref) => {
{options
? options.map((item) => (
<Option
disabled={item.removed}
key={item.id}
value={item.id}
cost={item.act_price ? item.act_price : 0}
part_type={item.part_type}
line_desc={item.line_desc}
part_qty={item.part_qty}
oem_partno={item.oem_partno}
style={{
...(item.removed ? { textDecoration: "line-through" } : {}),
}}
>
{`${item.line_desc}${
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}`}
</Option>

View File

@@ -8,6 +8,7 @@ import { selectSelectedConversation } from "../../redux/messaging/messaging.sele
import PhoneFormatter from "../../utils/PhoneFormatter";
import "./chat-conversation-list.styles.scss";
import { useTranslation } from "react-i18next";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation,
@@ -60,13 +61,18 @@ export function ChatConversationListComponent({
) : (
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
)}
{item.job_conversations.length > 0
? item.job_conversations.map((j, idx) => (
<Tag key={idx} className="ro-number-tag">
{j.job.ro_number}
</Tag>
))
: null}
<div sryle={{ display: "inline-block" }}>
<div>
{item.job_conversations.length > 0
? item.job_conversations.map((j, idx) => (
<Tag key={idx} className="ro-number-tag">
{j.job.ro_number}
</Tag>
))
: null}
</div>
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
</div>
<Badge count={item.messages_aggregate.aggregate.count || 0} />
</List.Item>
)}

View File

@@ -1,4 +1,5 @@
import Icon from "@ant-design/icons";
import { Tooltip } from "antd";
import i18n from "i18next";
import moment from "moment";
import React, { useEffect, useRef } from "react";
@@ -9,6 +10,7 @@ import {
CellMeasurerCache,
List,
} from "react-virtualized";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import "./chat-message-list.styles.scss";
export default function ChatMessageListComponent({ messages }) {
@@ -85,17 +87,22 @@ export default function ChatMessageListComponent({ messages }) {
const MessageRender = (message) => {
return (
<div>
{message.image_path &&
message.image_path.map((i, idx) => (
<div key={idx} style={{ display: "flex", justifyContent: "center" }}>
<a href={i} target="__blank">
<img alt="Received" className="message-img" src={i} />
</a>
</div>
))}
<div>{message.text}</div>
</div>
<Tooltip title={DateTimeFormatter({ children: message.created_at })}>
<div>
{message.image_path &&
message.image_path.map((i, idx) => (
<div
key={idx}
style={{ display: "flex", justifyContent: "center" }}
>
<a href={i} target="__blank">
<img alt="Received" className="message-img" src={i} />
</a>
</div>
))}
<div>{message.text}</div>
</div>
</Tooltip>
);
};

View File

@@ -83,8 +83,10 @@ export function ContractsList({
render: (text, record) => (
<Link to={`/manage/courtesycars/${record.courtesycar.id}`}>{`${
record.courtesycar.year
} ${record.courtesycar.make} ${record.courtesycar.model} ${
record.courtesycar.plate ? `(${record.courtesycar.plate})` : ""
} ${record.courtesycar.make} ${record.courtesycar.model}${
record.courtesycar.plate ? ` (${record.courtesycar.plate})` : ""
}${
record.courtesycar.fleetnumber ? ` (${record.courtesycar.fleetnumber})` : ""
}`}</Link>
),
},

View File

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

View File

@@ -45,6 +45,10 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
text: t("courtesycars.status.out"),
value: "courtesycars.status.out",
},
{
text: t("courtesycars.status.sold"),
value: "courtesycars.status.sold",
},
],
onFilter: (value, record) => value.includes(record.status),
sortOrder:

View File

@@ -244,7 +244,7 @@ const componentList = {
h: 3,
},
MonthlyPartsSales: {
label: i18next.t("dashboard.titles.productiondollars"),
label: i18next.t("dashboard.titles.monthlypartssales"),
component: DashboardMonthlyPartsSales,
gqlFragment: null,
minW: 2,
@@ -253,7 +253,7 @@ const componentList = {
h: 2,
},
MonthlyLaborSales: {
label: i18next.t("dashboard.titles.monthlypartssales"),
label: i18next.t("dashboard.titles.monthlylaborsales"),
component: DashboardMonthlyLaborSales,
gqlFragment: null,
minW: 2,

View File

@@ -1,25 +1,39 @@
import { Button, Table } from "antd";
import React, { useState } from "react";
import { Button, Card, Table, Typography } from "antd";
import React, { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import Dinero from "dinero.js";
import { SyncOutlined } from "@ant-design/icons";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(DmsAllocationsSummary);
export function DmsAllocationsSummary({ socket, bodyshop, jobId }) {
export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
const { t } = useTranslation();
const [allocationsSummary, setAllocationsSummary] = useState([]);
useEffect(() => {
if (socket.connected) {
socket.emit("cdk-calculate-allocations", jobId, (ack) => {
setAllocationsSummary(ack);
socket.allocationsSummary = ack;
});
}
}, [socket, socket.connected, jobId]);
const columns = [
{
title: t("jobs.fields.dms.center"),
@@ -62,8 +76,9 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId }) {
];
return (
<Table
title={() => (
<Card
title={title}
extra={
<Button
onClick={() => {
socket.emit("cdk-calculate-allocations", jobId, (ack) =>
@@ -71,13 +86,50 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId }) {
);
}}
>
Get
<SyncOutlined />
</Button>
)}
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}
rowKey="center"
dataSource={allocationsSummary}
/>
}
>
<Table
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}
rowKey="center"
dataSource={allocationsSummary}
summary={() => {
const totals = allocationsSummary.reduce(
(acc, val) => {
return {
totalSale: acc.totalSale.add(Dinero(val.sale)),
totalCost: acc.totalCost.add(Dinero(val.cost)),
};
},
{
totalSale: Dinero(),
totalCost: Dinero(),
}
);
return (
<Table.Summary.Row>
<Table.Summary.Cell>
<Typography.Title level={4}>
{t("general.labels.totals")}
</Typography.Title>
</Table.Summary.Cell>
<Table.Summary.Cell>
{totals.totalSale.toFormat()}
</Table.Summary.Cell>
<Table.Summary.Cell>
{
// totals.totalCost.toFormat()
}
</Table.Summary.Cell>
<Table.Summary.Cell></Table.Summary.Cell>
<Table.Summary.Cell></Table.Summary.Cell>
</Table.Summary.Row>
);
}}
/>
</Card>
);
}

View File

@@ -1,9 +1,12 @@
import { useLazyQuery } from "@apollo/client";
import { Button, Input, Modal, Table } from "antd";
import React, { useState } from "react";
import { Modal, Button, Table, Input } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { SEARCH_DMS_VEHICLES } from "../../graphql/dms.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
import AlertComponent from "../alert/alert.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -12,83 +15,71 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkMakes);
export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkVehicles);
export function DmsCdkMakes({ bodyshop, form, socket }) {
const [makesList, setMakesList] = useState([]);
const [searchText, setSearchText] = useState("");
const [loading, setLoading] = useState(false);
export function DmsCdkVehicles({ bodyshop, form, socket, job }) {
const [visible, setVisible] = useState(false);
const [selectedModel, setSelectedModel] = useState(null);
const { t } = useTranslation();
const [callSearch, { loading, error, data }] =
useLazyQuery(SEARCH_DMS_VEHICLES);
const columns = [
{
title: t("jobs.fields.dms.makeFullName"),
dataIndex: "makeFullName",
key: "makeFullName",
title: t("vehicles.fields.v_make_desc"),
dataIndex: "make",
key: "make",
},
{
title: t("jobs.fields.dms.modelFullName"),
dataIndex: "modelFullName",
key: "modelFullName",
title: t("vehicles.fields.v_model_desc"),
dataIndex: "model",
key: "model",
},
{
title: t("jobs.fields.dms.makeCode"),
dataIndex: "makeCode",
key: "makeCode",
title: t("jobs.fields.dms.dms_make"),
dataIndex: "makecode",
key: "makecode",
},
{
title: t("jobs.fields.dms.modelCode"),
dataIndex: "modelCode",
key: "modelCode",
title: t("jobs.fields.dms.dms_model"),
dataIndex: "modelcode",
key: "modelcode",
},
];
const filteredMakes =
searchText !== "" && searchText
? makesList.filter(
(make) =>
searchText
.split(" ")
.some((v) =>
make.makeFullName.toLowerCase().includes(v.toLowerCase())
) ||
searchText
.split(" ")
.some((v) =>
make.modelFullName.toLowerCase().includes(v.toLowerCase())
)
)
: makesList;
return (
<div>
<Modal width={"90%"} visible={visible} onCancel={() => setVisible(false)}>
<Modal
width={"90%"}
visible={visible}
onCancel={() => setVisible(false)}
onOk={() => {
form.setFieldsValue({
dms_make: selectedModel.makecode,
dms_model: selectedModel.modelcode,
});
setVisible(false);
}}
>
{error && <AlertComponent error={error.message} />}
<Table
title={() => (
<Input.Search
onSearch={(val) => setSearchText(val)}
onSearch={(val) => callSearch({ variables: { search: val } })}
placeholder={t("general.labels.search")}
/>
)}
columns={columns}
loading={loading}
id="id"
dataSource={filteredMakes}
rowKey="id"
dataSource={data ? data.search_dms_vehicles : []}
onRow={(record) => {
return {
onClick: setSelectedModel(record),
onClick: () => setSelectedModel(record),
};
}}
rowSelection={{
onSelect: (record, selected, ...props) => {
console.log(
"🚀 ~ file: dms-cdk-makes.component.jsx ~ line 85 ~ record, selected, ...props",
record,
selected,
...props
);
onSelect: (record) => {
setSelectedModel(record);
},
@@ -100,15 +91,14 @@ export function DmsCdkMakes({ bodyshop, form, socket }) {
<Button
onClick={() => {
setVisible(true);
setLoading(true);
socket.emit("cdk-get-makes", bodyshop.cdk_dealerid, (makes) => {
console.log("Called back", makes);
setMakesList(makes);
setLoading(false);
callSearch({
variables: {
search: job && job.v_model_desc && job.v_model_desc.substr(0, 3),
},
});
}}
>
Get Makes
{t("jobs.actions.dms.findmakemodelcode")}
</Button>
</div>
);

View File

@@ -0,0 +1,34 @@
import { Button } from "antd";
import axios from "axios";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkMakesRefetch);
export function DmsCdkMakesRefetch({ bodyshop, form, socket }) {
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const handleRefetch = async () => {
setLoading(true);
const response = await axios.post("/cdk/getvehicles", {
cdk_dealerid: bodyshop.cdk_dealerid,
bodyshopid: bodyshop.id,
});
console.log(response);
setLoading(false);
};
return (
<Button loading={loading} onClick={handleRefetch}>
{t("jobs.actions.dms.refetchmakesmodels")}
</Button>
);
}

View File

@@ -1,10 +1,24 @@
import { Button, Table } from "antd";
import { Button, Table, Col , Checkbox} from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { socket } from "../../pages/dms/dms.container";
import PhoneFormatter from "../../utils/PhoneFormatter";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { alphaSort } from "../../utils/sorters";
export default function DmsCustomerSelector() {
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(DmsCustomerSelector);
export function DmsCustomerSelector({ bodyshop }) {
const { t } = useTranslation();
const [customerList, setcustomerList] = useState([]);
const [visible, setVisible] = useState(false);
@@ -15,63 +29,93 @@ export default function DmsCustomerSelector() {
setcustomerList(customerList);
});
const onOk = () => {
const onUseSelected = () => {
setVisible(false);
socket.emit("cdk-selected-customer", selectedCustomer);
setSelectedCustomer(null);
};
const onUseGeneric = () => {
setVisible(false);
socket.emit(
"cdk-selected-customer",
bodyshop.cdk_configuration.generic_customer_number
);
setSelectedCustomer(null);
};
const onCreateNew = () => {
setVisible(false);
socket.emit("cdk-selected-customer", null);
setSelectedCustomer(null);
};
const columns = [
{
title: t("dms.fields.name1"),
title: t("jobs.fields.dms.id"),
dataIndex: ["id", "value"],
key: "id",
},
{
title: t("jobs.fields.dms.vinowner"),
dataIndex: "vinOwner",
key: "vinOwner",
render: (text, record) => <Checkbox disabled checked={record.vinOwner}/>
},
{
title: t("jobs.fields.dms.name1"),
dataIndex: ["name1", "fullName"],
key: "name1",
sorter: (a, b) => alphaSort(a.name1?.fullName, b.name1?.fullName),
},
{
title: t("dms.fields.name2"),
dataIndex: ["name2", "fullName"],
key: "name2",
sorter: (a, b) => alphaSort(a.name2?.fullName, b.name2?.fullName),
},
{
title: t("dms.fields.phone"),
dataIndex: ["contactInfo", "mainTelephoneNumber", "value"],
key: "phone",
render: (record, value) => (
<PhoneFormatter>
{record.contactInfo?.mainTelephoneNumber?.value}
</PhoneFormatter>
),
},
{
title: t("dms.fields.address"),
title: t("jobs.fields.dms.address"),
//dataIndex: ["name2", "fullName"],
key: "address",
render: (record, value) =>
`${record.address?.addressLine[0]}, ${record.address?.city} ${record.address?.stateOrProvince} ${record.address?.postalCode}`,
`${record?.address?.addressLine[0]}, ${record.address?.city} ${record.address?.stateOrProvince} ${record.address?.postalCode}`,
},
];
if (!visible) return <></>;
return (
<Table
title={() => (
<div>
<Button onClick={onOk}>Select</Button>
</div>
)}
pagination={{ position: "top" }}
columns={columns}
rowKey={(record) => record.id.value}
dataSource={customerList}
//onChange={handleTableChange}
rowSelection={{
onSelect: (props) => {
setSelectedCustomer(props.id.value);
},
type: "radio",
selectedRowKeys: [selectedCustomer],
}}
/>
<Col span={24}>
<Table
title={() => (
<div>
<Button onClick={onUseSelected} disabled={!selectedCustomer}>
{t("jobs.actions.dms.useselected")}
</Button>
<Button
onClick={onUseGeneric}
disabled={
!(
bodyshop.cdk_configuration &&
bodyshop.cdk_configuration.generic_customer_number
)
}
>
{t("jobs.actions.dms.usegeneric")}
</Button>
<Button onClick={onCreateNew}>
{t("jobs.actions.dms.createnewcustomer")}
</Button>
</div>
)}
pagination={{ position: "top" }}
columns={columns}
rowKey={(record) => record.id.value}
dataSource={customerList}
//onChange={handleTableChange}
rowSelection={{
onSelect: (props) => {
setSelectedCustomer(props.id.value);
},
type: "radio",
selectedRowKeys: [selectedCustomer],
}}
/>
</Col>
);
}

View File

@@ -1,13 +1,27 @@
import { DeleteFilled } from "@ant-design/icons";
import { Button, Form, Input } from "antd";
import {
Button,
Card,
Divider,
Form,
Input,
InputNumber,
Select,
Space,
Statistic,
Typography,
} from "antd";
import Dinero from "dinero.js";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { determineDmsType } from "../../pages/dms/dms.container";
import { selectBodyshop } from "../../redux/user/user.selectors";
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -17,134 +31,310 @@ const mapDispatchToProps = (dispatch) => ({
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm);
export function DmsPostForm({ bodyshop, socket, jobId }) {
export function DmsPostForm({ bodyshop, socket, job }) {
const [form] = Form.useForm();
const { t } = useTranslation();
const handlePayerSelect = (value, index) => {
form.setFieldsValue({
payers: form.getFieldValue("payers").map((payer, mapIndex) => {
if (index !== mapIndex) return payer;
const cdkPayer =
bodyshop.cdk_configuration.payers &&
bodyshop.cdk_configuration.payers.find((i) => i.name === value);
if (!cdkPayer) return payer;
return {
...cdkPayer,
dms_acctnumber: cdkPayer.dms_acctnumber,
controlnumber: job && job[cdkPayer.control_type],
};
}),
});
};
const handleFinish = (values) => {
socket.emit(`${determineDmsType(bodyshop)}-export-job`, {
jobid: job.id,
txEnvelope: values,
});
};
return (
<Form form={form} layout="vertical">
<LayoutFormRow>
<Form.Item
name="journal"
label={t("jobs.fields.dms.journal")}
initialValue={
bodyshop.cdk_configuration &&
bodyshop.cdk_configuration.default_journal
}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="dms_make"
label={t("jobs.fields.dms.dms_make")}
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="dms_make"
label={t("jobs.fields.dms.dms_make")}
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<DmsCdkMakes form={form} socket={socket} />
</LayoutFormRow>
<Form.List name={["payers"]}>
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow>
<Form.Item
label={t("jobs.fields.dms.payer.name")}
key={`${index}name`}
name={[field.name, "name"]}
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.account")}
key={`${index}account`}
name={[field.name, "account"]}
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.amount")}
key={`${index}amount`}
name={[field.name, "amount"]}
rules={[
{
required: true,
},
]}
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.controlnumber")}
key={`${index}controlnumber`}
name={[field.name, "controlnumber"]}
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</LayoutFormRow>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
</div>
);
<Card title={t("jobs.labels.dms.postingform")}>
<Form
form={form}
layout="vertical"
onFinish={handleFinish}
initialValues={{
story: t("jobs.labels.dms.defaultstory", {
ro_number: job.ro_number,
area_of_damage: job.area_of_damage && job.area_of_damage.impact1,
}).substr(0, 239),
}}
</Form.List>
</Form>
>
<LayoutFormRow grow>
<Form.Item
name="journal"
label={t("jobs.fields.dms.journal")}
initialValue={
bodyshop.cdk_configuration &&
bodyshop.cdk_configuration.default_journal
}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="kmin"
label={t("jobs.fields.kmin")}
initialValue={job && job.kmin}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber disabled />
</Form.Item>
<Form.Item
name="kmout"
label={t("jobs.fields.kmout")}
initialValue={job && job.kmout}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber disabled />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow style={{ justifyContent: "center" }} grow>
<Form.Item
name="dms_make"
label={t("jobs.fields.dms.dms_make")}
rules={[
{
required: true,
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item
name="dms_model"
label={t("jobs.fields.dms.dms_model")}
rules={[
{
required: true,
},
]}
>
<Input disabled />
</Form.Item>
<DmsCdkMakes form={form} socket={socket} job={job} />
<DmsCdkMakesRefetch />
</LayoutFormRow>
<Form.Item
name="story"
label={t("jobs.fields.dms.story")}
rules={[
{
required: true,
},
]}
>
<Input.TextArea maxLength={240} />
</Form.Item>
<Divider />
<Form.List name={["payers"]}>
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<Space wrap>
<Form.Item
label={t("jobs.fields.dms.payer.name")}
key={`${index}name`}
name={[field.name, "name"]}
rules={[
{
required: true,
},
]}
>
<Select
style={{ minWidth: "15rem" }}
onSelect={(value) => handlePayerSelect(value, index)}
>
{bodyshop.cdk_configuration &&
bodyshop.cdk_configuration.payers &&
bodyshop.cdk_configuration.payers.map((payer) => (
<Select.Option key={payer.name}>
{payer.name}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.dms_acctnumber")}
key={`${index}dms_acctnumber`}
name={[field.name, "dms_acctnumber"]}
rules={[
{
required: true,
},
]}
>
<Input disabled />
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.amount")}
key={`${index}amount`}
name={[field.name, "amount"]}
rules={[
{
required: true,
},
]}
>
<CurrencyInput min={0} />
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.controlnumber")}
key={`${index}controlnumber`}
name={[field.name, "controlnumber"]}
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item shouldUpdate>
{() => {
const payers = form.getFieldValue("payers");
const row = payers && payers[index];
const cdkPayer =
bodyshop.cdk_configuration.payers &&
bodyshop.cdk_configuration.payers.find(
(i) => i && row && i.name === row.name
);
return (
<div>
{cdkPayer &&
t(`jobs.fields.${cdkPayer.control_type}`)}
</div>
);
}}
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</Space>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
disabled={!(fields.length < 3)}
onClick={() => {
if (fields.length < 3) add();
}}
style={{ width: "100%" }}
>
{t("jobs.actions.dms.addpayer")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
<Form.Item shouldUpdate>
{() => {
//Perform Calculation to determine discrepancy.
let totalAllocated = Dinero();
const payers = form.getFieldValue("payers");
payers &&
payers.forEach((payer) => {
totalAllocated = totalAllocated.add(
Dinero({ amount: Math.round((payer?.amount || 0) * 100) })
);
});
const totals =
socket.allocationsSummary &&
socket.allocationsSummary.reduce(
(acc, val) => {
return {
totalSale: acc.totalSale.add(Dinero(val.sale)),
totalCost: acc.totalCost.add(Dinero(val.cost)),
};
},
{
totalSale: Dinero(),
totalCost: Dinero(),
}
);
const discrep = totals
? totals.totalSale.subtract(totalAllocated)
: Dinero();
return (
<Space size="large" wrap align="center">
<Statistic
title={t("jobs.labels.subtotal")}
value={(totals ? totals.totalSale : Dinero()).toFormat()}
/>
<Typography.Title>-</Typography.Title>
<Statistic
title={t("jobs.labels.dms.totalallocated")}
value={totalAllocated.toFormat()}
/>
<Typography.Title>=</Typography.Title>
<Statistic
title={t("jobs.labels.dms.notallocated")}
valueStyle={{
color: discrep.getAmount() === 0 ? "green" : "red",
}}
value={discrep.toFormat()}
/>
<Button
disabled={
!socket.allocationsSummary || discrep.getAmount() !== 0
}
htmlType="submit"
>
{t("jobs.actions.dms.post")}
</Button>
</Space>
);
}}
</Form.Item>
</Form>
</Card>
);
}

View File

@@ -9,6 +9,7 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { tracker } from "../../App/App.container";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -36,6 +37,7 @@ class ErrorBoundary extends React.Component {
componentDidCatch(error, info) {
console.log("Exception Caught by Error Boundary.", error, info);
this.setState({ ...this.state, error, info });
tracker.event("error_boundary", error, true);
}
handleErrorSubmit = () => {

View File

@@ -2,7 +2,7 @@ import { withApollo } from "@apollo/client/react/hoc";
import React, { Component } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent, messaging } from "../../firebase/firebase.utils";
//import { logImEXEvent, messaging } from "../../firebase/firebase.utils";
import { selectCurrentUser } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
@@ -15,21 +15,20 @@ const mapDispatchToProps = (dispatch) => ({
class FcmNotificationComponent extends Component {
async componentDidMount() {
//const { client, currentUser } = this.props;
if (!!!messaging) return; //Skip all of the notification functionality if the firebase SDK could not start.
messaging
.requestPermission()
.then(async function () {
// const token = await messaging.getToken();
// client.mutate({
// mutation: UPDATE_FCM_TOKEN,
// variables: { authEmail: currentUser.email, token: { [token]: true } },
// });
})
.catch(function (err) {
console.log("Unable to get permission to notify.", err);
logImEXEvent("fcm_permission_denied", { message: err });
});
// if (!!!messaging) return; //Skip all of the notification functionality if the firebase SDK could not start.
// messaging
// .requestPermission()
// .then(async function () {
// // const token = await messaging.getToken();
// // client.mutate({
// // mutation: UPDATE_FCM_TOKEN,
// // variables: { authEmail: currentUser.email, token: { [token]: true } },
// // });
// })
// .catch(function (err) {
// console.log("Unable to get permission to notify.", err);
// logImEXEvent("fcm_permission_denied", { message: err });
// });
}
render() {

View File

@@ -62,13 +62,16 @@ export default function GlobalSearch() {
}`,
label: (
<Link to={`/manage/owners/${owner.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<Space size="small" split={<Divider type="vertical" />} wrap>
<span>{`${owner.ownr_fn || ""} ${owner.ownr_ln || ""} ${
owner.ownr_co_nm || ""
}`}</span>
<PhoneNumberFormatter>
{owner.ownr_ph1}
</PhoneNumberFormatter>
<PhoneNumberFormatter>
{owner.ownr_ph2}
</PhoneNumberFormatter>
</Space>
</Link>
),
@@ -168,7 +171,6 @@ export default function GlobalSearch() {
<AutoComplete
options={options}
onSearch={handleSearch}
allowClear
placeholder={t("general.labels.globalsearch")}
>
<Input.Search loading={loading} />

View File

@@ -106,6 +106,12 @@ export function ScheduleEventComponent({
jobid={event.job.id}
/>
</DataLabel>
<DataLabel label={t("jobs.fields.ownr_ph2")}>
<ChatOpenButton
phone={event.job && event.job.ownr_ph2}
jobid={event.job.id}
/>
</DataLabel>
<DataLabel label={t("jobs.fields.alt_transport")}>
{(event.job && event.job.alt_transport) || ""}
<ScheduleAtChange job={event && event.job} />

View File

@@ -64,14 +64,16 @@ export function JobChecklistForm({
...(type === "intake" && { actual_in: new Date() }),
...(type === "intake" && {
production_vars: {
...job.production_vars,
...values.production_vars,
...(job ? job.production_vars : {}),
note:
values.production_vars &&
values.production_vars.note &&
values.production_vars.note !== ""
? job.production_vars && values.production_vars.note
: job.production_vars && job.production_vars.note,
? values &&
values.production_vars &&
values.production_vars.note
: job && job.production_vars && job.production_vars.note,
},
}),
...(type === "intake" && {
@@ -165,7 +167,11 @@ export function JobChecklistForm({
title={t("checklist.labels.checklist")}
extra={
!readOnly && (
<Button loading={loading} onClick={() => form.submit()}>
<Button
loading={loading}
type="primary"
onClick={() => form.submit()}
>
{t("general.actions.submit")}
</Button>
)
@@ -245,6 +251,7 @@ export function JobChecklistForm({
name={["production_vars", "note"]}
label={t("jobs.fields.production_vars.note")}
disabled={readOnly}
trigger="onChange"
>
<Input.TextArea rows={3} disabled={readOnly} />
</Form.Item>

View File

@@ -112,60 +112,46 @@ export function JobDetailCards({ setPrintCenterContext }) {
<Divider type="horizontal" />
<Row gutter={[16, 16]}>
<Col {...span}>
<Card.Grid style={{ width: "100%", height: "100%" }}>
<JobDetailCardsInsuranceComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Card.Grid>
<JobDetailCardsInsuranceComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...span}>
<Card.Grid style={{ width: "100%", height: "100%" }}>
<JobDetailCardsTotalsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Card.Grid>
<JobDetailCardsTotalsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...span}>
<Card.Grid style={{ width: "100%", height: "100%" }}>
<JobDetailCardsDatesComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Card.Grid>
<JobDetailCardsDatesComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...span}>
<Card.Grid style={{ width: "100%", height: "100%" }}>
<JobDetailCardsPartsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Card.Grid>
<JobDetailCardsPartsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...span}>
<Card.Grid style={{ width: "100%", height: "100%" }}>
<JobDetailCardsNotesComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Card.Grid>
<JobDetailCardsNotesComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...span}>
<Card.Grid style={{ width: "100%", height: "100%" }}>
<JobDetailCardsDocumentsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Card.Grid>
<JobDetailCardsDocumentsComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
<Col {...span}>
<Card.Grid style={{ width: "100%", height: "100%" }}>
<JobDetailCardsDamageComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Card.Grid>
<JobDetailCardsDamageComponent
loading={loading}
data={data ? data.jobs_by_pk : null}
/>
</Col>
</Row>
</Card>

View File

@@ -1,7 +1,7 @@
import { Timeline } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { DateFormatter } from "../../utils/DateFormatter";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import CardTemplate from "./job-detail-cards.template.component";
export default function JobDetailCardsDatesComponent({ loading, data }) {
@@ -26,80 +26,86 @@ export default function JobDetailCardsDatesComponent({ loading, data }) {
) ? (
<div>{t("jobs.errors.nodates")}</div>
) : null}
{data.date_last_contacted ? (
<Timeline.Item>
<label>{t("jobs.fields.date_last_contacted")}: </label>
<DateTimeFormatter>{data.date_last_contacted}</DateTimeFormatter>
</Timeline.Item>
) : null}
{data.date_open ? (
<Timeline.Item>
<label>{t("jobs.fields.date_open")}: </label>
<DateFormatter>{data.date_open}</DateFormatter>
<DateTimeFormatter>{data.date_open}</DateTimeFormatter>
</Timeline.Item>
) : null}
{data.date_estimated ? (
<Timeline.Item>
<label>{t("jobs.fields.date_estimated")}: </label>
<DateFormatter>{data.date_estimated}</DateFormatter>
<DateTimeFormatter>{data.date_estimated}</DateTimeFormatter>
</Timeline.Item>
) : null}
{data.date_scheduled ? (
<Timeline.Item>
<label>{t("jobs.fields.date_scheduled")}: </label>
<DateFormatter>{data.date_scheduled}</DateFormatter>
<DateTimeFormatter>{data.date_scheduled}</DateTimeFormatter>
</Timeline.Item>
) : null}
{data.scheduled_in ? (
<Timeline.Item>
<label>{t("jobs.fields.scheduled_in")}: </label>
<DateFormatter>{data.scheduled_in}</DateFormatter>
<DateTimeFormatter>{data.scheduled_in}</DateTimeFormatter>
</Timeline.Item>
) : null}
{data.actual_in ? (
<Timeline.Item>
<label>{t("jobs.fields.actual_in")}: </label>
<DateFormatter>{data.actual_in}</DateFormatter>
<DateTimeFormatter>{data.actual_in}</DateTimeFormatter>
</Timeline.Item>
) : null}
{data.scheduled_completion ? (
<Timeline.Item>
<label>{t("jobs.fields.scheduled_completion")}: </label>
<DateFormatter>{data.scheduled_completion}</DateFormatter>
<DateTimeFormatter>{data.scheduled_completion}</DateTimeFormatter>
</Timeline.Item>
) : null}
{data.actual_completion ? (
<Timeline.Item>
<label>{t("jobs.fields.actual_completion")}: </label>
<DateFormatter>{data.actual_completion}</DateFormatter>
<DateTimeFormatter>{data.actual_completion}</DateTimeFormatter>
</Timeline.Item>
) : null}
{data.scheduled_delivery ? (
<Timeline.Item>
<label>{t("jobs.fields.scheduled_delivery")}: </label>
<DateFormatter>{data.scheduled_delivery}</DateFormatter>
<DateTimeFormatter>{data.scheduled_delivery}</DateTimeFormatter>
</Timeline.Item>
) : null}
{data.actual_delivery ? (
<Timeline.Item>
<label>{t("jobs.fields.actual_delivery")}: </label>
<DateFormatter>{data.actual_delivery}</DateFormatter>
<DateTimeFormatter>{data.actual_delivery}</DateTimeFormatter>
</Timeline.Item>
) : null}
{data.date_invoiced ? (
<Timeline.Item>
<label>{t("jobs.fields.date_invoiced")}: </label>
<DateFormatter>{data.date_invoiced}</DateFormatter>
<DateTimeFormatter>{data.date_invoiced}</DateTimeFormatter>
</Timeline.Item>
) : null}
{data.date_exported ? (
<Timeline.Item>
<label>{t("jobs.fields.date_exported")}: </label>
<DateFormatter>{data.date_exported}</DateFormatter>
<DateTimeFormatter>{data.date_exported}</DateTimeFormatter>
</Timeline.Item>
) : null}
</Timeline>

View File

@@ -1,8 +1,8 @@
import { Carousel } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { GenerateThumbUrl } from "../jobs-documents-gallery/job-documents.utility";
import CardTemplate from "./job-detail-cards.template.component";
import { DetermineFileType } from "../documents-upload/documents-upload.utility";
export default function JobDetailCardsDocumentsComponent({ loading, data }) {
const { t } = useTranslation();
@@ -22,15 +22,7 @@ export default function JobDetailCardsDocumentsComponent({ loading, data }) {
{data.documents.length > 0 ? (
<Carousel autoplay>
{data.documents.map((item) => (
<img
key={item.id}
src={`${
process.env.REACT_APP_CLOUDINARY_ENDPOINT
}/${DetermineFileType(item.type)}/upload/${
process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
}/${item.key}`}
alt={item.name}
/>
<img key={item.id} src={GenerateThumbUrl(item)} alt={item.name} />
))}
</Carousel>
) : (

View File

@@ -12,11 +12,13 @@ export default function JobDetailCardTemplate({
if (extraLink) extra = { extra: <Link to={extraLink}>More</Link> };
return (
<Card
size='small'
className='job-card'
size="small"
className="job-card"
title={title}
loading={loading}
{...extra}>
style={{ height: "100%" }}
{...extra}
>
{otherProps.children}
</Card>
);

View File

@@ -36,6 +36,7 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
// import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
// import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
import _ from "lodash";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -295,18 +296,17 @@ export function JobLinesComponent({
onClick={async () => {
await deleteJobLine({
variables: { joblineId: record.id },
// update(cache) {
// cache.modify({
// id: cache.identify(job),
// fields: {
// joblines(existingJobLines, { readField }) {
// return existingJobLines.filter(
// (jlRef) => record.id !== readField("id", jlRef)
// );
// },
// },
// });
// },
update(cache) {
cache.modify({
fields: {
joblines(existingJobLines, { readField }) {
return existingJobLines.filter(
(jlRef) => record.id !== readField("id", jlRef)
);
},
},
});
},
});
await axios.post("/job/totalsssu", {
id: job.id,
@@ -334,10 +334,12 @@ export function JobLinesComponent({
const markedTypes = [e.key];
if (e.key === "PAN") markedTypes.push("PAP");
if (e.key === "PAS") markedTypes.push("PASL");
setSelectedLines([
...selectedLines,
...jobLines.filter((item) => markedTypes.includes(item.part_type)),
]);
setSelectedLines(
_.uniq([
...selectedLines,
...jobLines.filter((item) => markedTypes.includes(item.part_type)),
])
);
}
};
@@ -400,7 +402,7 @@ export function JobLinesComponent({
setState({
...state,
filteredInfo: {
part_type: ["PAN,PAC,PAR,PAL,PAA,PAM,PAP,PAS,PASL"],
part_type: ["PAN,PAC,PAR,PAL,PAA,PAM,PAP,PAS,PASL,PAG"],
},
});
}}
@@ -435,7 +437,7 @@ export function JobLinesComponent({
columns={columns}
rowKey="id"
loading={loading}
pagination={{ position: "top", defaultPageSize: 50 }}
pagination={false}
dataSource={jobLines}
onChange={handleTableChange}
scroll={{

View File

@@ -1,4 +1,4 @@
import { Form, Input, InputNumber, Modal, Select } from "antd";
import { Form, Input, InputNumber, Modal, Select, Switch } from "antd";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import InputCurrency from "../form-items-formatted/currency-form-item.component";
@@ -115,18 +115,18 @@ export default function JobLinesUpsertModalComponent({
<Form.Item
label={t("joblines.fields.mod_lb_hrs")}
name="mod_lb_hrs"
// rules={[
// ({ getFieldValue }) => ({
// validator(rule, value) {
// if (!!getFieldValue("mod_lbr_ty") === !!value) {
// return Promise.resolve();
// }
// return Promise.reject(
// t("joblines.validations.hrsrequirediflbrtyp")
// );
// },
// }),
// ]}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!!getFieldValue("mod_lbr_ty") === !!value) {
return Promise.resolve();
}
return Promise.reject(
t("joblines.validations.hrsrequirediflbrtyp")
);
},
}),
]}
>
<InputNumber precision={1} />
</Form.Item>
@@ -169,49 +169,49 @@ export default function JobLinesUpsertModalComponent({
<Form.Item
label={t("joblines.fields.part_qty")}
name="part_qty"
// rules={[
// ({ getFieldValue }) => ({
// validator(rule, value) {
// if (!!getFieldValue("part_type") === !!value) {
// return Promise.resolve();
// }
// return Promise.reject(
// t("joblines.validations.requiredifparttype")
// );
// },
// }),
// ]}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!!getFieldValue("part_type") === !!value) {
return Promise.resolve();
}
return Promise.reject(
t("joblines.validations.requiredifparttype")
);
},
}),
]}
>
<InputNumber precision={0} min={0} />
</Form.Item>
<Form.Item label={t("joblines.fields.db_price")} name="db_price">
{/* <Form.Item label={t("joblines.fields.db_price")} name="db_price">
<InputCurrency precision={2} min={0} />
</Form.Item>
</Form.Item> */}
<Form.Item
label={t("joblines.fields.act_price")}
name="act_price"
// rules={[
// ({ getFieldValue }) => ({
// validator(rule, value) {
// if (!value || getFieldValue("part_type") !== "PAE") {
// return Promise.resolve();
// }
// return Promise.reject(
// t("joblines.validations.zeropriceexistingpart")
// );
// },
// }),
// ({ getFieldValue }) => ({
// validator(rule, value) {
// if (!!getFieldValue("part_type") === !!value) {
// return Promise.resolve();
// }
// return Promise.reject(
// t("joblines.validations.requiredifparttype")
// );
// },
// }),
// ]}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (!value || getFieldValue("part_type") !== "PAE") {
return Promise.resolve();
}
return Promise.reject(
t("joblines.validations.zeropriceexistingpart")
);
},
}),
({ getFieldValue }) => ({
validator(rule, value) {
if (!!getFieldValue("part_type") === !!value) {
return Promise.resolve();
}
return Promise.reject(
t("joblines.validations.requiredifparttype")
);
},
}),
]}
>
<InputCurrency precision={2} min={0} />
</Form.Item>
@@ -222,6 +222,14 @@ export default function JobLinesUpsertModalComponent({
>
<InputNumber precision={0} min={0} max={100} />
</Form.Item>
<Form.Item
label={t("joblines.fields.tax_part")}
name="tax_part"
valuePropName="checked"
initialValue={true}
>
<Switch />
</Form.Item>
</LayoutFormRow>
</Form>
</Modal>

View File

@@ -44,6 +44,7 @@ function JobLinesUpsertModalContainer({
},
],
},
refetchQueries: ["GET_LINE_TICKET_BY_PK"],
});
if (!r.errors) {
await Axios.post("/job/totalsssu", {
@@ -69,6 +70,7 @@ function JobLinesUpsertModalContainer({
lineId: jobLineEditModal.context.id,
line: values,
},
refetchQueries: ["GET_LINE_TICKET_BY_PK"],
});
if (!r.errors) {
notification["success"]({

View File

@@ -21,7 +21,8 @@ export default function JobReconciliationBillsTable({
title: t("billlines.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
width: "35%",
ellipsis: true,
minWidth: "65rem",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
@@ -30,7 +31,7 @@ export default function JobReconciliationBillsTable({
title: t("billlines.labels.from"),
dataIndex: "from",
key: "from",
width: "20%",
ellipsis: true,
render: (text, record) =>
`${record.bill.vendor && record.bill.vendor.name} / ${
@@ -42,6 +43,7 @@ export default function JobReconciliationBillsTable({
dataIndex: "actual_price",
key: "actual_price",
sorter: (a, b) => a.actual_price - b.actual_price,
width: "7rem",
sortOrder:
state.sortedInfo.columnKey === "actual_price" && state.sortedInfo.order,
render: (text, record) => (
@@ -53,6 +55,7 @@ export default function JobReconciliationBillsTable({
dataIndex: "actual_cost",
key: "actual_cost",
sorter: (a, b) => a.actual_cost - b.actual_cost,
width: "7rem",
sortOrder:
state.sortedInfo.columnKey === "actual_cost" && state.sortedInfo.order,
render: (text, record) => (
@@ -64,6 +67,7 @@ export default function JobReconciliationBillsTable({
dataIndex: "quantity",
key: "quantity",
sorter: (a, b) => a.quantity - b.quantity,
width: "4rem",
sortOrder:
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
},
@@ -72,9 +76,11 @@ export default function JobReconciliationBillsTable({
dataIndex: "is_credit_memo",
key: "is_credit_memo",
sorter: (a, b) => a.bill.is_credit_memo - b.bill.is_credit_memo,
width: "8rem",
sortOrder:
state.sortedInfo.columnKey === "is_credit_memo" &&
state.sortedInfo.order,
render: (text, record) => (
<Checkbox disabled checked={record.bill.is_credit_memo} />
),
@@ -94,7 +100,7 @@ export default function JobReconciliationBillsTable({
<Table
pagination={false}
size="small"
scroll={{ y: "80vh", x: true }}
scroll={{ y: "60vh" }}
columns={columns}
rowKey="id"
dataSource={invoiceLineData}
@@ -102,6 +108,9 @@ export default function JobReconciliationBillsTable({
rowSelection={{
onChange: handleOnRowClick,
selectedRowKeys: selectedLines,
getCheckboxProps: (record) => {
return { disabled: record.deductedfromlbr };
},
}}
/>
</div>

View File

@@ -1,12 +1,12 @@
.imex-reconciliation-modal {
top: 20px;
.ant-modal-content {
height: 95vh;
display: flex;
flex-direction: column;
.ant-modal-body {
display: flex;
flex: 1;
}
}
// .ant-modal-content {
// height: 95vh;
// display: flex;
// flex-direction: column;
// .ant-modal-body {
// display: flex;
// flex: 1;
// }
// }
}

View File

@@ -23,6 +23,7 @@ export default function JobReconcilitionPartsTable({
dataIndex: "line_desc",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
ellipses: true,
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
},
@@ -57,6 +58,7 @@ export default function JobReconcilitionPartsTable({
dataIndex: "act_price",
key: "act_price",
sorter: (a, b) => a.act_price - b.act_price,
width: "7rem",
sortOrder:
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
@@ -68,10 +70,12 @@ export default function JobReconcilitionPartsTable({
title: t("joblines.fields.part_qty"),
dataIndex: "part_qty",
key: "part_qty",
width: "4rem",
},
{
title: t("joblines.fields.total"),
dataIndex: "total",
width: "7rem",
key: "total",
sorter: (a, b) => a.act_price * a.part_qty - b.act_price * b.part_qty,
sortOrder:
@@ -89,6 +93,7 @@ export default function JobReconcilitionPartsTable({
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
width: "6rem",
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
},
@@ -108,7 +113,7 @@ export default function JobReconcilitionPartsTable({
pagination={false}
columns={columns}
size="small"
scroll={{ y: "80vh", x: true }}
scroll={{ y: "60vh" }}
rowKey="id"
dataSource={jobLineData}
onChange={handleTableChange}

View File

@@ -11,7 +11,7 @@ export const reconcileByAssocLine = (
const [selectedJobLines, setSelectedJobLines] = jobLineState;
const allJoblinesFromBills = billLines
.filter((bl) => bl.joblineid && !(bl.jobline && bl.jobline.removed))
.filter((bl) => bl.joblineid && bl.jobline && !bl.jobline.removed)
.map((bl) => bl.joblineid);
const duplicatedJobLinesbyInvoiceId = _.filter(

View File

@@ -1,12 +1,24 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Form, notification, Popover } from "antd";
import { useMutation, useLazyQuery } from "@apollo/client";
import {
Button,
Card,
Form,
InputNumber,
notification,
Popover,
Space,
} from "antd";
import moment from "moment";
import React, { useState } from "react";
import React, { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries";
import {
INSERT_SCOREBOARD_ENTRY,
QUERY_SCOREBOARD_ENTRY,
UPDATE_SCOREBOARD_ENTRY,
} from "../../graphql/scoreboard.queries";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import InputNumberCalculator from "../form-input-number-calculator/form-input-number-calculator.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
export default function ScoreboardAddButton({
job,
@@ -15,17 +27,46 @@ export default function ScoreboardAddButton({
}) {
const { t } = useTranslation();
const [insertScoreboardEntry] = useMutation(INSERT_SCOREBOARD_ENTRY);
const [updateScoreboardEntry] = useMutation(UPDATE_SCOREBOARD_ENTRY);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const [visibility, setVisibility] = useState(false);
const [callQuery, { loading: entryLoading, data: entryData }] = useLazyQuery(
QUERY_SCOREBOARD_ENTRY
);
useEffect(() => {
if (visibility) {
callQuery({ variables: { jobid: job.id } });
}
}, [visibility, job.id, callQuery]);
useEffect(() => {
console.log("UE", entryData);
if (entryData && entryData.scoreboard && entryData.scoreboard[0]) {
console.log("Setting FOrm");
form.setFieldsValue(entryData.scoreboard[0]);
}
}, [entryData, form]);
const handleFinish = async (values) => {
logImEXEvent("job_close_add_to_scoreboard");
setLoading(true);
const result = await insertScoreboardEntry({
variables: { sbInput: [{ jobid: job.id, ...values }] },
});
let result;
if (entryData && entryData.scoreboard && entryData.scoreboard[0]) {
result = await updateScoreboardEntry({
variables: {
sbId: entryData.scoreboard[0].id,
sbInput: values,
},
});
} else {
result = await insertScoreboardEntry({
variables: { sbInput: [{ jobid: job.id, ...values }] },
});
}
if (!!result.errors) {
notification["error"]({
@@ -45,53 +86,62 @@ export default function ScoreboardAddButton({
const overlay = (
<Card>
<div>
<Form
form={form}
layout="vertical"
onFinish={handleFinish}
initialValues={{}}
>
<Form.Item
label={t("scoreboard.fields.date")}
name="date"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
{entryLoading ? (
<LoadingSpinner />
) : (
<Form
form={form}
layout="vertical"
onFinish={handleFinish}
initialValues={{}}
>
<FormDatePicker />
</Form.Item>
<Form.Item
label={t("scoreboard.fields.bodyhrs")}
name="bodyhrs"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumberCalculator precision={1} />
</Form.Item>
<Form.Item
label={t("scoreboard.fields.painthrs")}
name="painthrs"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumberCalculator precision={1} />
</Form.Item>
<Form.Item
label={t("scoreboard.fields.date")}
name="date"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
<Form.Item
label={t("scoreboard.fields.bodyhrs")}
name="bodyhrs"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber precision={1} />
</Form.Item>
<Form.Item
label={t("scoreboard.fields.painthrs")}
name="painthrs"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber precision={1} />
</Form.Item>
<Button type="primary" htmlType="submit">
{t("general.actions.save")}
</Button>
</Form>
<Space wrap>
<Button type="primary" htmlType="submit">
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisibility(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
)}
</div>
</Card>
);
@@ -100,7 +150,7 @@ export default function ScoreboardAddButton({
setLoading(true);
const v = job.joblines.reduce(
(acc, val) => {
if (val.mod_lbr_ty === "LAB")
if (val.mod_lbr_ty !== "LAR")
acc = { ...acc, bodyhrs: acc.bodyhrs + val.mod_lb_hrs };
if (val.mod_lbr_ty === "LAR")
acc = { ...acc, painthrs: acc.painthrs + val.mod_lb_hrs };

View File

@@ -74,7 +74,12 @@ export default function JobsAdminDatesChange({ job }) {
<Form.Item label={t("jobs.fields.actual_in")} name="actual_in">
<DateTimePicker />
</Form.Item>
<Form.Item
label={t("jobs.fields.date_last_contacted")}
name="date_last_contacted"
>
<DateTimePicker />
</Form.Item>
<Form.Item
label={t("jobs.fields.scheduled_completion")}
name="scheduled_completion"

View File

@@ -8,7 +8,6 @@ import {
import { Col, notification, Row } from "antd";
import Axios from "axios";
import Dinero from "dinero.js";
import _ from "lodash";
import moment from "moment";
import queryString from "query-string";
import React, { useCallback, useEffect, useState } from "react";
@@ -30,6 +29,7 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import AlertComponent from "../alert/alert.component";
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
@@ -98,7 +98,9 @@ export function JobsAvailableContainer({
});
return;
}
//IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(estData.est_data, bodyshop);
console.log(estData);
const newTotals = (
await Axios.post("/job/totals", {
job: {
@@ -138,16 +140,17 @@ export function JobsAvailableContainer({
: {}),
};
if (selectedOwner) {
newJob.ownerid = selectedOwner;
delete newJob.owner;
}
if (newJob.vehicleid) {
delete newJob.vehicle;
}
insertNewJob({
variables: {
job: selectedOwner
? Object.assign(
{},
newJob,
{ owner: null },
{ ownerid: selectedOwner }
)
: newJob,
job: newJob,
},
})
.then((r) => {
@@ -187,7 +190,9 @@ export function JobsAvailableContainer({
setJobModalVisible(false);
setInsertLoading(true);
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
const estData = estDataRaw.data.available_jobs_by_pk;
if (!(estData && estData.est_data)) {
//We don't have the right data. Error!
setInsertLoading(false);
@@ -196,7 +201,9 @@ export function JobsAvailableContainer({
});
} else {
//create upsert job
let supp = _.cloneDeep(estData.est_data);
let supp = replaceEmpty({ ...estData.est_data });
//IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(supp, bodyshop);
delete supp.owner;
delete supp.vehicle;
@@ -208,7 +215,7 @@ export function JobsAvailableContainer({
let suppDelta = await GetSupplementDelta(
client,
selectedJob,
estData.est_data.joblines.data
supp.joblines.data
);
delete supp.joblines;
@@ -380,10 +387,117 @@ export default connect(
)(JobsAvailableContainer);
function replaceEmpty(someObj, replaceValue = null) {
const replacer = (key, value) => (value === "" ? replaceValue : value);
const replacer = (key, value) =>
value === "" ? replaceValue || null : value;
//^ because you seem to want to replace (strings) "null" or "undefined" too
console.log(someObj);
const temp = JSON.stringify(someObj, replacer);
console.log(`temp`, temp);
return JSON.parse(temp);
}
async function CheckTaxRates(estData, bodyshop) {
//LKQ Check
if (
!estData.parts_tax_rates?.PAL ||
estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAL) {
estData.parts_tax_rates.PAL = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAL",
};
}
estData.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAL.prt_tax_in = true;
}
}
//PAC Check
if (
!estData.parts_tax_rates?.PAC ||
estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAC) {
estData.parts_tax_rates.PAC = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAC",
};
}
estData.parts_tax_rates.PAC.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAC.prt_tax_in = true;
}
}
//PAM Check
if (
!estData.parts_tax_rates?.PAM ||
estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAM) {
estData.parts_tax_rates.PAM = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAM",
};
}
estData.parts_tax_rates.PAM.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAM.prt_tax_in = true;
}
}
if (
!estData.parts_tax_rates?.PAR ||
estData.parts_tax_rates?.PAR?.prt_tax_rt === null ||
estData.parts_tax_rates?.PAR?.prt_tax_rt === 0
) {
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
);
if (res) {
if (!estData.parts_tax_rates.PAR) {
estData.parts_tax_rates.PAR = {
prt_discp: 0,
prt_mktyp: true,
prt_mkupp: 0,
prt_type: "PAR",
};
}
estData.parts_tax_rates.PAR.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.parts_tax_rates.PAR.prt_tax_in = true;
}
}
//IO-1387 If a sublet line is NOT R&R, use the labor tax. If it is, use the sublet tax rate.
//Currently limited to SK shops only.
//if (bodyshop.region_config === "CA_SK") {
estData.joblines.data.forEach((jl, index) => {
if (
(jl.part_type === "PASL" || jl.part_type === "PAS") &&
jl.lbr_op !== "OP11"
) {
estData.joblines.data[index].tax_part = jl.lbr_tax;
}
});
//}
}

View File

@@ -13,6 +13,7 @@ import {
} from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { useHistory } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -26,56 +27,73 @@ export function JobsCloseExportButton({
disabled,
setSelectedJobs,
}) {
const history = useHistory();
const { t } = useTranslation();
const [updateJob] = useMutation(UPDATE_JOB);
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
const [loading, setLoading] = useState(false);
const handleQbxml = async () => {
//Check if it's a CDK setup.
if (bodyshop.cdk_dealerid) {
history.push(`/manage/dms?jobId=${jobId}`);
return;
}
logImEXEvent("jobs_close_export");
setLoading(true);
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/receivables",
{ jobIds: [jobId] },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("jobs.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
setLoading(false);
return;
}
//Check if it's a QBO Setup.
let PartnerResponse;
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
// "http://609feaeae986.ngrok.io/qb/",
QbXmlResponse.data,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("jobs.errors.exporting-partner"),
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/receivables`, {
withCredentials: true,
jobIds: [jobId],
});
setLoading(false);
return;
} else {
//Default is QBD
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/receivables",
{ jobIds: [jobId] },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("jobs.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
setLoading(false);
return;
}
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
// "http://609feaeae986.ngrok.io/qb/",
QbXmlResponse.data,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("jobs.errors.exporting-partner"),
});
setLoading(false);
return;
}
}
console.log("PartnerResponse", PartnerResponse);
@@ -159,12 +177,7 @@ export function JobsCloseExportButton({
};
return (
<Button
onClick={handleQbxml}
loading={loading}
disabled={disabled}
type="dashed"
>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")}
</Button>
);

View File

@@ -2,6 +2,7 @@ import { useMutation } from "@apollo/client";
import {
Button,
Form,
Input,
notification,
Popover,
Select,
@@ -112,24 +113,32 @@ export function JobsConvertButton({
</Form.Item>
)}
{bodyshop.enforce_referral && (
<Form.Item
name={"referral_source"}
label={t("jobs.fields.referralsource")}
rules={[
{
required: bodyshop.enforce_referral,
//message: t("general.validation.required"),
},
]}
>
<Select>
{bodyshop.md_referral_sources.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item>
<>
<Form.Item
name={"referral_source"}
label={t("jobs.fields.referralsource")}
rules={[
{
required: bodyshop.enforce_referral,
//message: t("general.validation.required"),
},
]}
>
<Select>
{bodyshop.md_referral_sources.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("jobs.fields.referral_source_extra")}
name="referral_source_extra"
>
<Input />
</Form.Item>
</>
)}
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}

View File

@@ -181,6 +181,12 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
))}
</Select>
</Form.Item>
<Form.Item
label={t("jobs.fields.referral_source_extra")}
name="referral_source_extra"
>
<Input />
</Form.Item>
</LayoutFormRow>
</Collapse.Panel>
<Collapse.Panel

View File

@@ -129,6 +129,19 @@ export default function JobsCreateOwnerInfoNewComponent() {
>
<FormItemPhone disabled={!state.owner.new} />
</Form.Item>
<Form.Item
label={t("owners.fields.ownr_ph2")}
name={["owner", "data", "ownr_ph2"]}
rules={[
({ getFieldValue }) =>
PhoneItemFormatterValidation(
getFieldValue,
"owner.data.ownr_ph2"
),
]}
>
<FormItemPhone disabled={!state.owner.new} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<Form.Item

View File

@@ -84,6 +84,18 @@ export default function JobsCreateOwnerInfoSearchComponent({
tableState.sortedInfo.columnKey === "ownr_ph1" &&
tableState.sortedInfo.order,
},
{
title: t("owners.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
render: (text, record) => (
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
),
sorter: (a, b) => alphaSort(a.ownr_ph2, b.ownr_ph2),
sortOrder:
tableState.sortedInfo.columnKey === "ownr_ph2" &&
tableState.sortedInfo.order,
},
];
const handleTableChange = (pagination, filters, sorter) => {

View File

@@ -69,6 +69,12 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
</Tooltip>
</FormRow>
<FormRow header={t("jobs.forms.repairdates")}>
<Form.Item
label={t("jobs.fields.date_last_contacted")}
name="date_last_contacted"
>
<DateTimePicker />
</Form.Item>
<Form.Item
label={t("jobs.fields.scheduled_completion")}
name="scheduled_completion"
@@ -93,7 +99,6 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
);
}}
</Form.Item>
<Form.Item
label={t("jobs.fields.scheduled_delivery")}
name="scheduled_delivery"

View File

@@ -114,6 +114,12 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
))}
</Select>
</Form.Item>
<Form.Item
label={t("jobs.fields.referral_source_extra")}
name="referral_source_extra"
>
<Input />
</Form.Item>
<Form.Item label={t("jobs.fields.alt_transport")} name="alt_transport">
<Select disabled={jobRO} allowClear>
{bodyshop.appt_alt_transport.map((s) => (

View File

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

View File

@@ -15,6 +15,7 @@ import JobAltTransportChange from "../job-at-change/job-at-change.component";
import JobEmployeeAssignments from "../job-employee-assignments/job-employee-assignments.container";
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import "./jobs-detail-header.styles.scss";
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -75,11 +76,15 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
{job.ins_co_nm}
</DataLabel>
<DataLabel label={t("jobs.fields.clm_no")}>{job.clm_no}</DataLabel>
<DataLabel label={t("jobs.fields.ponumber")} hideIfNull>
{job.po_number}
</DataLabel>
<DataLabel label={t("jobs.fields.repairtotal")}>
<CurrencyFormatter>{job.clm_total}</CurrencyFormatter>
<span style={{ margin: "0rem .5rem" }}>/</span>
<CurrencyFormatter>{job.owner_owing}</CurrencyFormatter>
</DataLabel>
<DataLabel label={t("jobs.fields.alt_transport")}>
{job.alt_transport}
<JobAltTransportChange job={job} />
@@ -136,6 +141,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<DataLabel key="2" label={t("jobs.fields.ownr_ph1")}>
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
</DataLabel>
<DataLabel key="22" label={t("jobs.fields.ownr_ph2")}>
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
</DataLabel>
<DataLabel key="3" label={t("owners.fields.address")}>
{`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${
job.ownr_city || ""
@@ -177,6 +185,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<DataLabel key="4" label={t("vehicles.fields.v_vin")}>
{`${job.v_vin || t("general.labels.na")}`}
</DataLabel>
<DataLabel label={t("jobs.labels.relatedros")}>
<JobsRelatedRos jobid={job.id} job={job} />
</DataLabel>
</div>
</Card>
</Col>

View File

@@ -13,6 +13,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CABCpvrtCalculator from "../ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
@@ -22,9 +23,10 @@ import JobsDetailRatesParts from "./jobs-detail-rates.parts.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
});
export function JobsDetailRates({ jobRO, form, job }) {
export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
const { t } = useTranslation();
return (
<div>
@@ -36,7 +38,7 @@ export function JobsDetailRates({ jobRO, form, job }) {
label={t("jobs.fields.depreciation_taxes")}
name="depreciation_taxes"
>
<CurrencyInput disabled={jobRO} />
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<Tooltip title={t("jobs.labels.ca_gst_all_if_null")}>
<Form.Item
@@ -45,6 +47,7 @@ export function JobsDetailRates({ jobRO, form, job }) {
>
<CurrencyInput
disabled={jobRO}
min={0}
max={
Math.round(
(job.job_totals &&
@@ -59,29 +62,29 @@ export function JobsDetailRates({ jobRO, form, job }) {
label={t("jobs.fields.other_amount_payable")}
name="other_amount_payable"
>
<CurrencyInput disabled={jobRO} />
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<Form.Item
label={t("jobs.fields.towing_payable")}
name="towing_payable"
>
<CurrencyInput disabled={jobRO} />
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<Form.Item
label={t("jobs.fields.storage_payable")}
name="storage_payable"
>
<CurrencyInput disabled={jobRO} />
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<Form.Item
label={t("jobs.fields.adjustment_bottom_line")}
name="adjustment_bottom_line"
>
<CurrencyInput disabled={jobRO} />
<CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} />
</Form.Item>
<Space align="end">
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
<CurrencyInput disabled={jobRO} />
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<CABCpvrtCalculator form={form} disabled={jobRO} />
</Space>
@@ -133,72 +136,72 @@ export function JobsDetailRates({ jobRO, form, job }) {
<Input disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_laa")} name="rate_laa">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lad")} name="rate_lad">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lae")} name="rate_lae">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lar")} name="rate_lar">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_las")} name="rate_las">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_laf")} name="rate_laf">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lam")} name="rate_lam">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lag")} name="rate_lag">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la1")} name="rate_la1">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la2")} name="rate_la2">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la3")} name="rate_la3">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_la4")} name="rate_la4">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_lau")} name="rate_lau">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mapa")} name="rate_mapa">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mash")} name="rate_mash">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_mahw")} name="rate_mahw">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_ma2s")} name="rate_ma2s">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.rate_ma3s")} name="rate_ma3s">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
{
// <Form.Item label={t("jobs.fields.rate_mabl")} name="rate_mabl">
// <CurrencyInput disabled={jobRO} />
// <CurrencyInput min={0}disabled={jobRO} />
// </Form.Item>
// <Form.Item label={t("jobs.fields.rate_macs")} name="rate_macs">
// <CurrencyInput disabled={jobRO} />
// <CurrencyInput min={0}disabled={jobRO} />
// </Form.Item>
}
<Form.Item label={t("jobs.fields.rate_matd")} name="rate_matd">
<CurrencyInput disabled={jobRO} />
<CurrencyInput min={0} disabled={jobRO} />
</Form.Item>
</FormRow>
<JobsDetailRatesParts form={form} />

View File

@@ -29,20 +29,6 @@ function JobsDocumentsComponent({
setIndex(index);
};
// useEffect(() => {
// console.log("Added event listening for reteching.");
// window.addEventListener("storage", (ev) => {
// if (ev.key === "refetch" && ev.newValue === true) {
// refetch && refetch();
// localStorage.setItem("refetch", false);
// }
// });
// return () => {
// window.removeEventListener("storage");
// };
// }, [refetch]);
useEffect(() => {
let documents = data.reduce(
(acc, value) => {

View File

@@ -34,53 +34,61 @@ export function JobsExportAllButton({
const [loading, setLoading] = useState(false);
const handleQbxml = async () => {
logImEXEvent("jobs_export_all");
setLoading(true);
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/receivables",
{ jobIds: jobIds },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("jobs.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
setLoading(false);
return;
}
let PartnerResponse;
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
// "http://609feaeae986.ngrok.io/qb/",
QbXmlResponse.data,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("jobs.errors.exporting-partner"),
setLoading(true);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/receivables`, {
withCredentials: true,
jobIds: jobIds,
});
setLoading(false);
return;
}
} else {
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/receivables",
{ jobIds: jobIds },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("jobs.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
setLoading(false);
return;
}
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
// "http://609feaeae986.ngrok.io/qb/",
QbXmlResponse.data,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("jobs.errors.exporting-partner"),
});
setLoading(false);
return;
}
}
console.log("PartnerResponse", PartnerResponse);
const groupedData = _.groupBy(PartnerResponse.data, "id");
const groupedData = _.groupBy(
PartnerResponse.data,
bodyshop.accountingconfig.qbo ? "jobid" : "id"
);
await Promise.all(
Object.keys(groupedData).map(async (key) => {
@@ -157,6 +165,7 @@ export function JobsExportAllButton({
if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
};

View File

@@ -69,6 +69,20 @@ export default function JobsFindModalComponent({
);
},
},
{
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
width: "12%",
ellipsis: true,
render: (text, record) => {
return record.ownr_ph2 ? (
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
) : (
t("general.labels.unknown")
);
},
},
{
title: t("jobs.fields.status"),
dataIndex: "status",

View File

@@ -73,6 +73,16 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
<StartChatButton phone={record.ownr_ph1} jobid={record.id} />
),
},
{
title: t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
width: "12%",
ellipsis: true,
render: (text, record) => (
<StartChatButton phone={record.ownr_ph2} jobid={record.id} />
),
},
{
title: t("jobs.fields.status"),
dataIndex: "status",
@@ -130,13 +140,10 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder: sortcolumn === "clm_no" && sortorder,
render: (text, record) => {
return record.clm_no ? (
<span>{record.clm_no}</span>
) : (
t("general.labels.unknown")
);
},
render: (text, record) =>
`${record.clm_no || ""}${
record.po_number ? ` (PO: ${record.po_number})` : ""
}`,
},
{
title: t("jobs.fields.ins_co_nm"),

View File

@@ -145,6 +145,16 @@ export function JobsList({ bodyshop }) {
<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"),
@@ -208,6 +218,10 @@ export function JobsList({ bodyshop }) {
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"),

View File

@@ -10,8 +10,21 @@ import {
QUERY_NOTES_BY_JOB_PK,
} from "../../graphql/notes.queries";
import JobNotesComponent from "./jobs.notes.component";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
export default function JobNotesContainer({ jobId }) {
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(mapStateToProps, mapDispatchToProps)(JobNotesContainer);
export function JobNotesContainer({ jobId, insertAuditTrail }) {
const { loading, error, data, refetch } = useQuery(QUERY_NOTES_BY_JOB_PK, {
variables: { id: jobId },
fetchPolicy: "network-only",
@@ -32,6 +45,10 @@ export default function JobNotesContainer({ jobId }) {
notification["success"]({
message: t("notes.successes.deleted"),
});
insertAuditTrail({
jobid: jobId,
operation: AuditTrailMapping.jobnotedeleted(),
});
});
setDeleteLoading(false);
};

View File

@@ -0,0 +1,20 @@
import { Space, Tag } from "antd";
import React from "react";
import { Link } from "react-router-dom";
export default function JobsRelatedRos({ jobid, job }) {
if (!(job && job.vehicle && job.vehicle.jobs)) return null;
return (
<Space wrap>
{job.vehicle.jobs
.filter((j) => j.id !== job.id)
.map((j) => (
<Tag key={j.id}>
<Link to={`/manage/jobs/${j?.id}`}>{`${j.ro_number || "N/A"}${
j.clm_no ? ` | ${j.clm_no}` : ""
}${j.status ? ` | ${j.status}` : ""}`}</Link>
</Tag>
))}
</Space>
);
}

View File

@@ -57,6 +57,7 @@ export function LaborAllocationsTable({
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
sortOrder:
state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order,
render: (text, record) => `${record.cost_center} (${record.mod_lbr_ty})`,
},
{
title: t("jobs.labels.hrs_total"),

View File

@@ -16,6 +16,7 @@ export const CalculateAllocationsTotals = (
const r = {
opcode: value,
cost_center: responsibilitycenters.defaults.costs[value],
mod_lbr_ty: value,
total: joblines.reduce((acc2, val2) => {
return val2.mod_lbr_ty === value ? acc2 + val2.mod_lb_hrs : acc2;
}, 0),

View File

@@ -10,6 +10,8 @@ import { selectNoteUpsert } from "../../redux/modals/modals.selectors";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import NoteUpsertModalComponent from "./note-upsert-modal.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -17,12 +19,15 @@ const mapStateToProps = createStructuredSelector({
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("noteUpsert")),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function NoteUpsertModalContainer({
currentUser,
noteUpsertModal,
toggleModalVisible,
insertAuditTrail,
}) {
const { t } = useTranslation();
const [insertNote] = useMutation(INSERT_NEW_NOTE);
@@ -56,6 +61,10 @@ export function NoteUpsertModalContainer({
notification["success"]({
message: t("notes.successes.updated"),
});
insertAuditTrail({
jobid: context.jobId,
operation: AuditTrailMapping.jobnoteupdated(),
});
});
if (refetch) refetch();
toggleModalVisible();
@@ -75,6 +84,10 @@ export function NoteUpsertModalContainer({
notification["success"]({
message: t("notes.successes.create"),
});
insertAuditTrail({
jobid: context.jobId,
operation: AuditTrailMapping.jobnoteadded(),
});
});
}
};

View File

@@ -81,6 +81,16 @@ export default function OwnerDetailFormComponent({ form, loading }) {
>
<FormItemPhone />
</Form.Item>
<Form.Item
label={t("owners.fields.ownr_ph2")}
name="ownr_ph2"
rules={[
({ getFieldValue }) =>
PhoneItemFormatterValidation(getFieldValue, "ownr_ph2"),
]}
>
<FormItemPhone />
</Form.Item>
<Form.Item
label={t("owners.fields.preferred_contact")}
name="preferred_contact"

View File

@@ -51,6 +51,14 @@ export default function OwnerFindModalComponent({
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
),
},
{
title: t("owners.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
render: (text, record) => (
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
),
},
];
const handleOnRowClick = (record) => {

View File

@@ -16,6 +16,9 @@ export default function OwnerTagPopoverComponent({ job }) {
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}>
<PhoneFormatter>{job.ownr_ph1 || ""}</PhoneFormatter>
</Descriptions.Item>
<Descriptions.Item key="22" label={t("jobs.fields.ownr_ph2")}>
<PhoneFormatter>{job.ownr_ph2 || ""}</PhoneFormatter>
</Descriptions.Item>
<Descriptions.Item key="3" label={t("owners.fields.address")}>
{`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${
job.ownr_city || ""
@@ -36,6 +39,12 @@ export default function OwnerTagPopoverComponent({ job }) {
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}>
<PhoneFormatter>{job.owner.ownr_ph1 || ""}</PhoneFormatter>
</Descriptions.Item>
<Descriptions.Item key="22" label={t("jobs.fields.ownr_ph2")}>
<PhoneFormatter>{job.owner.ownr_ph2 || ""}</PhoneFormatter>
</Descriptions.Item>
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph2")}>
<PhoneFormatter>{job.owner.ownr_ph2 || ""}</PhoneFormatter>
</Descriptions.Item>
<Descriptions.Item key="3" label={t("owners.fields.address")}>
{`${job.owner.ownr_addr1 || ""} ${job.owner.ownr_addr2 || ""} ${
job.owner.ownr_city || ""

View File

@@ -47,6 +47,14 @@ export default function OwnersListComponent({
return <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>;
},
},
{
title: t("owners.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
render: (text, record) => {
return <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>;
},
},
{
title: t("owners.fields.ownr_ea"),
dataIndex: "ownr_ea",

View File

@@ -27,6 +27,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import DataLabel from "../data-label/data-label.component";
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
@@ -379,6 +380,9 @@ export function PartsOrderListTableComponent({
rowKey="id"
dataSource={record.parts_order_lines}
/>
<DataLabel label={t("parts_orders.fields.comments")}>
<div style={{ whiteSpace: "pre" }}>{record.comments}</div>
</DataLabel>
</div>
);
};

View File

@@ -73,7 +73,7 @@ export default function PartsOrderModalComponent({
<Form.Item required={false} key={field.key}>
<LayoutFormRow grow noDivider>
<Form.Item
span={8}
//span={8}
label={t("parts_orders.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
@@ -106,6 +106,12 @@ export default function PartsOrderModalComponent({
label={t("parts_orders.fields.quantity")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber />
</Form.Item>
@@ -126,19 +132,21 @@ export default function PartsOrderModalComponent({
</Form.Item>
)}
<div>
<DeleteFilled
style={{ margin: "1rem" }}
onClick={() => {
remove(field.name);
}}
/>
<Space wrap align="center">
<div>
<DeleteFilled
style={{ margin: "1rem" }}
onClick={() => {
remove(field.name);
}}
/>
</div>
<FormListMoveArrows
move={move}
index={index}
total={fields.length}
/>
</div>
</Space>
</LayoutFormRow>
</Form.Item>
))}
@@ -146,6 +154,9 @@ export default function PartsOrderModalComponent({
);
}}
</Form.List>
<Form.Item name="comments" label={t("parts_orders.fields.comments")}>
<Input.TextArea rows={3} />
</Form.Item>
<Radio.Group
defaultValue={sendType}
onChange={(e) => setSendType(e.target.value)}

View File

@@ -176,7 +176,7 @@ export function PartsOrderModalContainer({
if (refetch) refetch();
toggleModalVisible();
const Templates = TemplateList("partsorder");
const Templates = TemplateList("partsorder", context);
if (sendType === "e") {
const matchingVendor = data.vendors.filter(
@@ -260,7 +260,7 @@ export function PartsOrderModalContainer({
onCancel={() => toggleModalVisible()}
onOk={() => form.submit()}
destroyOnClose
width="50%"
width="75%"
forceRender
>
{error ? <AlertComponent message={error.message} type="error" /> : null}

View File

@@ -35,52 +35,61 @@ export function PayableExportAll({
const handleQbxml = async () => {
logImEXEvent("accounting_payables_export_all");
let PartnerResponse;
setLoading(true);
if (!!loadingCallback) loadingCallback(true);
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/payables",
{ bills: billids },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("bills.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/receivables`, {
withCredentials: true,
bills: billids,
});
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
} else {
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/payables",
{ bills: billids },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("bills.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
let PartnerResponse;
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
QbXmlResponse.data
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("bills.errors.exporting-partner"),
});
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
return;
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
QbXmlResponse.data
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("bills.errors.exporting-partner"),
});
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
}
console.log("handleQbxml -> PartnerResponse", PartnerResponse);
const groupedData = _.groupBy(PartnerResponse.data, "id");
const groupedData = _.groupBy(
PartnerResponse.data,
bodyshop.accountingconfig.qbo ? "billid" : "id"
);
const proms = [];
Object.keys(groupedData).forEach((key) => {
proms.push(

View File

@@ -38,44 +38,53 @@ export function PayableExportButton({
setLoading(true);
if (!!loadingCallback) loadingCallback(true);
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/payables",
{ bills: [billId] },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("bills.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
//Check if it's a QBO Setup.
let PartnerResponse;
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
QbXmlResponse.data
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("bills.errors.exporting-partner"),
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/payables`, {
withCredentials: true,
bills: [billId],
});
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
return;
} else {
//Default is QBD
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/payables",
{ bills: [billId] },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("bills.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
QbXmlResponse.data
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("bills.errors.exporting-partner"),
});
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
}
console.log("handleQbxml -> PartnerResponse", PartnerResponse);
@@ -123,7 +132,14 @@ export function PayableExportButton({
});
const billUpdateResponse = await updateBill({
variables: {
billIdList: successfulTransactions.map((st) => st.id),
billIdList: successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
? "billid"
: "id"
]
),
bill: {
exported: true,
exported_at: new Date(),

View File

@@ -6,7 +6,6 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { auth } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_PAYMENTS } from "../../graphql/payments.queries";
import {
@@ -37,15 +36,9 @@ export function PaymentsExportAllButton({
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/payments",
{ payments: paymentIds },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
QbXmlResponse = await axios.post("/accounting/qbxml/payments", {
payments: paymentIds,
});
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({

View File

@@ -1,28 +1,35 @@
import { Card, Col, Input, Row, Space, Typography } from "antd";
import _ from "lodash";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectPrintCenter } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { TemplateList } from "../../utils/TemplateConstants";
import Jobd3RdPartyModal from "../job-3rd-party-modal/job-3rd-party-modal.component";
import PrintCenterItem from "../print-center-item/print-center-item.component";
import PrintCenterSpeedPrint from "../print-center-speed-print/print-center-speed-print.component";
import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({
printCenterModal: selectPrintCenter,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({});
export function PrintCenterJobsComponent({ printCenterModal }) {
export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
const [search, setSearch] = useState("");
const { id: jobId } = printCenterModal.context;
const tempList = TemplateList("job", {});
const { t } = useTranslation();
const JobsReportsList = Object.keys(tempList).map((key) => {
return tempList[key];
});
const JobsReportsList = Object.keys(tempList)
.map((key) => {
return tempList[key];
})
.filter(
(temp) =>
!temp.regions || (temp.regions && temp.regions[bodyshop.region_config])
);
const filteredJobsReportsList =
search !== ""

View File

@@ -38,6 +38,7 @@ export function ProductionBoardFilters({
}}
/>
<EmployeeSearchSelectComponent
style={{ minWidth: "20rem" }}
options={bodyshop.employees.filter((e) => e.active)}
value={filter.employeeId}
placeholder={t("production.labels.employeesearch")}

View File

@@ -15,6 +15,7 @@ import ProductionListColumnPaintPriority from "./production-list-columns.paintpr
import ProductionListColumnNote from "./production-list-columns.productionnote.component";
import ProductionListColumnStatus from "./production-list-columns.status.component";
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
import ProductionListLastContacted from "./production-list-columns.lastcontacted.component";
const r = ({ technician, state }) => {
return [
@@ -97,6 +98,17 @@ const r = ({ technician, state }) => {
<ProductionListDate record={record} field="scheduled_completion" />
),
},
{
title: i18n.t("jobs.fields.date_last_contacted"),
dataIndex: "date_last_contacted",
key: "date_last_contacted",
ellipsis: true,
sorter: (a, b) => dateSort(a.date_last_contacted, b.date_last_contacted),
sortOrder:
state.sortedInfo.columnKey === "date_last_contacted" &&
state.sortedInfo.order,
render: (text, record) => <ProductionListLastContacted record={record} />,
},
{
title: i18n.t("jobs.fields.scheduled_delivery"),
dataIndex: "scheduled_delivery",
@@ -161,6 +173,15 @@ const r = ({ technician, state }) => {
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
),
},
{
title: i18n.t("jobs.fields.ownr_ph2"),
dataIndex: "ownr_ph2",
key: "ownr_ph2",
ellipsis: true,
render: (text, record) => (
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
),
},
{
title: i18n.t("jobs.fields.specialcoveragepolicy"),
dataIndex: "special_coverage_policy",

View File

@@ -1,17 +1,20 @@
import { useMutation } from "@apollo/client";
import { DatePicker, Dropdown } from "antd";
import { DatePicker, Dropdown, TimePicker, Button, Card } from "antd";
import moment from "moment";
import React from "react";
import React, { useState } from "react";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { DateFormatter } from "../../utils/DateFormatter";
import { useTranslation } from "react-i18next";
const OneCalendarDay = 60 * 60 * 24 * 1000;
const Now = new Date();
export default function ProductionListDate({ record, field }) {
export default function ProductionListDate({ record, field, time }) {
const [updateAlert] = useMutation(UPDATE_JOB);
const [visible, setVisible] = useState(false);
const { t } = useTranslation();
const handleChange = (date) => {
logImEXEvent("product_toggle_date", { field });
@@ -25,27 +28,47 @@ export default function ProductionListDate({ record, field }) {
},
}).then(() => {
if (record.refetch) record.refetch();
if (!time) {
setVisible(false);
}
});
};
return (
<div>
<Dropdown
trigger={["click"]}
//trigger={["click"]}
visible={visible}
style={{
height: "19px",
}}
overlay={
<div onClick={(e) => e.stopPropagation()}>
<Card
style={{ padding: "1rem" }}
onClick={(e) => e.stopPropagation()}
>
<DatePicker
onClick={(e) => e.stopPropagation()}
value={(record[field] && moment(record[field])) || null}
onChange={handleChange}
format="MM/DD/YYYY"
/>
</div>
{time && (
<TimePicker
onClick={(e) => e.stopPropagation()}
value={(record[field] && moment(record[field])) || null}
onChange={handleChange}
format="hh:mm a"
/>
)}
<Button onClick={() => setVisible(false)}>
{t("general.actions.close")}
</Button>
</Card>
}
>
<div
onClick={() => setVisible(true)}
style={{
height: "19px",
}}

View File

@@ -0,0 +1,132 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Dropdown, Form, Input, notification, Space } from "antd";
import moment from "moment";
import React, { useState, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { INSERT_NEW_NOTE } from "../../graphql/notes.queries";
import { selectCurrentUser } from "../../redux/user/user.selectors";
import { DateFormatter } from "../../utils/DateFormatter";
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionLastContacted);
export function ProductionLastContacted({ currentUser, record }) {
const [updateAlert] = useMutation(UPDATE_JOB);
const [insertNote] = useMutation(INSERT_NEW_NOTE);
const [visible, setVisible] = useState(false);
const { t } = useTranslation();
const [form] = Form.useForm();
const handleFinish = async ({ date_last_contacted, note }) => {
logImEXEvent("production_last_contacted");
//e.stopPropagation();
const res = await updateAlert({
variables: {
jobId: record.id,
job: {
date_last_contacted,
},
},
});
if (res.errors) {
notification.open({
type: "error",
message: t("jobs.errors.saving", {
error: JSON.stringify(res.errors),
}),
});
}
if (note && note.trim() !== "") {
//Insert a note.
const res2 = await insertNote({
variables: {
noteInput: {
jobid: record.id,
text: note,
created_by: currentUser.email,
},
},
});
if (res2.errors) {
notification.open({
type: "error",
message: t("notes.errors.inserting", {
error: JSON.stringify(res.errors),
}),
});
}
}
if (record.refetch) record.refetch();
setVisible(false);
};
useEffect(() => {
if (visible) {
form.setFieldsValue({
note: null,
date_last_contacted:
record.date_last_contacted && moment(record.date_last_contacted),
});
}
}, [visible, form, record.date_last_contacted]);
return (
<div>
<Dropdown
//trigger={["click"]}
visible={visible}
style={{
height: "19px",
}}
overlay={
<Card
style={{ padding: "1rem" }}
onClick={(e) => e.stopPropagation()}
>
<Form form={form} onFinish={handleFinish} layout="vertical">
<Form.Item name="date_last_contacted">
<FormDateTimePickerComponent />
</Form.Item>
<Form.Item label={t("notes.labels.notetoadd")} name="note">
<Input.TextArea rows={4} />
</Form.Item>
<Space>
<Button type="primary" htmlType="submit">
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisible(false)}>
{t("general.actions.close")}
</Button>
</Space>
</Form>
</Card>
}
>
<div
onClick={() => setVisible(true)}
style={{
height: "19px",
}}
>
<DateFormatter bordered={false}>
{record.date_last_contacted}
</DateFormatter>
</div>
</Dropdown>
</div>
);
}

View File

@@ -77,6 +77,10 @@ export default function ProductionListDetail({ jobs }) {
phone={data.jobs_by_pk.ownr_ph1}
jobid={data.jobs_by_pk.id}
/>
<StartChatButton
phone={data.jobs_by_pk.ownr_ph2}
jobid={data.jobs_by_pk.id}
/>
</Descriptions.Item>
<Descriptions.Item label={t("jobs.fields.vehicle")}>
{`${theJob.v_model_yr || ""} ${theJob.v_color || ""} ${

View File

@@ -66,12 +66,7 @@ export function ProductionListSaveConfigButton({
};
const popMenu = (
<div>
<Form
layout="vertical"
form={form}
onFinish={handleSaveConfig}
initialValues={{ driveable: true, towin: false }}
>
<Form layout="vertical" form={form} onFinish={handleSaveConfig}>
<Form.Item
label={t("production.labels.viewname")}
name="name"

View File

@@ -55,6 +55,19 @@ export function ProductionListTable({
if (assoc) {
await updateDefaultProdView({
variables: { assocId: assoc.id, view: value },
update(cache) {
cache.modify({
id: cache.identify(bodyshop),
fields: {
associations(existingAssociations, { readField }) {
return existingAssociations.map((a) => {
if (a.useremail !== currentUser.email) return a;
return { ...a, default_prod_list_view: value };
});
},
},
});
},
});
}
};
@@ -85,13 +98,18 @@ export function ProductionListTable({
setState(bodyshop.production_config[0].columns.tableState);
};
const assoc = bodyshop.associations.find(
(a) => a.useremail === currentUser.email
);
const defaultView = assoc && assoc.default_prod_list_view;
return (
<div style={{ width: "10rem" }}>
<Select
onSelect={handleSelect}
placeholder={t("production.labels.selectview")}
optionLabelProp="label"
defaultValue={defaultView}
>
{bodyshop.production_config.map((config) => (
<Select.Option key={config.name} label={config.name}>

View File

@@ -32,12 +32,11 @@ export function ProductionListTable({
}) {
const [searchText, setSearchText] = useState("");
const defaultView = useMemo(() => {
const assoc = bodyshop.associations.find(
(a) => a.useremail === currentUser.email
);
return assoc && assoc.default_prod_list_view;
}, [bodyshop.associations, currentUser.email]);
const assoc = bodyshop.associations.find(
(a) => a.useremail === currentUser.email
);
const defaultView = assoc && assoc.default_prod_list_view;
const [state, setState] = useState(
(bodyshop.production_config &&

View File

@@ -0,0 +1,57 @@
import { Space, Tag } from "antd";
import Axios from "axios";
import queryString from "query-string";
import React, { useEffect } from "react";
import { useCookies } from "react-cookie";
import { useHistory, useLocation } from "react-router-dom";
import QboSignIn from "../../assets/qbo/C2QB_green_btn_med_default.svg";
export default function QboAuthorizeComponent() {
const location = useLocation();
const history = useHistory();
const [cookies, setCookie] = useCookies(["access_token", "refresh_token"]);
const handleQbSignIn = async () => {
const result = await Axios.post("/qbo/authorize");
window.location.href = result.data;
};
const qs = queryString.parse(location.search);
const { error } = qs;
useEffect(() => {
const { code, state, realmId } = qs;
const hasBeenCalledBack = code && realmId && state;
if (hasBeenCalledBack) {
setCookie("qbo_code", code, { path: "/" });
setCookie("qbo_state", state, { path: "/" });
let expires = new Date();
expires.setTime(expires.getTime() + 8726400 * 1000);
setCookie("qbo_realmId", realmId, {
path: "/",
expires,
});
history.push({ pathname: `/manage/accounting/receivables` });
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [qs, location, setCookie]);
return (
<Space>
<img
onClick={handleQbSignIn}
alt="Sign In to QuickBooks Online"
src={QboSignIn}
style={{ cursor: "pointer" }}
/>
{!cookies.qbo_realmId && (
<Tag color="red">No QuickBooks company has been connected.</Tag>
)}
{error && JSON.parse(decodeURIComponent(error)).error_description}
</Space>
);
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -52,8 +52,9 @@ const ret = {
"shiftclock:view": 2,
"shop:config": 4,
"shop:rbac": 5,
"shop:vendors": 2,
"shop:rbac": 1,
"shop:dashboard": 3,
"shop:templates": 4,

View File

@@ -1,5 +1,16 @@
import { useLazyQuery } from "@apollo/client";
import { Button, DatePicker, Form, Radio, Space } from "antd";
import {
Button,
Card,
Col,
DatePicker,
Form,
Input,
Radio,
Row,
Typography,
} from "antd";
import _ from "lodash";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -14,7 +25,6 @@ import { TemplateList } from "../../utils/TemplateConstants";
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import "./report-center-modal.styles.scss";
const mapStateToProps = createStructuredSelector({
reportCenterModal: selectReportCenter,
});
@@ -28,9 +38,14 @@ export default connect(
export function ReportCenterModalComponent({ reportCenterModal }) {
const [form] = Form.useForm();
const [search, setSearch] = useState("");
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const Templates = TemplateList("report_center");
const ReportsList = Object.keys(Templates).map((key) => {
return Templates[key];
});
const { visible } = reportCenterModal;
const [callVendorQuery, { data: vendorData, called: vendorCalled }] =
@@ -67,6 +82,9 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
...(end
? { end: moment(end).endOf("day").format("YYYY-MM-DD") }
: {}),
...(start ? { starttz: moment(start).startOf("day") } : {}),
...(end ? { endtz: moment(end).endOf("day") } : {}),
...(id ? { id: id } : {}),
},
},
@@ -80,6 +98,17 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
setLoading(false);
};
const FilteredReportsList =
search !== ""
? ReportsList.filter((r) =>
r.title.toLowerCase().includes(search.toLowerCase())
)
: ReportsList;
//Group it, create cards, and then filter out.
const grouped = _.groupBy(FilteredReportsList, "group");
return (
<div>
<Form
@@ -88,6 +117,10 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
layout="vertical"
form={form}
>
<Input.Search
onChange={(e) => setSearch(e.target.value)}
value={search}
/>
<Form.Item
name="key"
label={t("reportcenter.labels.key")}
@@ -100,23 +133,42 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
]}
>
<Radio.Group>
<Space
direction="vertical"
wrap
size="small"
style={{
maxHeight: "50vh",
}}
>
{Object.keys(Templates).map((key) => (
{/* {Object.keys(Templates).map((key) => (
<Radio key={key} value={key}>
{Templates[key].title}
</Radio>
))} */}
<Row gutter={[16, 16]}>
{Object.keys(grouped).map((key) => (
<Col md={8} sm={12} key={key}>
<Card.Grid
style={{
width: "100%",
height: "100%",
maxHeight: "33vh",
overflowY: "scroll",
}}
>
<Typography.Title level={4}>
{t(`reportcenter.labels.groups.${key}`)}
</Typography.Title>
<ul style={{ columns: "2 auto" }}>
{grouped[key].map((item) => (
<li key={item.key}>
<Radio key={item.key} value={item.key}>
{item.title}
</Radio>
</li>
))}
</ul>
</Card.Grid>
</Col>
))}
</Space>
</Row>
</Radio.Group>
</Form.Item>
<Form.Item dependencies={["key"]}>
<Form.Item style={{ margin: 0, padding: 0 }} dependencies={["key"]}>
{() => {
const key = form.getFieldValue("key");
if (!key) return null;
@@ -137,14 +189,14 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
shouldUpdate={(prev, cur) =>
Templates[prev.key]?.idtype !== Templates[cur.key]?.idtype
}
style={{ display: "none" }}
style={{ display: "none", margin: 0, padding: 0 }}
>
{() => {
form.setFieldsValue({ id: null });
return null;
}}
</Form.Item>
<Form.Item dependencies={["key"]}>
<Form.Item style={{ margin: 0, padding: 0 }} dependencies={["key"]}>
{() => {
const key = form.getFieldValue("key");
if (!key) return null;
@@ -204,7 +256,7 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
]}
>
<DatePicker.RangePicker
format="YYYY-MM-DD"
format="MM/DD/YYYY"
ranges={DatePIckerRanges}
/>
</Form.Item>

View File

@@ -124,7 +124,7 @@ export function ScheduleJobModalComponent({
handleDateBlur();
}}
>
<DateFormatter>{d}</DateFormatter>
<DateFormatter includeDay>{d}</DateFormatter>
</Button>
))}
</Space>

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