Compare commits

...

194 Commits

Author SHA1 Message Date
Patrick Fic
375b8ba050 Updated task scheduler. 2022-02-18 09:42:42 -08:00
Patrick Fic
2192cb1e7c IO-1744 Added missing totals to job costing modal. 2022-02-17 17:14:37 -08:00
Patrick Fic
65b505035a IO-1706 Resolve print center issue. 2022-02-17 17:08:25 -08:00
Patrick Fic
191f3f96a2 IO-1744 Resolve missing sublet calcuations. 2022-02-17 17:06:02 -08:00
Patrick Fic
14d873f795 IO-1742 Remove flat rate slider on time ticket entry. 2022-02-17 16:23:51 -08:00
Patrick Fic
55ad75df8a IO-1744 Break out sublet on job costing 2022-02-17 16:17:45 -08:00
Patrick Fic
5f112e797d CI Fix. 2022-02-16 17:11:55 -08:00
Patrick Fic
b842cee076 IO-1738 PVRT Update 2022-02-16 16:45:49 -08:00
Patrick Fic
2c1c11828d IO-1740 Scoreboard to 1 decimal. 2022-02-16 15:27:13 -08:00
Patrick Fic
e7c380d780 Standardize production minute picker. 2022-02-16 15:20:56 -08:00
Patrick Fic
0958ea5ba6 IO-1706 Payroll Table 2022-02-16 14:22:39 -08:00
Patrick Fic
8031f2b2ed Merge branch 'hotfix/2022-02-14' into release/2022-02-18 2022-02-15 16:03:39 -08:00
Patrick Fic
996863fcb7 Potential resolution to date time issues post 4pm. 2022-02-15 16:03:20 -08:00
Patrick Fic
066f395a40 IO-1738 Remove PVRT from subtotal. 2022-02-15 13:41:35 -08:00
Patrick Fic
d8ec6dd997 Merge branch 'hotfix/2022-02-14' into release/2022-02-18 2022-02-15 12:16:34 -08:00
Patrick Fic
cc636bdfe2 Job costing additional fix. 2022-02-15 12:16:08 -08:00
Patrick Fic
a3e8f56728 Resolve Date Picker Issues. 2022-02-15 09:30:17 -08:00
Patrick Fic
2e7d8df781 Revert "Potential resolution to date time issues post 4pm."
This reverts commit e79b9f9084.
2022-02-15 09:13:10 -08:00
Patrick Fic
680dd98f6d Merge branch 'hotfix/2022-02-14' into release/2022-02-18 2022-02-14 19:33:13 -08:00
Patrick Fic
e79b9f9084 Potential resolution to date time issues post 4pm. 2022-02-14 19:31:26 -08:00
Patrick Fic
22e6d596e6 Merge branch 'hotfix/2022-02-14' into release/2022-02-18 2022-02-14 13:14:12 -08:00
Patrick Fic
62be2b0a0a Resolve bill update error. 2022-02-14 13:11:14 -08:00
Patrick Fic
dc77930950 Resolve bill update error. 2022-02-14 13:02:54 -08:00
Patrick Fic
520b61706f Merged in release/2022-02-18 (pull request #386)
Release/2022 02 18
2022-02-14 19:03:49 +00:00
Patrick Fic
09a87dd2a7 IO-1724 Update cancel appointment label. 2022-02-14 10:43:11 -08:00
Patrick Fic
f884d2e23f IO-1721 Update time ticket time fields to be in 5 minute increments. 2022-02-14 10:41:09 -08:00
Patrick Fic
b84935efdc IO-1707 Add employee filtering to time ticket list. 2022-02-14 10:38:58 -08:00
Patrick Fic
b3aeee4f45 IO-1667 Add print center to production detail drawer. 2022-02-14 10:33:21 -08:00
Patrick Fic
06f266a292 IO-1700 Add name to cc list. 2022-02-14 10:27:41 -08:00
Patrick Fic
a2d54d5dd5 Merged in release/2022-02-11 (pull request #385)
Release/2022 02 11
2022-02-11 23:06:48 +00:00
Patrick Fic
b34694f3c4 Merged in release/2022-02-11 (pull request #384)
Add PBS Error message.
2022-02-11 23:03:19 +00:00
Patrick Fic
43b8842027 Add PBS Error message. 2022-02-11 14:45:28 -08:00
Patrick Fic
0dbb3a446a Merged in release/2022-02-11 (pull request #383)
IO-1708 Revert timezone change in app
2022-02-11 16:28:12 +00:00
Patrick Fic
75b2398421 IO-1708 Revert timezone change in app 2022-02-11 08:24:34 -08:00
Patrick Fic
2409042450 Merged in release/2022-02-11 (pull request #382)
Release/2022 02 11
2022-02-11 01:52:23 +00:00
Patrick Fic
85ccb36b2e IO-1708 Revert time zone app side changes. 2022-02-10 17:51:55 -08:00
Patrick Fic
a616921e2b IO-1708 Remove setting application side time zone. 2022-02-10 17:50:46 -08:00
Patrick Fic
f509ea07c0 Merged in release/2022-02-11 (pull request #381)
IO-1723 Add Additional GP to summary for multi costing.
2022-02-11 01:01:59 +00:00
Patrick Fic
efb62b59f4 IO-1723 Add Additional GP to summary for multi costing. 2022-02-10 17:00:59 -08:00
Patrick Fic
e263c32d83 Merged in release/2022-02-11 (pull request #380)
Release/2022 02 11
2022-02-11 00:31:18 +00:00
Patrick Fic
30afe97fba IO-1685 Task Scheduler. 2022-02-10 16:30:49 -08:00
Patrick Fic
e383b0800c IO-1723 Add GPs for additional items on costing. 2022-02-10 16:30:04 -08:00
Patrick Fic
e0507f2d17 IO-1723 Add GPs for additional items on costing. 2022-02-10 16:29:45 -08:00
Patrick Fic
cdd3841d49 IO-1708 Updated timezone for date fields. 2022-02-10 16:21:10 -08:00
Patrick Fic
4a023faf67 Merged in release/2022-02-11 (pull request #379)
IO-1718 Only allow type change based on split for part order.
2022-02-10 23:15:13 +00:00
Patrick Fic
e9f4b48839 IO-1718 Only allow type change based on split for part order. 2022-02-10 15:14:25 -08:00
Patrick Fic
90f0232ff0 Merged in release/2022-02-11 (pull request #378)
Release/2022 02 11
2022-02-10 22:49:49 +00:00
Patrick Fic
b94ea099b9 Smart Scheduling Updates. 2022-02-10 14:49:19 -08:00
Patrick Fic
9f5b1c4ea5 Resolve in house bill posting. 2022-02-10 14:21:56 -08:00
Patrick Fic
4842605035 Autohouse year parsing upddate. 2022-02-10 11:01:23 -08:00
Patrick Fic
f9f14255a8 AH Updates. 2022-02-10 10:48:31 -08:00
Patrick Fic
139dedd3e7 Add upload logging. 2022-02-10 08:35:49 -08:00
Patrick Fic
bd9c2cd0af IO-1727 Skip posting contact and vehicle for PBS.. 2022-02-09 16:46:32 -08:00
Patrick Fic
e0e2183d86 Merged in release/2022-02-11 (pull request #377)
Release/2022 02 11
2022-02-10 00:30:28 +00:00
Patrick Fic
f8695972a3 IO-1708 Remove timezone from date fields on server calcuations. 2022-02-09 16:29:16 -08:00
Patrick Fic
4c70351429 Remove OEC from return modal. 2022-02-09 16:00:07 -08:00
Patrick Fic
bc504d2a78 Merged in release/2022-02-11 (pull request #376)
Release/2022 02 11
2022-02-09 23:52:27 +00:00
Patrick Fic
22dfcc215e IO-1718 Add part type to OEC. 2022-02-09 15:51:19 -08:00
Patrick Fic
415d6cca29 IO-1723 Job Costing Additional Items 2022-02-09 15:06:00 -08:00
Patrick Fic
6c0bf67f37 IO-1723 Updated job costing. 2022-02-09 14:55:16 -08:00
Patrick Fic
351459681c IO-1637 Tax rates randomly not pulling. 2022-02-09 10:55:12 -08:00
Patrick Fic
e7e9ca6dfc Schema Updates. 2022-02-09 10:35:15 -08:00
Patrick Fic
39998a279e Autohouse Fixes. 2022-02-09 10:34:52 -08:00
Patrick Fic
7c66e5cb90 Merged in release/2022-02-11 (pull request #375)
Release/2022 02 11
2022-02-09 17:46:15 +00:00
Patrick Fic
b4e0dcc395 IO-1699 Add additional scoreboard total details. 2022-02-08 13:08:50 -08:00
Patrick Fic
c16a2a83a5 IO-1679 Add returns to reconciliation calculation. 2022-02-08 13:08:31 -08:00
Patrick Fic
a2dca6c1a1 IO-1720 Add bill mark as exported. 2022-02-08 11:11:56 -08:00
Patrick Fic
ffb39bbee7 IO-1687 Add payable due date. 2022-02-08 11:07:31 -08:00
Patrick Fic
46731975e6 Merge branch 'hotfix/2022-02-08' into release/2022-02-11 2022-02-08 10:50:33 -08:00
Patrick Fic
06f725ebb1 Merged in hotfix/2022-02-08 (pull request #374)
Resolve monthly employee efficiency for infinity.
2022-02-08 18:05:04 +00:00
Patrick Fic
8745ffd08f Merged in hotfix/2022-02-08 (pull request #373)
Resolve monthly employee efficiency for infinity.
2022-02-08 18:04:24 +00:00
Patrick Fic
634adb6e9a Resolve monthly employee efficiency for infinity. 2022-02-08 09:47:58 -08:00
Patrick Fic
b5386be6af Merged in hotfix/2022-02-08 (pull request #372)
Hotfix/2022 02 08
2022-02-08 17:25:38 +00:00
Patrick Fic
14e9ac2cdb IO-1714 Resolve monthly efficiency component. 2022-02-08 09:08:16 -08:00
Patrick Fic
76fb8f453d Updated projected monthly sales. 2022-02-08 09:08:09 -08:00
Patrick Fic
afc674d74c IO-1676 Add RO Comment. 2022-02-07 19:31:08 -08:00
Patrick Fic
8c67a94387 IO-1714 Resolve monthly efficiency component. 2022-02-07 18:50:18 -08:00
Patrick Fic
a17b2b0923 Updated projected monthly sales. 2022-02-07 18:31:06 -08:00
Patrick Fic
91c5560fe8 IO-1708 Add shop timezone & update server side calculations. 2022-02-07 17:43:34 -08:00
Patrick Fic
356928ce77 Resolve double partner notification. 2022-02-07 15:04:07 -08:00
Patrick Fic
eb05a746c4 Resolve dashboard/reporting discrepancy. 2022-02-07 10:38:05 -08:00
Patrick Fic
07b5c5e93c Merged in release/2022-02-04 (pull request #368)
Update QBO Export.
2022-02-07 16:55:08 +00:00
Patrick Fic
7ef8ef5f2f Update QBO Export. 2022-02-07 08:46:27 -08:00
Patrick Fic
dcb9c32336 Merged in release/2022-02-04 (pull request #366)
Release/2022 02 04
2022-02-05 01:35:28 +00:00
Patrick Fic
f41277c081 Remove unnecessary logging. 2022-02-04 09:44:31 -08:00
Patrick Fic
7f15e9ef7a IO-1695 Add validation to time ticket window for clock times. 2022-02-04 09:29:37 -08:00
Patrick Fic
6d3fc783d6 IO-1697 Dont display partner message unless QBD. 2022-02-04 09:18:31 -08:00
Patrick Fic
0052a54915 IO-1662 removed scheduled completion on event cancel. 2022-02-03 12:20:45 -08:00
Patrick Fic
375856bd68 Remove customer name from QBO payables. 2022-02-03 12:15:33 -08:00
Patrick Fic
cf4f6f6e55 QBO Resolve classes issues. 2022-02-03 12:07:20 -08:00
Patrick Fic
173e9a278c QBO Updates. 2022-02-03 11:15:23 -08:00
Patrick Fic
35ab86c5fd Missed QBO receivables change. 2022-02-02 14:16:01 -08:00
Patrick Fic
932c89f8f9 Resolve QBO Receivables incorrectly labels. 2022-02-02 14:10:11 -08:00
Patrick Fic
ddd5ce4bf0 IO-1702 OEC Price. 2022-02-02 10:52:23 -08:00
Patrick Fic
1d0e526466 Autohouse query updates. 2022-02-02 09:50:47 -08:00
Patrick Fic
73e2b2d65d IO-1697 Remove acct path popup if not qbd. 2022-02-02 08:47:32 -08:00
Patrick Fic
88bc8d4d05 IO-1695 Edit time stamp when creating time ticket. 2022-02-01 15:09:20 -08:00
Patrick Fic
53cecd68f0 IO-1678 Add actual hours deducted from labor on bill posting. 2022-02-01 14:52:59 -08:00
Patrick Fic
4bce6d996e Create additional indexes for system performance. 2022-02-01 14:36:48 -08:00
Patrick Fic
6a59092d6a Merged in release/2022-01-28 (pull request #362)
release/2022-01-28

Approved-by: Patrick Fic
2022-01-28 01:51:05 +00:00
Patrick Fic
83b51384c7 Merged in release/2022-01-28 (pull request #361)
release/2022-01-28

Approved-by: Patrick Fic
2022-01-27 23:20:06 +00:00
Patrick Fic
f5e20b7041 IO-1675 Add OEM Part No to parts order modal. 2022-01-27 15:18:52 -08:00
Patrick Fic
8d1988d4ad IO-1674 Resolve new bill lines issues. 2022-01-27 15:07:00 -08:00
Patrick Fic
cd071500cf IO-1682 Restore customer name for payments. 2022-01-27 14:40:29 -08:00
Patrick Fic
09a0309108 Merged in release/2022-01-28 (pull request #360)
release/2022-01-28

Approved-by: Patrick Fic
2022-01-27 22:35:43 +00:00
Patrick Fic
65d93b8f9b IO-1669 Add print production by filtered category 2022-01-27 14:34:06 -08:00
Patrick Fic
b147d4c2ed IO-1664 Remove extra spaces for DMS story. 2022-01-27 14:13:49 -08:00
Patrick Fic
0c8c303d08 IO-1656 Allow images only to be sent via messaging. 2022-01-27 14:02:09 -08:00
Patrick Fic
b19120af3d Merged in release/2022-01-28 (pull request #359)
release/2022-01-28
2022-01-27 19:11:54 +00:00
Patrick Fic
d83e2ace2e IO-1671 Improved date handling. 2022-01-27 11:08:51 -08:00
Patrick Fic
468227b7b9 IO-1659 Resolve date saving issue. 2022-01-27 10:47:43 -08:00
Patrick Fic
021098fa2a IO-1674 Improve bill posting line display & order by price descending. 2022-01-27 08:46:19 -08:00
Patrick Fic
49f5668e89 IO-1674 Reorder bill posting lines by price. 2022-01-27 08:41:37 -08:00
Patrick Fic
9b444a130b IO-1673 Posting bills to removed lines for credit memos. 2022-01-27 08:37:26 -08:00
Patrick Fic
25fd90f881 IO-1672 Updated vendor discount display. 2022-01-26 14:06:24 -08:00
Patrick Fic
aa61aa6702 IO-1672 Updated bill line discount display. 2022-01-26 14:03:28 -08:00
Patrick Fic
a8b1537cd6 IO-1671 Improved date handling for form item. 2022-01-26 13:26:42 -08:00
Patrick Fic
2c702da1fd IO-1664 IO-1670 Improve DMS Story. 2022-01-26 13:26:28 -08:00
Patrick Fic
cb48ea64f9 IO-1682 Remove name from Payable/Payments exporting. 2022-01-26 12:34:43 -08:00
Patrick Fic
c17e1e92aa IO-1691 Segmented logrocket tracking. 2022-01-26 12:32:08 -08:00
Patrick Fic
b546d90c9e IO-1651 add estimates written/converted. 2022-01-26 08:46:44 -08:00
Patrick Fic
3985293cee IO-1656 Send media through sms fix. 2022-01-26 08:44:44 -08:00
Patrick Fic
4bd3b851ef IO-1657 Add status to global search. 2022-01-24 16:30:10 -08:00
Patrick Fic
bef76491d7 IO-1630 Add count to Visual Board. 2022-01-24 16:22:57 -08:00
Patrick Fic
4f3090c3bd IO-1377 Vendor name consistency on drawer title. 2022-01-24 16:07:08 -08:00
Patrick Fic
9d770a4cd5 Merged in release/2022-01-28 (pull request #358)
release/2022-01-28

Approved-by: Patrick Fic
2022-01-24 23:57:37 +00:00
Patrick Fic
f71a4b7c83 Merged in release/2022-01-28 (pull request #357)
release/2022-01-28

Approved-by: Patrick Fic
2022-01-24 21:44:40 +00:00
Patrick Fic
7ddd29ca27 IO-1642 Resolve delivery invalid date on intake. 2022-01-24 13:44:02 -08:00
Patrick Fic
ab607e9919 Merge branch 'release/2021-12-31' into test 2022-01-24 13:33:35 -08:00
Patrick Fic
5511dd44ce Autohouse - Export 1 2022-01-24 13:32:38 -08:00
Patrick Fic
d6a84b8b7f Remove autohouse testing. 2022-01-24 10:53:41 -08:00
Patrick Fic
a20e005583 Merged in release/2022-01-21 (pull request #356)
PBS Improvements.
2022-01-21 22:55:52 +00:00
Patrick Fic
c962d848c1 PBS Improvements. 2022-01-21 14:34:15 -08:00
Patrick Fic
b81d3369af Merged in release/2022-01-21 (pull request #355)
Release/2022 01 21
2022-01-21 22:21:45 +00:00
Patrick Fic
febcff3383 Merged in release/2022-01-21 (pull request #354)
Add PBS Logging.
2022-01-21 18:36:56 +00:00
Patrick Fic
61090aa04c Add PBS Logging. 2022-01-21 10:32:02 -08:00
Patrick Fic
e0f75fa357 Merged in release/2022-01-21 (pull request #353)
release/2022-01-21

Approved-by: Patrick Fic
2022-01-21 00:20:46 +00:00
Patrick Fic
9aa85b3ab5 IO-1552 AH Updates. 2022-01-20 16:20:09 -08:00
Patrick Fic
4704fd9ce1 IO-1573 Resolve CDK job costing error. 2022-01-20 15:04:57 -08:00
Patrick Fic
a0f06ffdc2 IO-1558 Add past due indicator to Kanban card. 2022-01-20 13:10:45 -08:00
Patrick Fic
c4eed109e9 Merged in release/2022-01-21 (pull request #352)
Release/2022 01 21
2022-01-20 18:38:19 +00:00
Patrick Fic
7d9eb737ec IO-1552 Autohouse include additional fields. 2022-01-20 09:54:48 -08:00
Patrick Fic
6fbf954dc2 IO-1652 Add production filtered by technician 2022-01-20 08:27:38 -08:00
Patrick Fic
968da48399 IO-1654 add discount for 900500 lines. 2022-01-20 08:12:12 -08:00
Patrick Fic
83906ea788 Merged in release/2022-01-21 (pull request #351)
Release/2022 01 21
2022-01-20 00:30:05 +00:00
Patrick Fic
83255cd316 IO-1653 Include Other images when downloading 2022-01-19 16:12:16 -08:00
Patrick Fic
1966e91463 IO-1644 Allow invoice date to be set on job closure. 2022-01-19 14:09:24 -08:00
Patrick Fic
413400fa71 IO-1652 Printing tickets from tech console. 2022-01-19 12:45:16 -08:00
Patrick Fic
abf9d0b7f4 IO-1558 2022-01-19 12:03:30 -08:00
Patrick Fic
4deed41a12 Revert "IO-1606 Include additional costs in reconciliation."
This reverts commit 821b044515.
2022-01-19 11:50:14 -08:00
Patrick Fic
4a7eb9b373 User email case sensitivity on login. 2022-01-19 11:46:49 -08:00
Patrick Fic
bf81c6dd06 ARMS Updates 2022-01-19 08:38:03 -08:00
Patrick Fic
7a89781a20 Merged in release/2022-01-21 (pull request #350)
Release/2022 01 21
2022-01-17 21:21:33 +00:00
Patrick Fic
34ae2c56b7 IO-1642 Resolve delivery checklist not including dates. 2022-01-17 12:35:43 -08:00
Patrick Fic
e2941bfe84 IO-1558 Add color to dates that are today. 2022-01-17 12:08:09 -08:00
Patrick Fic
821b044515 IO-1606 Include additional costs in reconciliation. 2022-01-17 11:48:12 -08:00
Patrick Fic
933e0a62fb IO-1573 CDK Job costing updates 2022-01-17 10:18:07 -08:00
Patrick Fic
25bc343027 IO-1643 Add shop name to bread crumb. 2022-01-17 09:26:38 -08:00
Patrick Fic
9ab08fbdd0 Merged in release/2022-01-14 (pull request #349)
release/2022-01-14

Approved-by: Patrick Fic
2022-01-14 22:41:23 +00:00
Patrick Fic
77727cc4b1 Merged in release/2022-01-14 (pull request #348)
release/2022-01-14

Approved-by: Patrick Fic
2022-01-13 21:43:19 +00:00
Patrick Fic
002301c792 Enable autohouse upload again. 2022-01-13 13:42:54 -08:00
Patrick Fic
7ee544b013 Minor PBS Updates. 2022-01-13 10:55:16 -08:00
Patrick Fic
3bc1785351 IO-1627 Add production category report. 2022-01-13 09:19:41 -08:00
Patrick Fic
1f58ee7d67 Merged in release/2022-01-14 (pull request #347)
release/2022-01-14

Approved-by: Patrick Fic
2022-01-12 21:44:45 +00:00
Patrick Fic
5ec1e84671 IO-1627 Add category to production. 2022-01-12 12:49:38 -08:00
Patrick Fic
d9fa00e633 IO-1636 Resolve scheduling error 2022-01-12 12:21:30 -08:00
Patrick Fic
1a53e7c2f7 Merged in release/2022-01-14 (pull request #346)
Release/2022 01 14
2022-01-12 16:53:58 +00:00
Patrick Fic
53a55c2b6e Merged in release/2022-01-14 (pull request #345)
Release/2022 01 14
2022-01-12 16:24:17 +00:00
Patrick Fic
5fe8a15a8c IO-1622 Resolve shift clock. 2022-01-12 08:15:16 -08:00
Patrick Fic
70cfbf2f5b IO-1552 Autohouse schema updates. 2022-01-11 13:13:54 -08:00
Patrick Fic
36a7b8346e Merged in release/2022-01-14 (pull request #344)
Release/2022 01 14
2022-01-11 20:44:01 +00:00
Patrick Fic
5e6ab55159 Merged in release/2022-01-14 (pull request #343)
Tech Console Posting
2022-01-11 20:28:57 +00:00
Patrick Fic
2aa6fdfac5 IO-1622 Add Rates to tech console. 2022-01-11 12:28:15 -08:00
Patrick Fic
8acd51c4f1 Merged in release/2022-01-14 (pull request #342)
Add RBAC to employees page.
2022-01-11 20:03:49 +00:00
Patrick Fic
e1d5558dac Add RBAC to employees page. 2022-01-11 12:03:09 -08:00
Patrick Fic
061212f915 Merge branch 'master' into release/2022-01-14 2022-01-11 12:01:03 -08:00
Patrick Fic
14bb735f00 Merged in release/2022-01-14 (pull request #341)
IO-1632 Resolve missing cieca code on clock out.
2022-01-11 19:40:43 +00:00
Patrick Fic
9714371a36 IO-1632 Resolve missing cieca code on clock out. 2022-01-11 11:39:56 -08:00
Patrick Fic
c7c5feeabe Merged in release/2022-01-14 (pull request #340)
IO-1632 Update tech console for CDK posting.
2022-01-11 18:17:39 +00:00
Patrick Fic
1c2a64cf50 IO-1632 Update tech console for CDK posting. 2022-01-11 10:12:10 -08:00
Patrick Fic
7d4d97ce4e Merged in release/2022-01-07 (pull request #339)
Release/2022 01 07
2022-01-08 00:43:51 +00:00
Patrick Fic
1d70053459 Merged in release/2022-01-07 (pull request #338)
IO-1612 Disable popover on PTO

Approved-by: Patrick Fic
2022-01-07 18:25:38 +00:00
Patrick Fic
675ccff3ce Merged in release/2022-01-07 (pull request #337)
release/2022-01-07

Approved-by: Patrick Fic
2022-01-07 18:01:34 +00:00
Patrick Fic
25b236072e Merged in release/2022-01-07 (pull request #336)
release/2022-01-07

Approved-by: Patrick Fic
2022-01-06 22:26:49 +00:00
Patrick Fic
b22318015e Merged in release/2022-01-07 (pull request #335)
release/2022-01-07

Approved-by: Patrick Fic
2022-01-06 18:14:27 +00:00
Patrick Fic
ddb7d8915e Merged in release/2022-01-07 (pull request #334)
release/2022-01-07

Approved-by: Patrick Fic
2022-01-04 20:33:41 +00:00
Patrick Fic
bca8ce0898 Merged in release/2022-01-07 (pull request #333)
release/2022-01-07

Approved-by: Patrick Fic
2022-01-03 18:49:44 +00:00
Patrick Fic
4fff9a5c99 Merged in hotfix/2021-12-29 (pull request #329)
hotfix/2021-12-29

Approved-by: Patrick Fic
2021-12-30 05:55:45 +00:00
Patrick Fic
e98e3049d8 Merged in hotfix/2021-12-29 (pull request #327)
hotfix/2021-12-29

Approved-by: Patrick Fic
2021-12-30 04:06:19 +00:00
Patrick Fic
456b00a605 Merged in release/2021-12/31 (pull request #326)
release/2021-12/31

Approved-by: Patrick Fic
2021-12-29 22:24:33 +00:00
Patrick Fic
cc078df80d Merged in release/2021-12/31 (pull request #325)
release/2021-12/31

Approved-by: Patrick Fic
2021-12-29 16:27:26 +00:00
Patrick Fic
80582692cf Merged in hotfix/2021-12-28 (pull request #323)
hotfix/2021-12-28

Approved-by: Patrick Fic
2021-12-28 22:17:06 +00:00
Patrick Fic
1bb0cbaebc Merged in release/2021-12/31 (pull request #321)
release/2021-12/31

Approved-by: Patrick Fic
2021-12-28 19:04:38 +00:00
Patrick Fic
a20a01dbd1 Merged in release/2021-12/31 (pull request #320)
release/2021-12/31

Approved-by: Patrick Fic
2021-12-27 22:00:14 +00:00
157 changed files with 8932 additions and 794 deletions

View File

@@ -10,7 +10,7 @@ npx hasura migrate apply --endpoint https://db.imex.online/ --admin-secret 'Prod
npx hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret 'Test-ImEXOnlineBySnaptSoftware!'
NGROK TEsting:
./ngrok.exe http http://localhost:5000 -host-header="localhost:5000"
./ngrok.exe http http://localhost:4000 -host-header="localhost:4000"
Finding deadfiles - run from client directory
npx deadfile ./src/index.js --exclude build templates

View File

@@ -1,4 +1,4 @@
<babeledit_project be_version="2.7.1" version="1.2">
<babeledit_project version="1.2" be_version="2.7.1">
<!--
BabelEdit project file
@@ -2863,6 +2863,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>markexported</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>markforreexport</name>
<definition_loaded>false</definition_loaded>
@@ -3120,6 +3141,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>markexported</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>reexport</name>
<definition_loaded>false</definition_loaded>
@@ -3830,6 +3872,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>disablecontactvehiclecreation</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>dms_acctnumber</name>
<definition_loaded>false</definition_loaded>
@@ -7794,6 +7857,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>timezone</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>tt_allow_post_to_invoiced</name>
<definition_loaded>false</definition_loaded>
@@ -20329,6 +20413,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>comment</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>customerowing</name>
<definition_loaded>false</definition_loaded>
@@ -25036,6 +25141,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>cost_Additional</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>cost_labor</name>
<definition_loaded>false</definition_loaded>
@@ -25078,6 +25204,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>cost_sublet</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>costs</name>
<definition_loaded>false</definition_loaded>
@@ -25448,6 +25595,27 @@
<folder_node>
<name>dms</name>
<children>
<concept_node>
<name>damageto</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>defaultstory</name>
<definition_loaded>false</definition_loaded>
@@ -25469,6 +25637,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>invoicedatefuture</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>kmoutnotgreaterthankmin</name>
<definition_loaded>false</definition_loaded>
@@ -26888,6 +27077,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>sale_additional</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>sale_labor</name>
<definition_loaded>false</definition_loaded>
@@ -26930,6 +27140,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>sale_sublet</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>sales</name>
<definition_loaded>false</definition_loaded>
@@ -31959,6 +32190,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>part_type</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>quantity</name>
<definition_loaded>false</definition_loaded>
@@ -35481,6 +35733,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>exported_payroll</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node>
@@ -36062,6 +36335,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>comment</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>compact</name>
<definition_loaded>false</definition_loaded>
@@ -37146,6 +37440,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>estimates_written_converted</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>estimator_detail</name>
<definition_loaded>false</definition_loaded>
@@ -38007,6 +38322,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>production_by_category</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>production_by_category_one</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>production_by_csr</name>
<definition_loaded>false</definition_loaded>
@@ -38133,6 +38490,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>production_by_technician_one</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>purchases_by_cost_center_detail</name>
<definition_loaded>false</definition_loaded>
@@ -38777,6 +39155,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>dailyactual</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>dailytarget</name>
<definition_loaded>false</definition_loaded>
@@ -38840,6 +39239,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>todateactual</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>weeklyactual</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>weeklytarget</name>
<definition_loaded>false</definition_loaded>
@@ -40135,6 +40576,53 @@
</concept_node>
</children>
</folder_node>
<folder_node>
<name>validation</name>
<children>
<concept_node>
<name>clockoffmustbeafterclockon</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>clockoffwithoutclockon</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>

View File

@@ -33,6 +33,7 @@
"logrocket": "^2.1.2",
"markerjs2": "^2.17.2",
"moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.34",
"phone": "^3.1.10",
"preval.macro": "^5.0.0",
"prop-types": "^15.7.2",

View File

@@ -2,7 +2,6 @@ import { ApolloProvider } from "@apollo/client";
import { SplitFactory, SplitSdk } from "@splitsoftware/splitio-react";
import { ConfigProvider } from "antd";
import enLocale from "antd/es/locale/en_US";
import LogRocket from "logrocket";
import moment from "moment";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -10,13 +9,8 @@ import GlobalLoadingBar from "../components/global-loading-bar/global-loading-ba
import client from "../utils/GraphQLClient";
import App from "./App";
moment.locale("en-US");
//tracker.start();
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
export const factory = SplitSdk({
core: {
authorizationKey: process.env.REACT_APP_SPLIT_API,

View File

@@ -1,4 +1,6 @@
import { useTreatments } from "@splitsoftware/splitio-react";
import { Button, Result } from "antd";
import LogRocket from "logrocket";
import React, { lazy, Suspense, useEffect } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -9,15 +11,18 @@ import ErrorBoundary from "../components/error-boundary/error-boundary.component
//Component Imports
import LoadingSpinner from "../components/loading-spinner/loading-spinner.component";
import DisclaimerPage from "../pages/disclaimer/disclaimer.page";
import LandingPage from "../pages/landing/landing.page";
import TechPageContainer from "../pages/tech/tech.page.container";
import { setOnline } from "../redux/application/application.actions";
import { selectOnline } from "../redux/application/application.selectors";
import { checkUserSession } from "../redux/user/user.actions";
import { selectCurrentUser } from "../redux/user/user.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../redux/user/user.selectors";
import PrivateRoute from "../utils/private-route";
import "./App.styles.scss";
import LandingPage from "../pages/landing/landing.page";
const ResetPassword = lazy(() =>
import("../pages/reset-password/reset-password.component")
);
@@ -32,13 +37,26 @@ const MobilePaymentContainer = lazy(() =>
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
online: selectOnline,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
checkUserSession: () => dispatch(checkUserSession()),
setOnline: (isOnline) => dispatch(setOnline(isOnline)),
});
export function App({ checkUserSession, currentUser, online, setOnline }) {
export function App({
bodyshop,
checkUserSession,
currentUser,
online,
setOnline,
}) {
const { LogRocket_Tracking } = useTreatments(
["LogRocket_Tracking"],
{},
bodyshop && bodyshop.imexshopid
);
useEffect(() => {
if (!navigator.onLine) {
setOnline(false);
@@ -59,6 +77,16 @@ export function App({ checkUserSession, currentUser, online, setOnline }) {
window.addEventListener("online", function (e) {
setOnline(true);
});
useEffect(() => {
if (currentUser.authorized) {
if (
process.env.NODE_ENV === "production" &&
LogRocket_Tracking.treatment === "on"
) {
LogRocket.init("gvfvfw/bodyshopapp");
}
}
}, [currentUser.authorized, LogRocket_Tracking.treatment]);
if (currentUser.authorized === null) {
return <LoadingSpinner message={t("general.labels.loggingin")} />;

View File

@@ -91,13 +91,13 @@
color: blue;
}
.production-completion-1 {
animation: production-completion-1-blinker 5s linear infinite;
.production-completion-soon {
color: rgba(255, 140, 0, 0.8);
font-weight: bold;
}
@keyframes production-completion-1-blinker {
50% {
background: rgba(207, 12, 12, 0.555);
}
.production-completion-past {
color: rgba(255, 0, 0, 0.8);
font-weight: bold;
}
.react-resizable {

View File

@@ -28,6 +28,7 @@ import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -107,7 +108,7 @@ export function BillDetailEditcontainer({
);
billlines.forEach((billline) => {
const { deductedfromlbr, ...il } = billline;
const { deductedfromlbr, jobline, ...il } = billline;
delete il.__typename;
if (il.id) {
@@ -206,6 +207,8 @@ export function BillDetailEditcontainer({
cost: i.actual_cost,
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type,
};
}),
isReturn: true,
@@ -234,6 +237,7 @@ export function BillDetailEditcontainer({
</Button>
</Popconfirm>
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
<BillMarkExportedButton bill={data && data.bills_by_pk} />
</Space>
}
/>

View File

@@ -93,6 +93,7 @@ function BillEnterModalContainer({
deductedfromlbr,
lbr_adjustment,
location: lineLocation,
part_type,
...restI
} = i;
@@ -216,7 +217,11 @@ function BillEnterModalContainer({
if (enterAgain) {
form.resetFields();
form.setFieldsValue({ ...form.getFieldsValue(), billlines: [] });
form.resetFields();
form.setFieldsValue({
...formValues,
billlines: [],
});
} else {
toggleModalVisible();
}
@@ -245,7 +250,7 @@ function BillEnterModalContainer({
return (
<Modal
title={t("bills.labels.new")}
width={"90%"}
width={"98%"}
visible={billEnterModal.visible}
okText={t("general.actions.save")}
keyboard="false"

View File

@@ -1,4 +1,4 @@
import { DeleteFilled, WarningOutlined } from "@ant-design/icons";
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import {
Button,
Form,
@@ -8,6 +8,7 @@ import {
Space,
Switch,
Table,
Tooltip
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -43,7 +44,7 @@ export function BillEnterModalLinesComponent({
title: t("billlines.fields.jobline"),
dataIndex: "joblineid",
editable: true,
width: "20rem",
formItemProps: (field) => {
return {
key: `${field.index}joblinename`,
@@ -57,11 +58,24 @@ export function BillEnterModalLinesComponent({
],
};
},
wrapper: (props) => (
<Form.Item
noStyle
shouldUpdate={(prev, cur) =>
prev.is_credit_memo !== cur.is_credit_memo
}
>
{() => {
return props.children;
}}
</Form.Item>
),
formInput: (record, index) => (
<BillLineSearchSelect
disabled={disabled}
options={lineData}
style={{ width: "100%", minWidth: "10rem" }}
allowRemoved={form.getFieldValue("is_credit_memo") || false}
onSelect={(value, opt) => {
setFieldsValue({
billlines: getFieldsValue(["billlines"]).billlines.map(
@@ -201,23 +215,58 @@ export function BillEnterModalLinesComponent({
};
},
formInput: (record, index) => (
<CurrencyInput min={0} disabled={disabled} />
<CurrencyInput
min={0}
disabled={disabled}
controls={false}
addonAfter={
<Form.Item shouldUpdate noStyle>
{() => {
const line = getFieldsValue(["billlines"]).billlines[index];
if (!!!line) return null;
let lineDiscount = 1 - line.actual_cost / line.actual_price;
if (isNaN(lineDiscount)) lineDiscount = 0;
return (
<Tooltip title={`${(lineDiscount * 100).toFixed(2) || 0}%`}>
<DollarCircleFilled
style={{
color:
Math.abs(lineDiscount - discount) > 0.005
? lineDiscount > discount
? "orange"
: "red"
: "green",
}}
/>
</Tooltip>
);
}}
</Form.Item>
}
/>
),
additional: (record, index) => (
<Form.Item shouldUpdate>
{() => {
const line = getFieldsValue(["billlines"]).billlines[index];
if (!!!line) return null;
const lineDiscount = (
1 -
Math.round((line.actual_cost / line.actual_price) * 100) / 100
).toPrecision(2);
// additional: (record, index) => (
// <Form.Item shouldUpdate>
// {() => {
// const line = getFieldsValue(["billlines"]).billlines[index];
// if (!!!line) return null;
// const lineDiscount = (
// 1 -
// Math.round((line.actual_cost / line.actual_price) * 100) / 100
// ).toPrecision(2);
if (lineDiscount - discount === 0) return <div />;
return <WarningOutlined style={{ color: "red" }} />;
}}
</Form.Item>
),
// return (
// <Tooltip title={`${(lineDiscount * 100).toFixed(0) || 0}%`}>
// <DollarCircleFilled
// style={{
// color: lineDiscount - discount !== 0 ? "red" : "green",
// }}
// />
// </Tooltip>
// );
// }}
// </Form.Item>
// ),
},
{
title: t("billlines.fields.cost_center"),
@@ -285,6 +334,19 @@ export function BillEnterModalLinesComponent({
additional: (record, index) => (
<Form.Item shouldUpdate style={{ display: "inline-block" }}>
{() => {
const price = getFieldValue([
"billlines",
record.name,
"actual_price",
]);
const adjustmentRate = getFieldValue([
"billlines",
record.name,
"lbr_adjustment",
"rate",
]);
if (getFieldValue(["billlines", record.name, "deductedfromlbr"]))
return (
<div>
@@ -357,6 +419,9 @@ export function BillEnterModalLinesComponent({
>
<InputNumber precision={2} min={0.01} />
</Form.Item>
{price &&
adjustmentRate &&
`${(price / adjustmentRate).toFixed(1)} hrs`}
</div>
);
return <></>;
@@ -488,6 +553,7 @@ const EditableCell = ({
formInput,
formItemProps,
additional,
wrapper,
...restProps
}) => {
if (additional)
@@ -505,7 +571,20 @@ const EditableCell = ({
</Space>
</td>
);
if (wrapper)
return (
<wrapper>
<td {...restProps}>
<Form.Item
labelCol={{ span: 0 }}
name={dataIndex}
{...(formItemProps && formItemProps(record))}
>
{(formInput && formInput(record, record.name)) || children}
</Form.Item>
</td>
</wrapper>
);
return (
<td {...restProps}>
<Form.Item

View File

@@ -4,7 +4,10 @@ import { useTranslation } from "react-i18next";
//To be used as a form element only.
const { Option } = Select;
const BillLineSearchSelect = ({ options, disabled, ...restProps }, ref) => {
const BillLineSearchSelect = (
{ options, disabled, allowRemoved, ...restProps },
ref
) => {
const { t } = useTranslation();
return (
@@ -12,6 +15,7 @@ const BillLineSearchSelect = ({ options, disabled, ...restProps }, ref) => {
disabled={disabled}
ref={ref}
showSearch
dropdownMatchSelectWidth={false}
// optionFilterProp="line_desc"
filterOption={(inputValue, option) => {
return (
@@ -36,7 +40,7 @@ const BillLineSearchSelect = ({ options, disabled, ...restProps }, ref) => {
{options
? options.map((item) => (
<Option
disabled={item.removed}
disabled={allowRemoved ? false : item.removed}
key={item.id}
value={item.id}
cost={item.act_price ? item.act_price : 0}
@@ -49,9 +53,14 @@ const BillLineSearchSelect = ({ options, disabled, ...restProps }, ref) => {
...(item.removed ? { textDecoration: "line-through" } : {}),
}}
>
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
<span>{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.act_price ? ` - $${item.act_price}` : ``}`}
}`}</span>
<span style={{ float: "right", paddingleft: "1rem" }}>
{item.act_price
? `$${item.act_price && item.act_price.toFixed(2)}`
: ``}
</span>
</Option>
))
: null}

View File

@@ -0,0 +1,82 @@
import { useMutation } from "@apollo/client";
import { Button, notification } from "antd";
import { gql } from "@apollo/client";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
selectAuthLevel,
selectBodyshop,
} from "../../redux/user/user.selectors";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillMarkExportedButton);
export function BillMarkExportedButton({ bodyshop, authLevel, bill }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [updateBill] = useMutation(gql`
mutation UPDATE_BILL($billId: uuid!) {
update_bills(where: { id: { _eq: $billId } }, _set: { exported: true }) {
returning {
id
exported
exported_at
}
}
}
`);
const handleUpdate = async () => {
setLoading(true);
const result = await updateBill({
variables: { billId: bill.id },
});
if (!result.errors) {
notification["success"]({
message: t("bills.successes.markexported"),
});
} else {
notification["error"]({
message: t("bills.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
setLoading(false);
//Get the owner details, populate it all back into the job.
};
const hasAccess = HasRbacAccess({
bodyshop,
authLevel,
action: "bills:reexport",
});
if (hasAccess)
return (
<Button
loading={loading}
disabled={bill.exported}
onClick={handleUpdate}
>
{t("bills.labels.markexported")}
</Button>
);
return <></>;
}

View File

@@ -58,7 +58,8 @@ export function BillsListTableComponent({
disabled={
record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid
}
onClick={() =>
onClick={() => {
console.log(record);
setPartsOrderContext({
actions: {},
context: {
@@ -74,12 +75,14 @@ export function BillsListTableComponent({
cost: i.actual_cost,
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type,
};
}),
isReturn: true,
},
})
}
});
}}
>
{t("bills.actions.return")}
</Button>

View File

@@ -5,21 +5,25 @@ import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { selectBreadcrumbs } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import GlobalSearch from "../global-search/global-search.component";
import "./breadcrumbs.styles.scss";
const mapStateToProps = createStructuredSelector({
breadcrumbs: selectBreadcrumbs,
bodyshop: selectBodyshop,
});
export function BreadCrumbs({ breadcrumbs }) {
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
return (
<Row className="breadcrumb-container">
<Col xs={24} sm={24} md={16}>
<Breadcrumb separator=">">
<Breadcrumb.Item>
<Link to={`/manage`}>
<HomeFilled />
<HomeFilled />{" "}
{(bodyshop && bodyshop.shopname && `(${bodyshop.shopname})`) ||
""}
</Link>
</Breadcrumb.Item>
{breadcrumbs.map((item) =>

View File

@@ -45,13 +45,14 @@ function ChatSendMessageComponent({
const { t } = useTranslation();
const handleEnter = () => {
if (message === "" || !message) return;
logImEXEvent("messaging_send_message");
const selectedImages = selectedMedia.filter((i) => i.isSelected);
if (selectedImages < 11) {
if ((message === "" || !message) && selectedImages.length === 0) return;
logImEXEvent("messaging_send_message");
if (selectedImages.length < 11) {
sendMessage({
to: conversation.phone_num,
body: message,
body: message || "",
messagingServiceSid: bodyshop.messagingservicesid,
conversationid: conversation.id,
selectedMedia: selectedImages,
@@ -92,7 +93,7 @@ function ChatSendMessageComponent({
</span>
<SendOutlined
className="imex-flex-row__margin"
disabled={message === "" || !message}
// disabled={message === "" || !message}
onClick={handleEnter}
/>
<Spin

View File

@@ -97,7 +97,9 @@ export default function CourtesyCarsList({ loading, courtesycars, refetch }) {
render: (text, record) =>
record.cccontracts.length === 1 ? (
<Link to={`/manage/jobs/${record.cccontracts[0].job.id}`}>
{record.cccontracts[0].job.ro_number}
{`${record.cccontracts[0].job.ro_number} - ${
record.cccontracts[0].job.ownr_fn || ""
} ${record.cccontracts[0].job.ownr_ln || ""} ${record.cccontracts[0].job.ownr_co_nm || ""}`}
</Link>
) : null,
},

View File

@@ -40,7 +40,7 @@ export default function DashboardMonthlyEmployeeEfficiency({
(dayAcc, dayVal) => {
return {
actual: dayAcc.actual + dayVal.actualhrs,
productive: dayAcc.actual + dayVal.productivehrs,
productive: dayAcc.productive + dayVal.productivehrs,
};
},
{ actual: 0, productive: 0 }
@@ -50,11 +50,13 @@ export default function DashboardMonthlyEmployeeEfficiency({
}
const dailyEfficiency =
((dailyHrs.productive - dailyHrs.actual) / dailyHrs.productive + 1) * 100;
((dailyHrs.productive - dailyHrs.actual) / dailyHrs.actual + 1) * 100;
const theValue = {
date: moment(val).format("DD"),
...dailyHrs,
// ...dailyHrs,
actual: dailyHrs.actual.toFixed(1),
productive: dailyHrs.productive.toFixed(1),
dailyEfficiency: isNaN(dailyEfficiency) ? 0 : dailyEfficiency.toFixed(1),
accActual:
acc.length > 0
@@ -67,12 +69,18 @@ export default function DashboardMonthlyEmployeeEfficiency({
: dailyHrs.productive,
accEfficiency: 0,
};
theValue.accEfficiency = (
theValue.accEfficiency =
((theValue.accProductive - theValue.accActual) /
(theValue.accProductive || 1) +
(theValue.accActual || 0) +
1) *
100
).toFixed(1);
100;
if (isNaN(theValue.accEfficiency)) {
theValue.accEfficiency = 0;
} else {
theValue.accEfficiency = theValue.accEfficiency.toFixed(1);
}
return [...acc, theValue];
}, []);
@@ -131,6 +139,7 @@ export default function DashboardMonthlyEmployeeEfficiency({
//stackId="day"
barSize={20}
fill="#102568"
format={"0.0"}
/>
<Bar
name="Productive Hours"
@@ -140,6 +149,7 @@ export default function DashboardMonthlyEmployeeEfficiency({
//stackId="day"
barSize={20}
fill="#017664"
format={"0.0"}
/>
</ComposedChart>
</ResponsiveContainer>

View File

@@ -31,16 +31,51 @@ export default function DashboardProjectedMonthlySales({ data, ...cardProps }) {
}
export const DashboardProjectedMonthlySalesGql = `
projected_monthly_sales: jobs(where: {_or: [{_and: [{date_invoiced: {_gte: "${moment()
projected_monthly_sales: jobs(where: {
voided: {_eq: false},
_or: [
{_and: [
{date_invoiced:{_is_null: false }},
{date_invoiced: {_gte: "${moment()
.startOf("month")
.startOf("day")
.toISOString()}"}}, {date_invoiced: {_lte: "${moment()
.endOf("month")
.endOf("day")
.toISOString()}"}}]},
{
_and:[
{date_invoiced:{_is_null: true }},
{actual_completion: {_gte: "${moment()
.startOf("month")
.startOf("day")
.toISOString()}"}}, {actual_completion: {_lte: "${moment()
.endOf("month")
.endOf("day")
.toISOString()}"}}
]
},
{_and: [
{date_invoiced: {_is_null: true}},
{actual_completion: {_is_null: true}}
{scheduled_completion: {_gte: "${moment()
.startOf("month")
.format("YYYY-MM-DD")}"}}, {date_invoiced: {_lte: "${moment()
.startOf("day")
.toISOString()}"}}, {scheduled_completion: {_lte: "${moment()
.endOf("month")
.format("YYYY-MM-DD")}"}}]}, {_and: [{scheduled_completion: {_gte: "${moment()
.startOf("month")
.format("YYYY-MM-DD")}"}}, {scheduled_completion: {_lte: "${moment()
.endOf("month")
.format("YYYY-MM-DD")}"}}]}]}) {
.endOf("day")
.toISOString()}"}}
]}
]}) {
id
ro_number
voided
date_invoiced
job_totals
}

View File

@@ -280,12 +280,13 @@ const createDashboardQuery = (state) => {
return gql`
query QUERY_DASHBOARD_DETAILS {
${componentBasedAdditions || ""}
monthly_sales: jobs(where: {_and: [{date_invoiced: {_gte: "${moment()
.startOf("month")
.format("YYYY-MM-DD")}"}}, {date_invoiced: {_lte: "${moment()
.endOf("month")
.format("YYYY-MM-DD")}"}}]}) {
monthly_sales: jobs(where: {_and: [
{ voided: {_eq: false}},
{date_invoiced: {_gte: "${moment()
.startOf("month").startOf('day').toISOString()}"}}, {date_invoiced: {_lte: "${moment()
.endOf("month").endOf('day').toISOString()}"}}]}) {
id
ro_number
date_invoiced
job_totals
rate_la1
@@ -333,14 +334,14 @@ const createDashboardQuery = (state) => {
part_qty
part_type
}
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" } }) {
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
aggregate {
sum {
mod_lb_hrs
}
}
}
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" } }) {
larhrs: joblines_aggregate(where: { mod_lbr_ty: { _eq: "LAR" }, removed: { _eq: false } }) {
aggregate {
sum {
mod_lb_hrs

View File

@@ -2,7 +2,6 @@ import { DeleteFilled, DownOutlined } from "@ant-design/icons";
import {
Button,
Card,
DatePicker,
Divider,
Dropdown,
Form,
@@ -15,6 +14,7 @@ import {
Typography,
} from "antd";
import Dinero from "dinero.js";
import moment from "moment";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -23,9 +23,9 @@ 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 DmsCdkMakesRefetch from "../dms-cdk-makes/dms-cdk-makes.refetch.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import moment from "moment";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -80,11 +80,25 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
layout="vertical"
onFinish={handleFinish}
initialValues={{
story: t("jobs.labels.dms.defaultstory", {
story: `${t("jobs.labels.dms.defaultstory", {
ro_number: job.ro_number,
area_of_damage:
(job.area_of_damage && job.area_of_damage.impact1) || "UNKNOWN",
}).substr(0, 239),
ownr_nm: `${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`.trim(),
ins_co_nm: job.ins_co_nm || "N/A",
clm_po: `${job.clm_no ? `${job.clm_no} ` : ""}${
job.po_number || ""
}`,
}).trim()}.${
job.area_of_damage && job.area_of_damage.impact1
? " " +
t("jobs.labels.dms.damageto", {
area_of_damage:
(job.area_of_damage && job.area_of_damage.impact1) ||
"UNKNOWN",
})
: ""
}`.substr(0, 239),
inservicedate: moment("2019-01-01"),
}}
>
@@ -162,7 +176,7 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
name="inservicedate"
label={t("jobs.fields.dms.inservicedate")}
>
<DatePicker format="MM/DD/YYYY" />
<FormDatePicker />
</Form.Item>
</LayoutFormRow>
<Space>

View File

@@ -6,6 +6,7 @@ import { INSERT_NEW_DOCUMENT } from "../../graphql/documents.queries";
import { axiosAuthInterceptorId } from "../../utils/CleanAxios";
import client from "../../utils/GraphQLClient";
import exifr from "exifr";
import { store } from "../../redux/store";
//Context: currentUserEmail, bodyshop, jobid, invoiceid
@@ -112,7 +113,19 @@ export const uploadToCloudinary = async (
);
if (cloudinaryUploadResponse.status !== 200) {
if (!!onError) onError(cloudinaryUploadResponse.statusText);
if (!!onError) {
onError(cloudinaryUploadResponse.statusText);
}
try {
axios.post("/newlog", {
message: "client-cloudinary-upload-error",
type: "error",
user: store.getState().user.email,
object: cloudinaryUploadResponse,
});
} catch (error) {}
notification["error"]({
message: i18n.t("documents.errors.insert", {
message: cloudinaryUploadResponse.statusText,

View File

@@ -1,35 +1,83 @@
import { DatePicker } from "antd";
import moment from "moment";
import React, { forwardRef } from "react";
import React, { useRef } from "react";
//To be used as a form element only.
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(FormDatePicker);
const dateFormat = "MM/DD/YYYY";
const FormDatePicker = (
{ value, onChange, onBlur, onlyFuture, ...restProps },
ref
) => {
export function FormDatePicker({
bodyshop,
value,
onChange,
onBlur,
onlyFuture,
isDateOnly = true,
...restProps
}) {
const ref = useRef();
const handleChange = (newDate) => {
if (value !== newDate && onChange) {
onChange(newDate);
onChange(isDateOnly ? newDate && newDate.format("YYYY-MM-DD") : newDate);
}
};
const handleKeyDown = (e) => {
if (e.key.toLowerCase() === "t") {
if (onChange) {
onChange(new moment());
onChange(isDateOnly ? moment().format("YYYY-MM-DD") : moment());
// if (ref.current && ref.current.blur) ref.current.blur();
}
} else if (e.key.toLowerCase() === "enter") {
if (ref.current && ref.current.blur) ref.current.blur();
}
};
const handleBlur = (e) => {
const v = e.target.value;
if (!v) return;
const _a = moment(
v,
["MMDDYY", "MMDDYYYY", "MMDD", "MM/DD/YY"],
"en",
false
);
if (_a.isValid() && value && value.isValid && value.isValid()) {
_a.set({
hours: value.hours(),
minutes: value.minutes(),
seconds: value.seconds(),
milliseconds: value.milliseconds(),
});
}
if (_a.isValid() && onChange)
onChange(isDateOnly ? _a.format("YYYY-MM-DD") : _a);
};
return (
<div onKeyDown={handleKeyDown}>
<DatePicker
ref={ref}
value={value ? moment(value) : null}
onChange={handleChange}
format={dateFormat}
onBlur={onBlur}
onBlur={onBlur || handleBlur}
showToday={false}
disabledTime
{...(onlyFuture && {
disabledDate: (d) => moment().subtract(1, "day").isAfter(d),
@@ -38,6 +86,4 @@ const FormDatePicker = (
/>
</div>
);
};
export default forwardRef(FormDatePicker);
}

View File

@@ -26,19 +26,20 @@ const DateTimePicker = (
value={value}
onBlur={onBlur}
onChange={onChange}
isDateOnly={false}
/>
<TimePicker
{...restProps}
value={value ? moment(value) : null}
{...(onlyFuture && {
disabledDate: (d) => moment().isAfter(d),
})}
onChange={onChange}
showSecond={false}
minuteStep={15}
onBlur={onBlur}
format="hh:mm a"
value={value ? moment(value) : null}
{...(onlyFuture && {
disabledDate: (d) => moment().isAfter(d),
})}
onChange={onChange}
showSecond={false}
minuteStep={15}
onBlur={onBlur}
format="hh:mm a"
{...restProps}
/>
</div>
);

View File

@@ -38,6 +38,7 @@ export default function GlobalSearch() {
<Link to={`/manage/jobs/${job.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<strong>{job.ro_number || t("general.labels.na")}</strong>
<span>{`${job.status || ""}`}</span>
<span>{`${job.ownr_fn || ""} ${job.ownr_ln || ""} ${
job.ownr_co_nm || ""
}`}</span>

View File

@@ -38,6 +38,7 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
job: {
date_scheduled: null,
scheduled_in: null,
scheduled_completion:null,
status: bodyshop.md_ro_statuses.default_imported,
},
},

View File

@@ -72,7 +72,7 @@ export default function JobBillsTotalComponent({
const discrepWithLbrAdj = discrepancy.add(lbrAdjustments);
const discrepWithCms = discrepWithLbrAdj.add(billCms);
const discrepWithCms = discrepWithLbrAdj.add(totalReturns);
const creditsNotReceived = totalReturns.subtract(billCms); //billCms is tracked as a negative number.
return (
@@ -171,8 +171,8 @@ export default function JobBillsTotalComponent({
}
>
<Statistic
title={t("bills.labels.billcmtotal")}
value={billCms.toFormat()}
title={t("bills.labels.totalreturns")}
value={totalReturns.toFormat()}
/>
</Tooltip>
<Typography.Title>=</Typography.Title>

View File

@@ -105,6 +105,10 @@ export function JobChecklistForm({
completed_at: new Date(),
},
...(type === "intake" &&
values.scheduled_delivery && {
scheduled_delivery: values.scheduled_delivery,
}),
...(type === "deliver" && {
scheduled_delivery: values.scheduled_delivery,
actual_delivery: values.actual_delivery,
@@ -171,7 +175,12 @@ export function JobChecklistForm({
});
}
};
console.log(job, {
removeFromProduction: true,
actual_completion:
job && job.actual_completion && moment(job.actual_completion),
actual_delivery: job && job.actual_delivery && moment(job.actual_delivery),
});
return (
<Card
title={t("checklist.labels.checklist")}
@@ -195,21 +204,27 @@ export function JobChecklistForm({
addToProduction: true,
allow_text_message: job.owner && job.owner.allow_text_message,
scheduled_completion:
(job && job.scheduled_completion) ||
(job.labbrs && job.larhrs
? moment().businessAdd(
(job.labhrs.aggregate.sum.mod_lb_hrs +
job.larhrs.aggregate.sum.mod_lb_hrs) /
bodyshop.target_touchtime,
"days"
)
: null),
scheduled_delivery: job && job.scheduled_delivery,
(job &&
job.scheduled_completion &&
moment(job.scheduled_completion)) ||
(job &&
job.labhrs &&
job.larhrs &&
moment().businessAdd(
(job.labhrs.aggregate.sum.mod_lb_hrs ||
0 + job.larhrs.aggregate.sum.mod_lb_hrs ||
0) / bodyshop.target_touchtime,
"days"
)),
scheduled_delivery:
job.scheduled_delivery && moment(job.scheduled_delivery),
}),
...(type === "deliver" && {
removeFromProduction: true,
actual_completion: job && job.actual_completion,
actual_delivery: job && job.actual_delivery,
actual_completion:
job && job.actual_completion && moment(job.actual_completion),
actual_delivery:
job && job.actual_delivery && moment(job.actual_delivery),
}),
...formItems
.filter((fi) => fi.value)

View File

@@ -26,11 +26,6 @@ export function JobCostingModalContainer({
const { visible, context } = jobCostingModal;
const { jobId } = context;
// const { loading, error, data } = useQuery(QUERY_JOB_COSTING_DETAILS, {
// variables: { id: jobId },
// skip: !jobId,
// });
useEffect(() => {
async function getData() {
if (jobId && visible) {
@@ -46,8 +41,14 @@ export function JobCostingModalContainer({
<Modal
visible={visible}
title={t("jobs.labels.jobcosting")}
onOk={() => toggleModalVisible()}
onCancel={() => toggleModalVisible()}
onOk={() => {
toggleModalVisible();
setCostingData(null);
}}
onCancel={() => {
toggleModalVisible();
setCostingData(null);
}}
cancelButtonProps={{ style: { display: "none" } }}
width="90%"
destroyOnClose

View File

@@ -16,6 +16,14 @@ export default function JobCostingStatistics({ summaryData }) {
value={Dinero(summaryData.totalPartsSales).toFormat()}
title={t("jobs.labels.sale_parts")}
/>
<Statistic
value={Dinero(summaryData.totalSubletSales).toFormat()}
title={t("jobs.labels.sale_sublet")}
/>
<Statistic
value={Dinero(summaryData.totalAdditionalSales).toFormat()}
title={t("jobs.labels.sale_additional")}
/>
<Statistic
value={Dinero(summaryData.totalSales).toFormat()}
title={t("jobs.labels.total_sales")}
@@ -28,6 +36,14 @@ export default function JobCostingStatistics({ summaryData }) {
value={Dinero(summaryData.totalPartsCost).toFormat()}
title={t("jobs.labels.cost_parts")}
/>
<Statistic
value={Dinero(summaryData.totalSubletCost).toFormat()}
title={t("jobs.labels.cost_sublet")}
/>
<Statistic
value={Dinero(summaryData.totalAdditionalCost).toFormat()}
title={t("jobs.labels.cost_Additional")}
/>
<Statistic
value={Dinero(summaryData.totalCost).toFormat()}
title={t("jobs.labels.total_cost")}

View File

@@ -33,10 +33,10 @@ export default function JobTotalsTableOther({ job }) {
key: t("jobs.fields.storage_payable"),
total: job.job_totals.additional.storage,
},
{
key: t("jobs.fields.ca_bc_pvrt"),
total: job.job_totals.additional.pvrt,
},
// {
// key: t("jobs.fields.ca_bc_pvrt"),
// total: job.job_totals.additional.pvrt,
// },
];
}, [job.job_totals, t]);

View File

@@ -3,7 +3,22 @@ import Dinero from "dinero.js";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
export default function JobTotalsTableTotals({ job }) {
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobTotalsTableTotals);
export function JobTotalsTableTotals({ bodyshop, job }) {
const { t } = useTranslation();
const data = useMemo(() => {
@@ -21,6 +36,14 @@ export default function JobTotalsTableTotals({ job }) {
key: t("jobs.labels.state_tax_amt"),
total: job.job_totals.totals.state_tax,
},
...(bodyshop.region_config === "CA_BC"
? [
{
key: t("jobs.fields.ca_bc_pvrt"),
total: job.job_totals.additional.pvrt,
},
]
: []),
{
key: t("jobs.labels.federal_tax_amt"),
total: job.job_totals.totals.federal_tax,
@@ -58,7 +81,7 @@ export default function JobTotalsTableTotals({ job }) {
bold: true,
},
];
}, [job.job_totals, t]);
}, [job.job_totals, t, bodyshop.region_config]);
const columns = [
{

View File

@@ -1,11 +1,13 @@
import { useMutation } from "@apollo/client";
import { Button, Form, notification, DatePicker } from "antd";
import { Button, Form, notification } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import moment from "moment";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
export default function JobsAdminDatesChange({ job }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
@@ -54,7 +56,7 @@ export default function JobsAdminDatesChange({ job }) {
label={t("jobs.fields.date_estimated")}
name="date_estimated"
>
<DatePicker format="MM/DD/YYYY" />
<FormDatePicker format="MM/DD/YYYY" />
</Form.Item>
<Form.Item label={t("jobs.fields.date_open")} name="date_open">
<DateTimePicker />

View File

@@ -1,4 +1,4 @@
import { DatePicker, Form, Statistic, Tooltip } from "antd";
import { Form, Statistic, Tooltip } from "antd";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -34,7 +34,7 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
label={t("jobs.fields.date_estimated")}
name="date_estimated"
>
<DatePicker disabled={jobRO} format="MM/DD/YYYY" />
<FormDatePicker disabled={jobRO} />
</Form.Item>
<Form.Item label={t("jobs.fields.date_open")} name="date_open">
<DateTimePicker disabled={jobRO} />

View File

@@ -21,6 +21,7 @@ import ProductionListColumnProductionNote from "../production-list-columns/produ
import "./jobs-detail-header.styles.scss";
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly,
@@ -86,6 +87,12 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
) : null}
</Space>
</DataLabel>
<DataLabel
label={t("jobs.fields.comment")}
valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}
>
<ProductionListColumnComment record={job} />
</DataLabel>
<DataLabel label={t("jobs.fields.ins_co_nm_short")}>
{job.ins_co_nm}
</DataLabel>

View File

@@ -36,7 +36,7 @@ export function JobsDocumentsDownloadButton({
);
const imagesToDownload = [
...galleryImages.images.filter((image) => image.isSelected),
// ...galleryImages.other.filter((image) => image.isSelected),
...galleryImages.other.filter((image) => image.isSelected),
];
function downloadProgress(progressEvent) {
@@ -123,6 +123,7 @@ export function JobsDocumentsDownloadButton({
a.click();
}
};
return (
<>
<Button

View File

@@ -30,7 +30,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
width: "8%",
sorter: true, //(a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder,
@@ -47,7 +47,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
key: "ownr_ln",
ellipsis: true,
//sorter: true, // (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
width: "25%",
//sortOrder: sortcolumn === "ownr_ln" && sortorder,
render: (text, record) => {
return record.ownerid ? (
@@ -67,7 +67,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.ownr_ph1"),
dataIndex: "ownr_ph1",
key: "ownr_ph1",
width: "12%",
ellipsis: true,
render: (text, record) => (
<StartChatButton phone={record.ownr_ph1} jobid={record.id} />
@@ -77,7 +77,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
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} />
@@ -87,7 +87,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.status"),
dataIndex: "status",
key: "status",
width: "10%",
ellipsis: true,
sorter: true, // (a, b) => alphaSort(a.status, b.status),
sortOrder: sortcolumn === "status" && sortorder,
@@ -104,7 +104,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.vehicle"),
dataIndex: "vehicle",
key: "vehicle",
width: "15%",
ellipsis: true,
render: (text, record) => {
return record.vehicleid ? (
@@ -124,7 +124,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("vehicles.fields.plate_no"),
dataIndex: "plate_no",
key: "plate_no",
width: "8%",
ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.plate_no, b.plate_no),
sortOrder: sortcolumn === "plate_no" && sortorder,
@@ -136,7 +136,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.clm_no"),
dataIndex: "clm_no",
key: "clm_no",
width: "12%",
ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder: sortcolumn === "clm_no" && sortorder,
@@ -155,7 +155,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.clm_total"),
dataIndex: "clm_total",
key: "clm_total",
width: "10%",
sorter: true, //(a, b) => a.clm_total - b.clm_total,
sortOrder: sortcolumn === "clm_total" && sortorder,
render: (text, record) => {
@@ -170,11 +170,17 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
title: t("jobs.fields.owner_owing"),
dataIndex: "owner_owing",
key: "owner_owing",
width: "8%",
render: (text, record) => (
<CurrencyFormatter>{record.owner_owing}</CurrencyFormatter>
),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
},
];
const handleTableChange = (pagination, filters, sorter) => {
@@ -224,7 +230,6 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
>
<Table
loading={loading}
scroll={{ x: true }}
pagination={{
position: "top",
pageSize: 25,

View File

@@ -60,6 +60,9 @@ export function JobsList({ bodyshop }) {
(j.ownr_co_nm || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.comments || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(j.ownr_fn || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
@@ -262,6 +265,13 @@ export function JobsList({ bodyshop }) {
<CurrencyFormatter>{record.clm_total}</CurrencyFormatter>
),
},
{
title: t("jobs.fields.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
responsive: ["md"],
},
// {
// title: t("jobs.fields.owner_owing"),
// dataIndex: "owner_owing",

View File

@@ -6,9 +6,11 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setPartnerVersion } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -19,26 +21,35 @@ export default connect(
mapDispatchToProps
)(PartnerPingComponent);
export function PartnerPingComponent({ setPartnerVersion }) {
export function PartnerPingComponent({ bodyshop, setPartnerVersion }) {
const { t } = useTranslation();
useEffect(() => {
// Create an scoped async function in the hook
async function checkPartnerStatus() {
if (!bodyshop) return;
try {
//if (process.env.NODE_ENV === "development") return;
const PartnerResponse = await axios.post("http://localhost:1337/ping/");
const { appver, qbpath } = PartnerResponse.data;
setPartnerVersion(appver);
console.log({ appver, qbpath });
if (!qbpath) {
if (
!qbpath &&
!(
bodyshop &&
(bodyshop.cdk_dealerid ||
bodyshop.pbs_serialnumber ||
bodyshop.accountingconfig.qbo)
)
) {
notification["error"]({
title: "",
message: t("general.messages.noacctfilepath"),
});
}
} catch (error) {
console.log(error);
notification["error"]({
title: "",
message: t("general.messages.partnernotrunning"),
@@ -48,7 +59,7 @@ export function PartnerPingComponent({ setPartnerVersion }) {
// Execute the created function directly
checkPartnerStatus();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
}, [bodyshop]);
return <></>;
}

View File

@@ -319,6 +319,15 @@ export function PartsOrderListTableComponent({
},
]
: []),
{
title: t("parts_orders.fields.part_type"),
dataIndex: "part_type",
key: "part_type",
render: (text, record) =>
record.part_type
? t(`joblines.fields.part_types.${record.part_type}`)
: null,
},
{
title: t("parts_orders.fields.oem_partno"),
dataIndex: "oem_partno",

View File

@@ -1,6 +1,15 @@
import { DeleteFilled, WarningFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Divider, Form, Input, InputNumber, Radio, Space, Tag } from "antd";
import {
Divider,
Form,
Input,
InputNumber,
Radio,
Space,
Tag,
Select,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -37,6 +46,11 @@ export function PartsOrderModalComponent({
{},
bodyshop.imexshopid
);
const { OEConnection_PriceChange } = useTreatments(
["OEConnection_PriceChange"],
{},
bodyshop.imexshopid
);
const { t } = useTranslation();
return (
@@ -91,68 +105,122 @@ export function PartsOrderModalComponent({
<div>
{fields.map((field, index) => (
<Form.Item required={false} key={field.key}>
<LayoutFormRow grow noDivider>
<Form.Item
//span={8}
label={t("parts_orders.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.line_remarks")}
key={`${index}line_remarks`}
name={[field.name, "line_remarks"]}
>
<Input />
</Form.Item>
{
// <Form.Item
// label={t("parts_orders.fields.db_price")}
// key={`${index}db_price`}
// name={[field.name, "db_price"]}
// >
// <CurrencyInput />
// </Form.Item>
}
<Form.Item
label={t("parts_orders.fields.quantity")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.act_price")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<CurrencyInput />
</Form.Item>
{isReturn && (
<div style={{ display: "flex" }}>
<LayoutFormRow grow noDivider style={{ flex: 1 }}>
<Form.Item
label={t("parts_orders.fields.cost")}
key={`${index}cost`}
name={[field.name, "cost"]}
//span={8}
label={t("parts_orders.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.line_remarks")}
key={`${index}line_remarks`}
name={[field.name, "line_remarks"]}
>
<Input />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.part_type")}
key={`${index}part_type`}
name={[field.name, "part_type"]}
>
<Select
disabled={
!(
sendType === "oec" &&
OEConnection_PriceChange.treatment === "on"
)
}
>
<Select.Option value="PAA">
{t("joblines.fields.part_types.PAA")}
</Select.Option>
<Select.Option value="PAC">
{t("joblines.fields.part_types.PAC")}
</Select.Option>
<Select.Option value="PAL">
{t("joblines.fields.part_types.PAL")}
</Select.Option>
<Select.Option value="PAG">
{t("joblines.fields.part_types.PAG")}
</Select.Option>
<Select.Option value="PAM">
{t("joblines.fields.part_types.PAM")}
</Select.Option>
<Select.Option value="PAP">
{t("joblines.fields.part_types.PAP")}
</Select.Option>
<Select.Option value="PAN">
{t("joblines.fields.part_types.PAN")}
</Select.Option>
<Select.Option value="PAO">
{t("joblines.fields.part_types.PAO")}
</Select.Option>
<Select.Option value="PAR">
{t("joblines.fields.part_types.PAR")}
</Select.Option>
<Select.Option value="PAS">
{t("joblines.fields.part_types.PAS")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
label={t("parts_orders.fields.oem_partno")}
key={`${index}oem_partno`}
name={[field.name, "oem_partno"]}
>
<Input />
</Form.Item>
{
// <Form.Item
// label={t("parts_orders.fields.db_price")}
// key={`${index}db_price`}
// name={[field.name, "db_price"]}
// >
// <CurrencyInput />
// </Form.Item>
}
<Form.Item
label={t("parts_orders.fields.quantity")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber />
</Form.Item>
<Form.Item
label={t("parts_orders.fields.act_price")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<CurrencyInput />
</Form.Item>
)}
<Space wrap align="center">
{isReturn && (
<Form.Item
label={t("parts_orders.fields.cost")}
key={`${index}cost`}
name={[field.name, "cost"]}
>
<CurrencyInput />
</Form.Item>
)}
</LayoutFormRow>
<Space wrap size="small" align="center">
<div>
<DeleteFilled
style={{ margin: "1rem" }}
@@ -167,7 +235,7 @@ export function PartsOrderModalComponent({
total={fields.length}
/>
</Space>
</LayoutFormRow>
</div>
</Form.Item>
))}
</div>
@@ -184,7 +252,7 @@ export function PartsOrderModalComponent({
<Radio value={"none"}>{t("general.labels.none")}</Radio>
<Radio value={"e"}>{t("parts_orders.labels.email")}</Radio>
<Radio value={"p"}>{t("parts_orders.labels.print")}</Radio>
{OEConnection.treatment === "on" && (
{OEConnection.treatment === "on" && !isReturn && (
<Radio value={"oec"}>{t("parts_orders.labels.oec")}</Radio>
)}
</Radio.Group>

View File

@@ -30,6 +30,8 @@ import AlertComponent from "../alert/alert.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import PartsOrderModalComponent from "./parts-order-modal.component";
import axios from "axios";
import { useTreatments } from "@splitsoftware/splitio-react";
import _ from "lodash";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -57,6 +59,11 @@ export function PartsOrderModalContainer({
}) {
const { t } = useTranslation();
const client = useApolloClient();
const { OEConnection_PriceChange } = useTreatments(
["OEConnection_PriceChange"],
{},
bodyshop.imexshopid
);
const { visible, context, actions } = partsOrderModal;
const {
jobId,
@@ -70,7 +77,7 @@ export function PartsOrderModalContainer({
const { refetch } = actions;
const [form] = Form.useForm();
const [saving, setSaving] = useState(false);
const sendTypeState = useState("e");
const sendType = sendTypeState[0];
@@ -86,7 +93,7 @@ export function PartsOrderModalContainer({
const handleFinish = async (values) => {
logImEXEvent("parts_order_insert");
setSaving(true);
const insertResult = await insertPartOrder({
variables: {
po: [
@@ -230,11 +237,20 @@ export function PartsOrderModalContainer({
id: insertResult.data.insert_parts_orders.returning[0].id,
},
});
let po;
//Massage the data based on the split. Should they be able to overwrite OEC pricing?
if (OEConnection_PriceChange.treatment === "on") {
//Set the flag to include the override.
po = _.cloneDeep(partsOrder.data.parts_orders_by_pk);
po.parts_order_lines.forEach((pol) => {
pol.priceChange = true;
});
}
const oecResponse = await axios.post(
"http://localhost:1337/oec/",
partsOrder.data.parts_orders_by_pk,
po || partsOrder.data.parts_orders_by_pk,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
@@ -257,11 +273,11 @@ export function PartsOrderModalContainer({
error: JSON.stringify(error.message),
}),
});
setSaving(false);
return;
}
}
setSaving(false);
toggleModalVisible();
};
@@ -283,6 +299,7 @@ export function PartsOrderModalContainer({
cost: value.cost,
quantity: value.part_qty,
job_line_id: isReturn ? value.joblineid : value.id,
part_type: value.part_type,
});
return acc;
}, [])
@@ -306,6 +323,8 @@ export function PartsOrderModalContainer({
}
onCancel={() => toggleModalVisible()}
onOk={() => form.submit()}
okButtonProps={{ loading: saving }}
cancelButtonProps={{ loading: saving }}
destroyOnClose
width="75%"
forceRender

View File

@@ -12,6 +12,7 @@ import ProductionAlert from "../production-list-columns/production-list-columns.
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
import "./production-board-card.styles.scss";
import moment from "moment";
export default function ProductionBoardCard(
technician,
@@ -37,6 +38,15 @@ export default function ProductionBoardCard(
// employee_csr = bodyshop.employees.find((e) => e.id === card.employee_csr);
// }
const pastDueAlert =
!!card.scheduled_completion &&
((moment().isSameOrAfter(moment(card.scheduled_completion), "day") &&
"production-completion-past") ||
(moment()
.add(1, "day")
.isSame(moment(card.scheduled_completion), "day") &&
"production-completion-soon"));
return (
<Card
className="react-kanban-card imex-kanban-card"
@@ -145,7 +155,7 @@ export default function ProductionBoardCard(
cardSettings.scheduled_completion &&
card.scheduled_completion && (
<Col span={cardSettings && cardSettings.compact ? 24 : 12}>
<Space>
<Space className={pastDueAlert}>
<CalendarOutlined />
<DateTimeFormatter format="MM/DD">
{card.scheduled_completion}

View File

@@ -2,7 +2,7 @@ import { useApolloClient } from "@apollo/client";
import Board, { moveCard } from "@asseinfo/react-kanban";
//import "@asseinfo/react-kanban/dist/styles.css";
import "./production-board-kanban.styles.scss";
import { SyncOutlined } from '@ant-design/icons'
import { SyncOutlined } from "@ant-design/icons";
import { Grid, notification, Button, PageHeader, Space, Statistic } from "antd";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -49,9 +49,16 @@ export function ProductionBoardKanbanComponent({
const { t } = useTranslation();
useEffect(() => {
setBoardLanes(
createBoardData(bodyshop.md_ro_statuses.production_statuses, data, filter)
const boardData = createBoardData(
bodyshop.md_ro_statuses.production_statuses,
data,
filter
);
boardData.columns = boardData.columns.map((d) => {
return { ...d, title: `${d.title} (${d.cards.length})` };
});
setBoardLanes(boardData);
setIsMoving(false);
}, [
data,

View File

@@ -0,0 +1,79 @@
import Icon from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, Input, Popover } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { FaRegStickyNote } from "react-icons/fa";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
export default function ProductionListColumnComment({ record }) {
const { t } = useTranslation();
const [note, setNote] = useState(record.comment || "");
const [visible, setVisible] = useState(false);
const [updateAlert] = useMutation(UPDATE_JOB);
const handleSaveNote = (e) => {
e.stopPropagation();
setVisible(false);
updateAlert({
variables: {
jobId: record.id,
job: {
comment: note,
},
},
}).then(() => {
if (record.refetch) record.refetch();
});
};
const handleChange = (e) => {
e.stopPropagation();
setNote(e.target.value);
};
const handleVisibleChange = (flag) => {
setVisible(flag);
if (flag) setNote(record.comment || "");
};
return (
<Popover
onVisibleChange={handleVisibleChange}
visible={visible}
content={
<div style={{ width: "30em" }}>
<Input.TextArea
rows={5}
value={note}
onChange={handleChange}
// onPressEnter={handleSaveNote}
autoFocus
allowClear
/>
<div>
<Button onClick={handleSaveNote}>
{t("general.actions.save")}
</Button>
</div>
</div>
}
trigger={["click"]}
>
<div
style={{
width: "100%",
height: "19px",
cursor: "pointer",
overflow: "hidden",
textOverflow: "ellipsis",
}}
>
<Icon component={FaRegStickyNote} style={{ marginRight: ".2rem" }} />
{record.comment || " "}
</div>
</Popover>
);
}

View File

@@ -18,7 +18,9 @@ import ProductionListLastContacted from "./production-list-columns.lastcontacted
import ProductionListColumnPaintPriority from "./production-list-columns.paintpriority.component";
import ProductionListColumnNote from "./production-list-columns.productionnote.component";
import ProductionListColumnStatus from "./production-list-columns.status.component";
import ProductionListColumnCategory from "./production-list-columns.status.category";
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
import ProductionListColumnComment from "./production-list-columns.comment.component";
const r = ({ technician, state, activeStatuses, bodyshop }) => {
return [
@@ -94,7 +96,7 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
sortOrder:
state.sortedInfo.columnKey === "actual_in" && state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="actual_in" />
<ProductionListDate record={record} field="actual_in" time/>
),
},
{
@@ -108,7 +110,12 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.columnKey === "scheduled_completion" &&
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="scheduled_completion" />
<ProductionListDate
record={record}
field="scheduled_completion"
pastIndicator
time
/>
),
},
{
@@ -155,7 +162,12 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.columnKey === "scheduled_delivery" &&
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="scheduled_delivery" />
<ProductionListDate
record={record}
field="scheduled_delivery"
pastIndicator
time
/>
),
},
{
@@ -251,6 +263,29 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
render: (text, record) => <ProductionListColumnStatus record={record} />,
},
{
title: i18n.t("jobs.fields.category"),
dataIndex: "category",
key: "category",
ellipsis: true,
filters:
(bodyshop &&
bodyshop.md_categories.map((s) => {
return {
text: s,
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.category),
sorter: (a, b) => alphaSort(a.category, b.category),
sortOrder:
state.sortedInfo.columnKey === "category" && state.sortedInfo.order,
render: (text, record) => (
<ProductionListColumnCategory record={record} />
),
},
{
title: i18n.t("production.labels.bodyhours"),
dataIndex: "labhrs",
@@ -301,6 +336,13 @@ const r = ({ technician, state, activeStatuses, bodyshop }) => {
ellipsis: true,
render: (text, record) => <ProductionListColumnNote record={record} />,
},
{
title: i18n.t("production.labels.comment"),
dataIndex: "comment",
key: "comment",
ellipsis: true,
render: (text, record) => <ProductionListColumnComment record={record} />,
},
{
title: i18n.t("production.labels.touchtime"),
dataIndex: "tt",

View File

@@ -1,22 +1,28 @@
import { useMutation } from "@apollo/client";
import { DatePicker, Dropdown, TimePicker, Button, Card } from "antd";
import { Button, Card, Dropdown, TimePicker } from "antd";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { DateFormatter } from "../../utils/DateFormatter";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import { useTranslation } from "react-i18next";
const OneCalendarDay = 60 * 60 * 24 * 1000;
const Now = new Date();
export default function ProductionListDate({ record, field, time }) {
export default function ProductionListDate({
record,
field,
time,
pastIndicator,
}) {
const [updateAlert] = useMutation(UPDATE_JOB);
const [visible, setVisible] = useState(false);
const { t } = useTranslation();
const handleChange = (date) => {
logImEXEvent("product_toggle_date", { field });
// if (date.isSame(record[field] && moment(record[field]))) {
// return;
// }
//e.stopPropagation();
updateAlert({
@@ -34,6 +40,15 @@ export default function ProductionListDate({ record, field, time }) {
});
};
let className = "";
if (pastIndicator) {
className =
!!record[field] &&
((moment().isSameOrAfter(moment(record[field]), "day") &&
"production-completion-past") ||
(moment().add(1, "day").isSame(moment(record[field]), "day") &&
"production-completion-soon"));
}
return (
<div>
<Dropdown
@@ -47,17 +62,19 @@ export default function ProductionListDate({ record, field, time }) {
style={{ padding: "1rem" }}
onClick={(e) => e.stopPropagation()}
>
<DatePicker
<FormDatePicker
onClick={(e) => e.stopPropagation()}
value={(record[field] && moment(record[field])) || null}
onChange={handleChange}
format="MM/DD/YYYY"
isDateOnly={!time}
/>
{time && (
<TimePicker
onClick={(e) => e.stopPropagation()}
value={(record[field] && moment(record[field])) || null}
onChange={handleChange}
minuteStep={15}
format="hh:mm a"
/>
)}
@@ -72,17 +89,9 @@ export default function ProductionListDate({ record, field, time }) {
style={{
height: "19px",
}}
className={className}
>
<DateFormatter
bordered={false}
className={
!!record[field] && new Date(record[field]) - Now < OneCalendarDay
? "production-completion-1"
: ""
}
>
{record[field]}
</DateFormatter>
<DateFormatter bordered={false}>{record[field]}</DateFormatter>
</div>
</Dropdown>
</div>

View File

@@ -0,0 +1,63 @@
import { useMutation } from "@apollo/client";
import { Dropdown, Menu, Spin } from "antd";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function ProductionListColumnCategory({ record, bodyshop }) {
const [updateJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false);
const handleSetStatus = async (e) => {
logImEXEvent("production_change_status");
setLoading(true);
const { key } = e;
await updateJob({
variables: {
jobId: record.id,
job: {
category: key,
},
},
});
setLoading(false);
};
return (
<Dropdown
overlay={
<Menu
style={{ maxHeight: "200px", overflowY: "auto" }}
onClick={handleSetStatus}
>
{bodyshop.md_categories.map((item) => (
<Menu.Item key={item}>{item}</Menu.Item>
))}
</Menu>
}
trigger={["click"]}
>
<div style={{ width: "100%", height: "19px", cursor: "pointer" }}>
{record.category}
{loading && <Spin />}
</div>
</Dropdown>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListColumnCategory);

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import { Descriptions, Drawer, Space } from "antd";
import { Descriptions, Drawer, Space, PageHeader, Button } from "antd";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -16,8 +16,25 @@ import JobEmployeeAssignments from "../job-employee-assignments/job-employee-ass
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import ProductionRemoveButton from "../production-remove-button/production-remove-button.component";
import JobAtChange from "../job-at-change/job-at-change.component";
import { PrinterFilled } from "@ant-design/icons";
export default function ProductionListDetail({ jobs }) {
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "printCenter" })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListDetail);
export function ProductionListDetail({ jobs, setPrintCenterContext }) {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const { selected } = search;
@@ -39,11 +56,29 @@ export default function ProductionListDetail({ jobs }) {
return (
<Drawer
title={
<Space>
<span>{t("production.labels.jobdetail")}</span>
<span>{theJob.ro_number}</span>
<ProductionRemoveButton jobId={theJob.id} />
</Space>
<PageHeader
title={theJob.ro_number}
extra={
<Space>
<ProductionRemoveButton jobId={theJob.id} />{" "}
<Button
onClick={() => {
setPrintCenterContext({
actions: { refetch: refetch },
context: {
id: theJob.id,
job: theJob,
type: "job",
},
});
}}
>
<PrinterFilled />
{t("jobs.actions.printCenter")}
</Button>
</Space>
}
/>
}
placement="right"
width={"33%"}

View File

@@ -4,9 +4,25 @@ import { TemplateList } from "../../utils/TemplateConstants";
import { useTranslation } from "react-i18next";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const ProdTemplates = TemplateList("production");
const { production_by_technician_one, production_by_category_one } =
TemplateList("special");
export default function ProductionListPrint() {
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListPrint);
export function ProductionListPrint({ bodyshop }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
return (
@@ -33,6 +49,52 @@ export default function ProductionListPrint() {
{ProdTemplates[key].title}
</Menu.Item>
))}
<Menu.SubMenu
title={t("reportcenter.templates.production_by_technician_one")}
>
{bodyshop.employees.map((e) => (
<Menu.Item
key={e.id}
onClick={async () => {
setLoading(true);
await GenerateDocument(
{
name: production_by_technician_one.key,
variables: { id: e.id },
},
{},
"p"
);
setLoading(false);
}}
>
{e.first_name} {e.last_name}
</Menu.Item>
))}
</Menu.SubMenu>
<Menu.SubMenu
title={t("reportcenter.templates.production_by_category_one")}
>
{bodyshop.md_categories.map((e) => (
<Menu.Item
key={e}
onClick={async () => {
setLoading(true);
await GenerateDocument(
{
name: production_by_category_one.key,
variables: { category: e },
},
{},
"p"
);
setLoading(false);
}}
>
{e}
</Menu.Item>
))}
</Menu.SubMenu>
</Menu>
}
>

View File

@@ -88,6 +88,12 @@ export function ProductionListTable({
);
const handleTableChange = (pagination, filters, sorter) => {
console.log(
"🚀 ~ file: production-list-table.component.jsx ~ line 91 ~ pagination, filters, sorter",
pagination,
filters,
sorter
);
setState({
...state,
filteredInfo: filters,
@@ -265,6 +271,7 @@ export function ProductionListTable({
columns={columns.map((c, index) => {
return {
...c,
filteredValue: state.filteredInfo[c.key] || null,
sortOrder:
state.sortedInfo.columnKey === c.key && state.sortedInfo.order,
title: headerItem(c),

View File

@@ -25,15 +25,20 @@ export function ScheduleCalendarHeaderGraph({ bodyshop, loadData }) {
const { ssbuckets } = bodyshop;
const data = useMemo(() => {
return Object.keys(loadData.expectedLoad).map((key) => {
const metadataBucket = ssbuckets.filter((b) => b.id === key)[0];
return (
(loadData &&
loadData.expectedLoad &&
Object.keys(loadData.expectedLoad).map((key) => {
const metadataBucket = ssbuckets.filter((b) => b.id === key)[0];
return {
bucket: loadData.expectedLoad[key].label,
current: loadData.expectedLoad[key].count,
target: metadataBucket && metadataBucket.target,
};
});
return {
bucket: loadData.expectedLoad[key].label,
current: loadData.expectedLoad[key].count,
target: metadataBucket && metadataBucket.target,
};
})) ||
[]
);
}, [loadData, ssbuckets]);
const popContent = (

View File

@@ -65,6 +65,7 @@ export function ScheduleCalendarContainer({ calculateScheduleLoad }) {
color: "red",
start: moment(e.start).startOf("day").toDate(),
end: moment(e.end).startOf("day").toDate(),
allDay: true,
vacation: true,
};
}),

View File

@@ -1,6 +1,8 @@
import { CalendarOutlined } from "@ant-design/icons";
import { Card, Col, Row, Statistic } from "antd";
import React from "react";
import _ from "lodash";
import moment from "moment";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -16,25 +18,78 @@ const mapDispatchToProps = (dispatch) => ({
});
const rowGutter = [16, 16];
const statSpans = { xs: 24, sm: 6 };
const statSpans = { xs: 24, sm: 3 };
export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
const { t } = useTranslation();
const values = useMemo(() => {
const dateHash = _.groupBy(scoreBoardlist, "date");
console.log(
"🚀 ~ file: scoreboard-targets-table.component.jsx ~ line 31 ~ values ~ dateHash",
dateHash
);
let ret = {
todayBody: 0,
todayPaint: 0,
weeklyPaint: 0,
weeklyBody: 0,
toDateBody: 0,
toDatePaint: 0,
};
const today = moment();
if (dateHash[today.format("YYYY-MM-DD")]) {
dateHash[today.format("YYYY-MM-DD")].forEach((d) => {
ret.todayBody = ret.todayBody + d.bodyhrs;
ret.todayPaint = ret.todayPaint + d.painthrs;
});
}
let StartOfWeek = moment().startOf("week");
while (StartOfWeek.isSameOrBefore(today)) {
if (dateHash[StartOfWeek.format("YYYY-MM-DD")]) {
dateHash[StartOfWeek.format("YYYY-MM-DD")].forEach((d) => {
ret.weeklyBody = ret.weeklyBody + d.bodyhrs;
ret.weeklyPaint = ret.weeklyPaint + d.painthrs;
});
}
StartOfWeek = StartOfWeek.add(1, "day");
}
let startOfMonth = moment().startOf("month");
while (startOfMonth.isSameOrBefore(today)) {
if (dateHash[startOfMonth.format("YYYY-MM-DD")]) {
dateHash[startOfMonth.format("YYYY-MM-DD")].forEach((d) => {
ret.toDateBody = ret.toDateBody + d.bodyhrs;
ret.toDatePaint = ret.toDatePaint + d.painthrs;
});
}
startOfMonth = startOfMonth.add(1, "day");
}
return ret;
}, [scoreBoardlist]);
console.log(
"🚀 ~ file: scoreboard-targets-table.component.jsx ~ line 51 ~ values ~ values",
values
);
return (
<Card
title={t("scoreboard.labels.targets")}
extra={<ScoreboardJobsList scoreBoardlist={scoreBoardlist} />}
>
<Row gutter={rowGutter}>
<Col xs={24} sm={{ offset: 0, span: 4 }} lg={{ offset: 5, span: 4 }}>
<Col xs={24} sm={{ offset: 0, span: 4 }} lg={{ span: 4 }}>
<Statistic
title={t("scoreboard.labels.workingdays")}
value={Util.CalculateWorkingDaysThisMonth()}
prefix={<CalendarOutlined />}
/>
</Col>
<Col xs={24} sm={{ offset: 0, span: 20 }} lg={{ offset: 0, span: 13 }}>
<Col xs={24} sm={{ offset: 0, span: 20 }} lg={{ offset: 0, span: 20 }}>
<Row>
<Col {...statSpans}>
<Statistic
@@ -43,6 +98,12 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
prefix="B"
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.dailyactual")}
value={values.todayBody.toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.weeklytarget")}
@@ -52,6 +113,12 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.weeklyactual")}
value={values.weeklyBody.toFixed(1)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.monthlytarget")}
@@ -70,6 +137,12 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
)}
/>
</Col>
<Col {...statSpans}>
<Statistic
title={t("scoreboard.labels.todateactual")}
value={values.toDateBody.toFixed(1)}
/>
</Col>
</Row>
<Row>
<Col {...statSpans}>
@@ -78,6 +151,9 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
prefix="P"
/>
</Col>
<Col {...statSpans}>
<Statistic value={values.todayPaint.toFixed(1)} />
</Col>
<Col {...statSpans}>
<Statistic
value={Util.WeeklyTargetHrs(
@@ -86,6 +162,9 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
)}
/>
</Col>
<Col {...statSpans}>
<Statistic value={values.weeklyPaint.toFixed(1)} />
</Col>
<Col {...statSpans}>
<Statistic
value={Util.MonthlyTargetHrs(
@@ -102,6 +181,9 @@ export function ScoreboardTargetsTable({ bodyshop, scoreBoardlist }) {
)}
/>
</Col>
<Col {...statSpans}>
<Statistic value={values.toDatePaint.toFixed(1)} />
</Col>
</Row>
</Col>
</Row>

View File

@@ -7,6 +7,8 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import ShopEmployeesFormComponent from "./shop-employees-form.component";
import ShopEmployeesListComponent from "./shop-employees-list.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -21,11 +23,13 @@ function ShopEmployeesContainer({ bodyshop }) {
return (
<div>
<ShopEmployeesListComponent
employees={data ? data.employees : []}
loading={loading}
/>
<ShopEmployeesFormComponent />
<RbacWrapper action="employees:page">
<ShopEmployeesListComponent
employees={data ? data.employees : []}
loading={loading}
/>
<ShopEmployeesFormComponent />
</RbacWrapper>
</div>
);
}

View File

@@ -19,6 +19,9 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import FormItemEmail from "../form-items-formatted/email-form-item.component";
import momentTZ from "moment-timezone";
const timeZonesList = momentTZ.tz.names();
export default function ShopInfoGeneral({ form }) {
const { t } = useTranslation();
return (
@@ -84,6 +87,7 @@ export default function ShopInfoGeneral({ form }) {
<Form.Item label={t("bodyshop.fields.email")} name="email">
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.phone")}
name="phone"
@@ -97,6 +101,23 @@ export default function ShopInfoGeneral({ form }) {
<Form.Item label={t("bodyshop.fields.website")} name="website">
<Input />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.timezone")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name="timezone"
>
<Select
showSearch
options={timeZonesList.map((z) => {
return { label: z, value: z };
})}
/>
</Form.Item>
<Form.Item
label={t("bodyshop.fields.insurance_vendor_id")}
name="insurance_vendor_id"

View File

@@ -75,9 +75,17 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
<div>
{(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
<>
<DataLabel label={t("bodyshop.labels.dms.cdk_dealerid")}>
{form.getFieldValue("cdk_dealerid")}
</DataLabel>
{bodyshop.cdk_dealerid && (
<DataLabel label={t("bodyshop.labels.dms.cdk_dealerid")}>
{form.getFieldValue("cdk_dealerid")}
</DataLabel>
)}
{bodyshop.pbs_serialnumber && (
<DataLabel label={t("bodyshop.labels.dms.pbs_serialnumber")}>
{form.getFieldValue("pbs_serialnumber")}
</DataLabel>
)}
<LayoutFormRow>
<Form.Item
label={t("bodyshop.fields.dms.default_journal")}
@@ -121,6 +129,15 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
>
<Input />
</Form.Item>
{bodyshop.pbs_serialnumber && (
<Form.Item
label={t("bodyshop.fields.dms.disablecontactvehiclecreation")}
valuePropName="checked"
name={["pbs_configuration", "disablecontactvehicle"]}
>
<Switch />
</Form.Item>
)}
</LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.dms.cdk.payers")}>
<Form.List name={["cdk_configuration", "payers"]}>
@@ -158,11 +175,11 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
label={t("jobs.fields.dms.payer.control_type")}
key={`${index}control_type`}
name={[field.name, "control_type"]}
rules={[
{
required: true,
},
]}
// rules={[
// {
// required: true,
// },
// ]}
>
<Select showSearch>
<Select.Option value="ro_number">

View File

@@ -49,9 +49,13 @@ export function TechClockInComponent({ form, bodyshop, technician }) {
<Select>
{emps &&
emps.rates.map((item) => (
<Select.Option key={item.cost_center}>
<Select.Option key={item.cost_center} value={item.cost_center}>
{item.cost_center === "timetickets.labels.shift"
? t(item.cost_center)
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? t(
`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`
)
: item.cost_center}
</Select.Option>
))}

View File

@@ -1,5 +1,5 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Form, notification } from "antd";
import { Button, Card, Form, notification, Space } from "antd";
import axios from "axios";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -9,6 +9,7 @@ import { INSERT_NEW_TIME_TICKET } from "../../graphql/timetickets.queries";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import TechClockInComponent from "./tech-job-clock-in-form.component";
import TechJobPrintTickets from "../tech-job-print-tickets/tech-job-print-tickets.component";
const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
@@ -36,14 +37,17 @@ export function TechClockInContainer({ technician, bodyshop }) {
clockon: theTime,
jobid: values.jobid,
cost_center: values.cost_center,
ciecacode: Object.keys(
bodyshop.md_responsibility_centers.defaults.costs
).find((key) => {
return (
bodyshop.md_responsibility_centers.defaults.costs[key] ===
values.cost_center
);
}),
ciecacode:
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? values.cost_center
: Object.keys(
bodyshop.md_responsibility_centers.defaults.costs
).find((key) => {
return (
bodyshop.md_responsibility_centers.defaults.costs[key] ===
values.cost_center
);
}),
},
],
},
@@ -68,9 +72,16 @@ export function TechClockInContainer({ technician, bodyshop }) {
<Card
title={t("timetickets.labels.clockintojob")}
extra={
<Button type="primary" onClick={() => form.submit()} loading={loading}>
{t("timetickets.actions.clockin")}
</Button>
<Space wrap>
<TechJobPrintTickets />
<Button
type="primary"
onClick={() => form.submit()}
loading={loading}
>
{t("timetickets.actions.clockin")}
</Button>
</Space>
}
>
<Form form={form} layout="vertical" onFinish={handleFinish}>

View File

@@ -55,6 +55,20 @@ export function TechClockOffButton({
timeticket: {
clockoff: (await axios.post("/utils/time")).data,
...values,
rate: emps && emps.rates.filter(
(r) => r.cost_center === values.cost_center
)[0]?.rate,
ciecacode:
bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? values.cost_center
: Object.keys(
bodyshop.md_responsibility_centers.defaults.costs
).find((key) => {
return (
bodyshop.md_responsibility_centers.defaults.costs[key] ===
values.cost_center
);
}),
},
},
});
@@ -141,6 +155,10 @@ export function TechClockOffButton({
<Select.Option key={item.cost_center}>
{item.cost_center === "timetickets.labels.shift"
? t(item.cost_center)
: bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? t(
`joblines.fields.lbr_types.${item.cost_center.toUpperCase()}`
)
: item.cost_center}
</Select.Option>
))

View File

@@ -0,0 +1,125 @@
import { Button, Card, DatePicker, Form, Popover, Space } from "antd";
import moment from "moment";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import DatePIckerRanges from "../../utils/DatePickerRanges";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({
technician: selectTechnician,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(TechJobPrintTickets);
export function TechJobPrintTickets({ technician, event }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const [visibility, setVisibility] = useState(false);
useEffect(() => {
if (visibility && event) {
form.setFieldsValue(event);
}
}, [visibility, form, event]);
const handleFinish = async (values) => {
logImEXEvent("schedule_manual_event");
setLoading(true);
const start = values.dates[0];
const end = values.dates[1];
try {
await GenerateDocument(
{
name: TemplateList().timetickets_employee.key,
variables: {
...(start
? { start: moment(start).startOf("day").format("YYYY-MM-DD") }
: {}),
...(end
? { end: moment(end).endOf("day").format("YYYY-MM-DD") }
: {}),
...(start ? { starttz: moment(start).startOf("day") } : {}),
...(end ? { endtz: moment(end).endOf("day") } : {}),
id: technician.id,
},
},
{
to: technician.email,
subject: TemplateList().timetickets_employee.subject,
},
"p"
);
} catch (error) {
console.log(error);
} finally {
setLoading(false);
setVisibility(false);
form.resetFields();
}
};
const overlay = (
<Card>
<div>
<Form form={form} layout="vertical" onFinish={handleFinish}>
<Form.Item
label={t("reportcenter.labels.dates")}
name="dates"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<DatePicker.RangePicker
ranges={DatePIckerRanges}
format={"MM/DD/YYYY"}
/>
</Form.Item>
<Space wrap>
<Button type="primary" onClick={() => form.submit()}>
{t("general.actions.print")}
</Button>
<Button
onClick={() => {
setVisibility(false);
form.resetFields();
}}
>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</div>
</Card>
);
const handleClick = (e) => {
setVisibility(true);
};
return (
<Popover content={overlay} visible={visibility}>
<Button loading={loading} onClick={handleClick}>
{t("general.actions.print")}
</Button>
</Popover>
);
}

View File

@@ -76,6 +76,21 @@ export function TimeTicketList({
state.sortedInfo.columnKey === "employee" && state.sortedInfo.order,
render: (text, record) =>
`${record.employee.first_name} ${record.employee.last_name}`,
filters:
timetickets
.map((l) => l.employeeid)
.filter(onlyUnique)
.map((s) => {
return {
text: (() => {
const emp = bodyshop.employees.find((e) => e.id === s);
return `${emp.first_name} ${emp.last_name}`;
})(), //
value: [s],
};
}) || [],
onFilter: (value, record) => value.includes(record.employeeid),
},
{
title: t("timetickets.fields.cost_center"),

View File

@@ -159,8 +159,10 @@ export function TimeTicketModalComponent({
name="flat_rate"
label={t("timetickets.fields.flat_rate")}
valuePropName="checked"
noStyle
style={{ display: "none" }}
>
<Switch />
<Switch style={{ display: "none" }} />
</Form.Item>
</LayoutFormRow>
@@ -208,10 +210,11 @@ export function TimeTicketModalComponent({
>
<InputNumber min={0} precision={1} />
</Form.Item>
{isEdit && (
{
<>
<Form.Item label={t("timetickets.fields.clockon")} name="clockon">
<FormDateTimePicker
minuteStep={5}
disabled={
!HasRbacAccess({
bodyshop,
@@ -221,8 +224,31 @@ export function TimeTicketModalComponent({
}
/>
</Form.Item>
<Form.Item label={t("timetickets.fields.clockoff")} name="clockoff">
<Form.Item
label={t("timetickets.fields.clockoff")}
name="clockoff"
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
const clockon = getFieldValue("clockon");
if (!value) return Promise.resolve();
if (!clockon && value)
return Promise.reject(
t("timetickets.validation.clockoffwithoutclockon")
);
if (!value.isSameOrAfter(clockon))
return Promise.reject(
t("timetickets.validation.clockoffmustbeafterclockon")
);
return Promise.resolve();
},
}),
]}
>
<FormDateTimePicker
minuteStep={5}
disabled={
!HasRbacAccess({
bodyshop,
@@ -233,7 +259,7 @@ export function TimeTicketModalComponent({
/>
</Form.Item>
</>
)}
}
<Form.Item label={t("timetickets.fields.memo")} name="memo">
<MemoInput />

View File

@@ -0,0 +1,41 @@
import { Button } from "antd";
import queryString from "query-string";
import React, { useState } from "react";
import { useLocation } from "react-router-dom";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import { useTranslation } from "react-i18next";
import moment from "moment";
const PayrollTemplate = TemplateList("special").exported_payroll;
export default function TimeTicketsPayrollTable() {
const searchParams = queryString.parse(useLocation().search);
const { start, end } = searchParams;
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const handleClick = async () => {
setLoading(true);
await GenerateDocument(
{
name: PayrollTemplate.key,
variables: {
start: start
? start
: moment().startOf("week").subtract(7, "days").format("YYYY-MM-DD"),
end: end ? end : moment().endOf("week").format("YYYY-MM-DD"),
},
},
{},
"x"
);
setLoading(false);
};
return (
<Button loading={loading} onClick={handleClick}>
{t("printcenter.payments.exported_payroll")}
</Button>
);
}

View File

@@ -32,7 +32,9 @@ export default function VendorsFormComponent({
return (
<div>
<PageHeader
title={form.getFieldValue("name")}
title={
<Form.Item shouldUpdate>{() => form.getFieldValue("name")}</Form.Item>
}
extra={
<Space>
<Form.Item
@@ -156,7 +158,7 @@ export default function VendorsFormComponent({
<InputNumber min={0} max={1} precision={2} step={0.01} />
</Form.Item>
<Form.Item label={t("vendors.fields.due_date")} name="due_date">
<InputNumber />
<InputNumber min={0} />
</Form.Item>
{
// <Form.Item

View File

@@ -84,6 +84,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
line_remarks
quantity
job_line_id
part_type
cost
jobline {
id
@@ -123,6 +124,10 @@ export const QUERY_BILLS_BY_JOBID = gql`
applicable_taxes
deductedfromlbr
lbr_adjustment
jobline{
oem_partno
part_type
}
}
}
}
@@ -159,6 +164,10 @@ export const QUERY_BILL_BY_PK = gql`
cost_center
quantity
joblineid
jobline{
oem_partno
part_type
}
applicable_taxes
deductedfromlbr
lbr_adjustment

View File

@@ -100,6 +100,7 @@ export const QUERY_BODYSHOP = gql`
pbs_serialnumber
md_filehandlers
md_email_cc
timezone
employees {
user_email
id
@@ -197,6 +198,7 @@ export const UPDATE_SHOP = gql`
pbs_serialnumber
md_filehandlers
md_email_cc
timezone
employees {
id
first_name
@@ -262,6 +264,9 @@ export const QUERY_DELIVER_CHECKLIST = gql`
jobs_by_pk(id: $jobId) {
id
ro_number
actual_completion
actual_delivery
}
}
`;

View File

@@ -89,6 +89,9 @@ export const QUERY_ALL_CC = gql`
job {
id
ro_number
ownr_fn
ownr_ln
ownr_co_nm
}
}
}

View File

@@ -181,7 +181,10 @@ export const UPDATE_JOB_LINE = gql`
export const GET_JOB_LINES_TO_ENTER_BILL = gql`
query GET_JOB_LINES_TO_ENTER_BILL($id: uuid!) {
joblines(where: { jobid: { _eq: $id } }) {
joblines(
where: { jobid: { _eq: $id } }
order_by: { act_price: desc_nulls_last }
) {
removed
id
line_desc

View File

@@ -12,11 +12,7 @@ export const QUERY_ALL_ACTIVE_JOBS = gql`
ownr_ph1
ownr_ph2
ownr_ea
owner {
id
allow_text_message
preferred_contact
}
comment
plate_no
plate_st
v_vin
@@ -121,8 +117,10 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
id
status
ro_number
comment
ownr_fn
ownr_ln
category
ownr_co_nm
v_model_yr
v_model_desc
@@ -192,7 +190,9 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
id
status
ro_number
comment
ownr_fn
category
ownr_ln
ownr_co_nm
v_model_yr
@@ -262,7 +262,9 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
jobs(where: { inproduction: { _eq: true } }) {
id
updated_at
comment
status
category
ro_number
ownr_fn
ownr_ln
@@ -486,6 +488,7 @@ export const GET_JOB_BY_PK = gql`
alt_transport
intakechecklist
invoice_final_note
comment
loss_desc
kmin
kmout
@@ -827,6 +830,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
ownr_co_nm
ownr_ph1
ownr_ph2
comment
ownr_ea
ca_gst_registrant
owner_owing
@@ -1031,7 +1035,7 @@ export const UPDATE_JOB = gql`
update_jobs(where: { id: { _eq: $jobId } }, _set: $job) {
returning {
id
comment
date_exported
status
alt_transport
@@ -1763,13 +1767,12 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
order_by: $order
where: { status: { _in: $statusList } }
) {
comment
ownr_fn
ownr_ln
ownr_co_nm
ownerid
ownr_ph1
ownr_ph2
ownr_ea
plate_no
plate_st
v_vin
@@ -1778,32 +1781,16 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
v_make_desc
v_color
vehicleid
actual_completion
actual_delivery
actual_in
id
ins_co_nm
ins_ct_fn
ins_ct_ln
ins_ph1
ins_ea
est_co_nm
est_ph1
est_ea
est_ct_fn
est_ct_ln
clm_no
clm_total
owner_owing
ro_number
po_number
scheduled_completion
scheduled_in
scheduled_delivery
status
updated_at
ded_amt
vehicleid
}
search_jobs_aggregate(
args: { search: $search }
@@ -1875,6 +1862,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
scheduled_delivery
actual_delivery
scheduled_in
date_invoiced
actual_in
kmin
kmout

View File

@@ -69,6 +69,7 @@ export const QUERY_PARTS_ORDER_OEC = gql`
db_price
line_desc
quantity
part_type
}
job {
bodyshop{

View File

@@ -5,14 +5,14 @@ export const GLOBAL_SEARCH_QUERY = gql`
search_jobs(args: { search: $search }) {
id
ro_number
status
clm_total
clm_no
v_model_yr
v_model_desc
v_make_desc
v_color
plate_no
ownr_fn
ownr_ln
ownr_co_nm

View File

@@ -56,7 +56,7 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
jobId: job.id,
job: {
status: bodyshop.md_ro_statuses.default_invoiced || "",
date_invoiced: new Date(),
date_invoiced: values.date_invoiced,
actual_in: values.actual_in,
actual_completion: values.actual_completion,
actual_delivery: values.actual_delivery,
@@ -119,6 +119,9 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
actual_delivery: job.actual_delivery
? moment(job.actual_delivery)
: job.scheduled_delivery && moment(job.scheduled_delivery),
date_invoiced: job.date_invoiced
? moment(job.date_invoiced)
: moment(),
kmin: job.kmin,
kmout: job.kmout,
dms_allocation: job.dms_allocation,
@@ -219,6 +222,32 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
>
<DateTimePicker disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.date_invoiced")}
name="date_invoiced"
rules={[
{
required: true,
},
({ getFieldValue }) => ({
validator(_, value) {
if (!bodyshop.cdk_dealerid) return Promise.resolve();
if (!value || moment(value).isSameOrAfter(moment(), "day")) {
return Promise.resolve();
}
return Promise.reject(
new Error(t("jobs.labels.dms.invoicedatefuture"))
);
},
}),
]}
>
<DateTimePicker
disabled={jobRO}
onlyFuture={!!bodyshop.cdk_dealerid}
/>
</Form.Item>
{(bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) && (
<Form.Item
label={t("jobs.fields.kmin")}

View File

@@ -69,6 +69,7 @@ export function JobsDeliverContainer({
checklistConfig={
(data && data.bodyshops_by_pk.deliverchecklist) || {}
}
job={data ? data.jobs_by_pk : {}}
/>
</div>
</RbacWrapper>

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import { Col, Row } from "antd";
import { Col, Row, Space } from "antd";
import moment from "moment";
import queryString from "query-string";
import React, { useEffect } from "react";
@@ -11,6 +11,7 @@ import AlertComponent from "../../components/alert/alert.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import TimeTicketsDatesSelector from "../../components/ticket-tickets-dates-selector/time-tickets-dates-selector.component";
import TimeTicketList from "../../components/time-ticket-list/time-ticket-list.component";
import TimeTicketsPayrollTable from "../../components/time-tickets-payroll-table/time-tickets-payroll-table.component";
import TimeTicketsSummaryEmployees from "../../components/time-tickets-summary-employees/time-tickets-summary-employees.component";
import { QUERY_TIME_TICKETS_IN_RANGE } from "../../graphql/timetickets.queries";
import {
@@ -68,7 +69,12 @@ export function TimeTicketsContainer({
<TimeTicketList
loading={loading}
timetickets={data ? data.timetickets : []}
extra={<TimeTicketsDatesSelector />}
extra={
<Space wrap>
<TimeTicketsPayrollTable />
<TimeTicketsDatesSelector />
</Space>
}
/>
</Col>
<Col span={24}>

View File

@@ -41,6 +41,7 @@ import UserActionTypes from "./user.types";
import axios from "axios";
import { messaging } from "../../firebase/firebase.utils";
import { getToken } from "firebase/messaging";
export function* onEmailSignInStart() {
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
}
@@ -266,11 +267,17 @@ export function* onSetShopDetails() {
export function* SetAuthLevelFromShopDetails({ payload }) {
try {
const userEmail = yield select((state) => state.user.currentUser.email);
try {
console.log("Setting shop timezone.");
// moment.tz.setDefault(payload.timezone);
} catch (error) {
console.log(error);
}
factory.client(payload.imexshopid);
const authRecord = payload.associations.filter(
(a) => a.useremail === userEmail
(a) => a.useremail.toLowerCase() === userEmail.toLowerCase()
);
yield put(setAuthlevel(authRecord[0] ? authRecord[0].authlevel : 0));

View File

@@ -21,7 +21,7 @@
"actions": {
"block": "Block Day",
"calculate": "Calculate SMART Dates",
"cancel": "Cancel",
"cancel": "Cancel Appointment",
"intake": "Intake",
"new": "New Appointment",
"preview": "Preview",
@@ -183,6 +183,7 @@
"federal_tax": "Federal Tax",
"iouexists": "An IOU exists that is associated to this RO.",
"local_tax": "Local Tax",
"markexported": "Mark Exported",
"markforreexport": "Mark for Re-export",
"new": "New Bill",
"noneselected": "No bill selected.",
@@ -197,6 +198,7 @@
"created": "Invoice added successfully.",
"deleted": "Bill deleted successfully.",
"exported": "Bill(s) exported successfully.",
"markexported": "Bill marked as exported.",
"reexport": "Bill marked for re-export."
},
"validation": {
@@ -244,6 +246,7 @@
"dms": {
"cashierid": "Cashier ID",
"default_journal": "Default Journal",
"disablecontactvehiclecreation": "Disable Contact & Vehicle Updates/Creation",
"dms_acctnumber": "DMS Account #",
"dms_wip_acctnumber": "DMS W.I.P. Account #",
"generic_customer_number": "Generic Customer Number",
@@ -483,6 +486,7 @@
"production_statuses": "Production Statuses"
},
"target_touchtime": "Target Touch Time",
"timezone": "Timezone",
"tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs",
"use_fippa": "Use FIPPA for Names on Generated Documents?",
"website": "Website",
@@ -1242,6 +1246,7 @@
"class": "Class",
"clm_no": "Claim #",
"clm_total": "Claim Total",
"comment": "Comment",
"customerowing": "Customer Owing",
"date_estimated": "Date Estimated",
"date_exported": "Exported",
@@ -1482,8 +1487,10 @@
"closejob": "Close Job {{ro_number}}",
"contracts": "CC Contracts",
"cost": "Cost",
"cost_Additional": "Cost - Additional",
"cost_labor": "Cost - Labor",
"cost_parts": "Cost - Parts",
"cost_sublet": "Cost - Sublet",
"costs": "Costs",
"create": {
"jobinfo": "Job Info",
@@ -1506,7 +1513,9 @@
"difference": "Difference",
"diskscan": "Scan Disk for Estimates",
"dms": {
"defaultstory": "Bodyshop RO {{ro_number}}. Damage to $t(jobs.fields.area_of_damage_impact.{{area_of_damage}}).",
"damageto": "Damage to $t(jobs.fields.area_of_damage_impact.{{area_of_damage}}).",
"defaultstory": "B/S RO: {{ro_number}}. Owner: {{ownr_nm}}. Insurance Co: {{ins_co_nm}}. Claim/PO #: {{clm_po}}",
"invoicedatefuture": "Invoice date must be today or in the future for CDK posting.",
"kmoutnotgreaterthankmin": "Mileage out must be greater than mileage in.",
"logs": "Logs",
"notallocated": "Not Allocated",
@@ -1551,7 +1560,7 @@
"partstotal": "Parts Total (ex. Taxes)",
"plitooltips": {
"billtotal": "The total amount of all bill lines that have been posted against this RO (not including credits, taxes, or labor adjustments).",
"creditmemos": "The total amount of all credit memos entered. This amount does not reflect any parts returns created.",
"creditmemos": "The total amount of all returns created. This amount does not reflect credit memos that have been posted.",
"creditsnotreceived": "The total amount of returns created for this job that do not have a corresponding credit memo posted. An amount greater than $0 indicates that vendors have not provided requested credit memos.",
"discrep1": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Too many bills/bill lines that have been posted against this RO. Check to make sure every bill posted on this RO is correctly posted and assigned.</li>\n<li>You do not have the latest supplement imported, or, a supplement must be submitted and then imported.</li>\n<li>You have posted a bill line to labor.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this job from reconciling.</i>",
"discrep2": "If the discrepancy is not $0, you may have one of the following: <br/><br/>\n\n<ul>\n<li>Used an incorrect rate when deducting from labor.</li>\n<li>An outstanding imbalance higher in the reconciliation process.</li>\n</ul>\n<br/>\n<i>There may be additional issues not listed above that prevent this job from reconciling.</i>",
@@ -1579,8 +1588,10 @@
"relatedros": "Related ROs",
"returntotals": "Return Totals",
"rosaletotal": "RO Parts Total",
"sale_additional": "Sales - Additional",
"sale_labor": "Sales - Labor",
"sale_parts": "Sales - Parts & Sublet",
"sale_parts": "Sales - Parts",
"sale_sublet": "Sales - Sublet",
"sales": "Sales",
"savebeforeconversion": "You have unsaved changes on the job. Please save them before converting it. ",
"scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the job. ",
@@ -1898,6 +1909,7 @@
"order_date": "Order Date",
"order_number": "Order Number",
"orderedby": "Ordered By",
"part_type": "Type",
"quantity": "Qty.",
"return": "Return",
"status": "Status"
@@ -2107,7 +2119,8 @@
"title": "Print Center"
},
"payments": {
"ca_bc_etf_table": "ICBC ETF Table"
"ca_bc_etf_table": "ICBC ETF Table",
"exported_payroll": "Payroll Table"
},
"subjects": {
"jobs": {
@@ -2148,6 +2161,7 @@
"bodypriority": "B/P",
"cardsettings": "Card Settings",
"clm_no": "Claim Number",
"comment": "Comment",
"compact": "Compact Cards",
"detailpriority": "D/P",
"employeeassignments": "Employee Assignments",
@@ -2219,6 +2233,7 @@
"attendance_summary": "Attendance Summary (All Employees)",
"credits_not_received_date": "Credits not Received by Date",
"csi": "CSI Responses",
"estimates_written_converted": "Estimates Written/Converted",
"estimator_detail": "Jobs by Estimator (Detail)",
"estimator_summary": "Jobs by Estimator (Summary)",
"export_payables": "Export Log - Payables",
@@ -2227,7 +2242,7 @@
"gsr_by_csr": "Gross Sales by CSR",
"gsr_by_delivery_date": "Gross Sales by Delivery Date",
"gsr_by_estimator": "Gross Sales by Estimator",
"gsr_by_exported_date": "Gross Sales by Export Date",
"gsr_by_exported_date": "Exported Gross Sales",
"gsr_by_ins_co": "Gross Sales by Insurance Company",
"gsr_by_make": "Gross Sales by Vehicle Make",
"gsr_by_referral": "Gross Sales by Referral Source",
@@ -2260,12 +2275,15 @@
"parts_not_recieved": "Parts Not Received",
"payments_by_date": "Payments by Date",
"payments_by_date_type": "Payments by Date and Type",
"production_by_category": "Production by Category",
"production_by_category_one": "Production filtered by Category",
"production_by_csr": "Production by CSR",
"production_by_last_name": "Production by Last Name",
"production_by_repair_status": "Production by Status",
"production_by_ro": "Production by RO",
"production_by_target_date": "Production by Target Date",
"production_by_technician": "Production by Technician",
"production_by_technician_one": "Production filtered by Technician",
"purchases_by_cost_center_detail": "Purchases by Cost Center (Detail)",
"purchases_by_cost_center_summary": "Purchases by Cost Center (Summary)",
"purchases_by_date_range_detail": "Purchases by Date - Detail",
@@ -2309,9 +2327,12 @@
},
"labels": {
"asoftodaytarget": "As of Today",
"dailyactual": "Actual (D)",
"dailytarget": "Daily",
"monthlytarget": "Monthly",
"targets": "Targets",
"todateactual": "Actual (MTD)",
"weeklyactual": "Actual (W)",
"weeklytarget": "Weekly",
"workingdays": "Working Days / Month"
},
@@ -2400,6 +2421,10 @@
"clockedout": "Clocked out successfully.",
"created": "Time ticket entered successfully.",
"deleted": "Time ticket deleted successfully."
},
"validation": {
"clockoffmustbeafterclockon": "Clock off time must be the same or after clock in time.",
"clockoffwithoutclockon": "Clock off time cannot be set without a clock in time."
}
},
"titles": {
@@ -2578,7 +2603,7 @@
"country": "Country",
"discount": "Discount % (as decimal)",
"display_name": "Display Name",
"due_date": "Payment Due Date",
"due_date": "Payment Due Date (# of days)",
"email": "Contact Email",
"favorite": "Favorite?",
"lkq": "LKQ",

View File

@@ -183,6 +183,7 @@
"federal_tax": "",
"iouexists": "",
"local_tax": "",
"markexported": "",
"markforreexport": "",
"new": "",
"noneselected": "",
@@ -197,6 +198,7 @@
"created": "",
"deleted": "",
"exported": "",
"markexported": "",
"reexport": ""
},
"validation": {
@@ -244,6 +246,7 @@
"dms": {
"cashierid": "",
"default_journal": "",
"disablecontactvehiclecreation": "",
"dms_acctnumber": "",
"dms_wip_acctnumber": "",
"generic_customer_number": "",
@@ -483,6 +486,7 @@
"production_statuses": ""
},
"target_touchtime": "",
"timezone": "",
"tt_allow_post_to_invoiced": "",
"use_fippa": "",
"website": "",
@@ -1242,6 +1246,7 @@
"class": "",
"clm_no": "Reclamación #",
"clm_total": "Reclamar total",
"comment": "",
"customerowing": "Cliente debido",
"date_estimated": "Fecha estimada",
"date_exported": "Exportado",
@@ -1482,8 +1487,10 @@
"closejob": "",
"contracts": "",
"cost": "",
"cost_Additional": "",
"cost_labor": "",
"cost_parts": "",
"cost_sublet": "",
"costs": "",
"create": {
"jobinfo": "",
@@ -1506,7 +1513,9 @@
"difference": "",
"diskscan": "",
"dms": {
"damageto": "",
"defaultstory": "",
"invoicedatefuture": "",
"kmoutnotgreaterthankmin": "",
"logs": "",
"notallocated": "",
@@ -1579,8 +1588,10 @@
"relatedros": "",
"returntotals": "",
"rosaletotal": "",
"sale_additional": "",
"sale_labor": "",
"sale_parts": "",
"sale_sublet": "",
"sales": "",
"savebeforeconversion": "",
"scheduledinchange": "",
@@ -1898,6 +1909,7 @@
"order_date": "",
"order_number": "",
"orderedby": "",
"part_type": "",
"quantity": "",
"return": "",
"status": ""
@@ -2107,7 +2119,8 @@
"title": ""
},
"payments": {
"ca_bc_etf_table": ""
"ca_bc_etf_table": "",
"exported_payroll": ""
},
"subjects": {
"jobs": {
@@ -2148,6 +2161,7 @@
"bodypriority": "",
"cardsettings": "",
"clm_no": "",
"comment": "",
"compact": "",
"detailpriority": "",
"employeeassignments": "",
@@ -2219,6 +2233,7 @@
"attendance_summary": "",
"credits_not_received_date": "",
"csi": "",
"estimates_written_converted": "",
"estimator_detail": "",
"estimator_summary": "",
"export_payables": "",
@@ -2260,12 +2275,15 @@
"parts_not_recieved": "",
"payments_by_date": "",
"payments_by_date_type": "",
"production_by_category": "",
"production_by_category_one": "",
"production_by_csr": "",
"production_by_last_name": "",
"production_by_repair_status": "",
"production_by_ro": "",
"production_by_target_date": "",
"production_by_technician": "",
"production_by_technician_one": "",
"purchases_by_cost_center_detail": "",
"purchases_by_cost_center_summary": "",
"purchases_by_date_range_detail": "",
@@ -2309,9 +2327,12 @@
},
"labels": {
"asoftodaytarget": "",
"dailyactual": "",
"dailytarget": "",
"monthlytarget": "",
"targets": "",
"todateactual": "",
"weeklyactual": "",
"weeklytarget": "",
"workingdays": ""
},
@@ -2400,6 +2421,10 @@
"clockedout": "",
"created": "",
"deleted": ""
},
"validation": {
"clockoffmustbeafterclockon": "",
"clockoffwithoutclockon": ""
}
},
"titles": {

View File

@@ -183,6 +183,7 @@
"federal_tax": "",
"iouexists": "",
"local_tax": "",
"markexported": "",
"markforreexport": "",
"new": "",
"noneselected": "",
@@ -197,6 +198,7 @@
"created": "",
"deleted": "",
"exported": "",
"markexported": "",
"reexport": ""
},
"validation": {
@@ -244,6 +246,7 @@
"dms": {
"cashierid": "",
"default_journal": "",
"disablecontactvehiclecreation": "",
"dms_acctnumber": "",
"dms_wip_acctnumber": "",
"generic_customer_number": "",
@@ -483,6 +486,7 @@
"production_statuses": ""
},
"target_touchtime": "",
"timezone": "",
"tt_allow_post_to_invoiced": "",
"use_fippa": "",
"website": "",
@@ -1242,6 +1246,7 @@
"class": "",
"clm_no": "Prétendre #",
"clm_total": "Total réclamation",
"comment": "",
"customerowing": "Client propriétaire",
"date_estimated": "Date estimée",
"date_exported": "Exportés",
@@ -1482,8 +1487,10 @@
"closejob": "",
"contracts": "",
"cost": "",
"cost_Additional": "",
"cost_labor": "",
"cost_parts": "",
"cost_sublet": "",
"costs": "",
"create": {
"jobinfo": "",
@@ -1506,7 +1513,9 @@
"difference": "",
"diskscan": "",
"dms": {
"damageto": "",
"defaultstory": "",
"invoicedatefuture": "",
"kmoutnotgreaterthankmin": "",
"logs": "",
"notallocated": "",
@@ -1579,8 +1588,10 @@
"relatedros": "",
"returntotals": "",
"rosaletotal": "",
"sale_additional": "",
"sale_labor": "",
"sale_parts": "",
"sale_sublet": "",
"sales": "",
"savebeforeconversion": "",
"scheduledinchange": "",
@@ -1898,6 +1909,7 @@
"order_date": "",
"order_number": "",
"orderedby": "",
"part_type": "",
"quantity": "",
"return": "",
"status": ""
@@ -2107,7 +2119,8 @@
"title": ""
},
"payments": {
"ca_bc_etf_table": ""
"ca_bc_etf_table": "",
"exported_payroll": ""
},
"subjects": {
"jobs": {
@@ -2148,6 +2161,7 @@
"bodypriority": "",
"cardsettings": "",
"clm_no": "",
"comment": "",
"compact": "",
"detailpriority": "",
"employeeassignments": "",
@@ -2219,6 +2233,7 @@
"attendance_summary": "",
"credits_not_received_date": "",
"csi": "",
"estimates_written_converted": "",
"estimator_detail": "",
"estimator_summary": "",
"export_payables": "",
@@ -2260,12 +2275,15 @@
"parts_not_recieved": "",
"payments_by_date": "",
"payments_by_date_type": "",
"production_by_category": "",
"production_by_category_one": "",
"production_by_csr": "",
"production_by_last_name": "",
"production_by_repair_status": "",
"production_by_ro": "",
"production_by_target_date": "",
"production_by_technician": "",
"production_by_technician_one": "",
"purchases_by_cost_center_detail": "",
"purchases_by_cost_center_summary": "",
"purchases_by_date_range_detail": "",
@@ -2309,9 +2327,12 @@
},
"labels": {
"asoftodaytarget": "",
"dailyactual": "",
"dailytarget": "",
"monthlytarget": "",
"targets": "",
"todateactual": "",
"weeklyactual": "",
"weeklytarget": "",
"workingdays": ""
},
@@ -2400,6 +2421,10 @@
"clockedout": "",
"created": "",
"deleted": ""
},
"validation": {
"clockoffmustbeafterclockon": "",
"clockoffwithoutclockon": ""
}
},
"titles": {

View File

@@ -17,7 +17,8 @@ const Templates = TemplateList();
export default async function RenderTemplate(
templateObject,
bodyshop,
renderAsHtml = false
renderAsHtml = false,
renderAsExcel = false
) {
//Query assets that match the template name. Must be in format <<templateName>>.query
let { contextData, useShopSpecificTemplate } = await fetchContextData(
@@ -30,6 +31,7 @@ export default async function RenderTemplate(
? `/${bodyshop.imexshopid}/${templateObject.name}`
: `/${templateObject.name}`,
...(renderAsHtml ? {} : { recipe: "chrome-pdf" }),
...(renderAsExcel ? { recipe: "html-to-xlsx" } : {}),
},
data: {
...contextData,
@@ -182,6 +184,9 @@ export const GenerateDocument = async (
template,
})
);
} else if (sendType === "x") {
console.log("excel");
await RenderTemplate(template, bodyshop, false, true);
} else {
await RenderTemplate(template, bodyshop);
}

View File

@@ -1466,6 +1466,24 @@ export const TemplateList = (type, context) => {
},
group: "customers",
},
estimates_written_converted: {
title: i18n.t("reportcenter.templates.estimates_written_converted"),
description: "",
subject: i18n.t(
"reportcenter.templates.estimates_written_converted"
),
key: "estimates_written_converted",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field:
i18n.t("jobs.fields.date_open") +
"/" +
i18n.t("jobs.fields.date_invoiced"), // Also date invoice.
},
group: "sales",
},
}
: {}),
...(!type || type === "courtesycarcontract"
@@ -1590,6 +1608,14 @@ export const TemplateList = (type, context) => {
//idtype: "vendor",
disabled: false,
},
production_by_category: {
title: i18n.t("reportcenter.templates.production_by_category"),
description: "",
subject: i18n.t("reportcenter.templates.production_by_category"),
key: "production_by_category",
//idtype: "vendor",
disabled: false,
},
production_by_technician: {
title: i18n.t("reportcenter.templates.production_by_technician"),
description: "",
@@ -1609,6 +1635,35 @@ export const TemplateList = (type, context) => {
key: "ca_bc_etf_table",
disabled: false,
},
exported_payroll: {
title: i18n.t("printcenter.payments.exported_payroll"),
description: "Est Detail",
subject: i18n.t("printcenter.payments.exported_payroll"),
key: "exported_payroll",
disabled: false,
},
production_by_technician_one: {
title: i18n.t(
"reportcenter.templates.production_by_technician_one"
),
description: "",
subject: i18n.t(
"reportcenter.templates.production_by_technician_one"
),
key: "production_by_technician_one",
//idtype: "vendor",
disabled: false,
},
production_by_category_one: {
title: i18n.t("reportcenter.templates.production_by_category_one"),
description: "",
subject: i18n.t(
"reportcenter.templates.production_by_category_one"
),
key: "production_by_category_one",
//idtype: "vendor",
disabled: false,
},
}
: {}),
};

View File

@@ -9460,7 +9460,14 @@ moment-business-days@^1.2.0:
resolved "https://registry.yarnpkg.com/moment-business-days/-/moment-business-days-1.2.0.tgz#6172f9f38dbf443c2f859baabeabbd2935f63d65"
integrity sha512-QJlceLfMSxy/jZSOgJYCKeKw+qGYHj8W0jMa/fYruyoJ85+bJuLRiYv5DIaflyuRipmYRfD4kDlSwVYteLN+Jw==
moment@^2.24.0, moment@^2.25.3:
moment-timezone@^0.5.34:
version "0.5.34"
resolved "https://registry.yarnpkg.com/moment-timezone/-/moment-timezone-0.5.34.tgz#a75938f7476b88f155d3504a9343f7519d9a405c"
integrity sha512-3zAEHh2hKUs3EXLESx/wsgw6IQdusOT8Bxm3D9UrHPQR7zlMmzwybC8zHEM1tQ4LJwP7fcxrWr8tuBg05fFCbg==
dependencies:
moment ">= 2.9.0"
"moment@>= 2.9.0", moment@^2.24.0, moment@^2.25.3:
version "2.29.1"
resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3"
integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==

View File

@@ -815,6 +815,8 @@
- email
- enforce_class
- enforce_referral
- entegral_configuration
- entegral_id
- features
- federal_tax_id
- id
@@ -866,6 +868,7 @@
- target_touchtime
- template_header
- textid
- timezone
- tt_allow_post_to_invoiced
- updated_at
- use_fippa
@@ -925,6 +928,7 @@
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- pbs_configuration
- phone
- prodtargethrs
- production_config
@@ -938,6 +942,7 @@
- state
- state_tax_id
- target_touchtime
- timezone
- tt_allow_post_to_invoiced
- updated_at
- use_fippa
@@ -2706,6 +2711,7 @@
- clm_title
- clm_total
- clm_zip
- comment
- converted
- created_at
- cust_pr
@@ -2962,6 +2968,7 @@
- clm_title
- clm_total
- clm_zip
- comment
- converted
- created_at
- cust_pr
@@ -3228,6 +3235,7 @@
- clm_title
- clm_total
- clm_zip
- comment
- converted
- created_at
- cust_pr

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "appointments_bodyshopid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "appointments_bodyshopid" on
"public"."appointments" using btree ("bodyshopid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "appointments_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "appointments_jobid" on
"public"."appointments" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "appointments_start";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "appointments_start" on
"public"."appointments" using btree ("start");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "appointments_end";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "appointments_end" on
"public"."appointments" using btree ("end");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "associations_bodyshopid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "associations_bodyshopid" on
"public"."associations" using btree ("shopid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "associations_useremail";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "associations_useremail" on
"public"."associations" using btree ("useremail");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "invoicelines_billid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "invoicelines_billid" on
"public"."billlines" using btree ("billid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "invoicelines_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "invoicelines_jobid" on
"public"."billlines" using btree ("joblineid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "bills_jobid";

View File

@@ -0,0 +1,2 @@
CREATE INDEX "bills_jobid" on
"public"."bills" using btree ("jobid");

View File

@@ -0,0 +1 @@
DROP INDEX IF EXISTS "conversations_shopid";

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