Compare commits

...

141 Commits

Author SHA1 Message Date
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
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
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
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
c962d848c1 PBS Improvements. 2022-01-21 14:34:15 -08: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
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
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
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
677c649ad8 IO-1612 Disable popover on PTO 2022-01-07 10:06:02 -08: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
adc48f50d7 IO-1622 Resolve CDK Export missing option 2022-01-07 10:01:09 -08:00
Patrick Fic
05fb2cd7a1 Further scheduling updates. 2022-01-07 09:54:43 -08: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
72c1ec74a5 IO-1623 Production priority column width increase. 2022-01-06 14:26:33 -08: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
ade92d86aa IO-347 Manual appointments. 2022-01-06 10:13:49 -08:00
Patrick Fic
fbd9fce230 IO-1604 Move inoice final note to notes screen. 2022-01-05 19:13:14 -08:00
Patrick Fic
172860b280 IO-1618 Prevent PAE posting of bills. 2022-01-05 13:56:15 -08:00
Patrick Fic
50926547b3 IO-1612 Add employee vacation tracking. 2022-01-05 11:45:37 -08: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
6457acc61e IO-1613 add suspended days to jobs. 2022-01-04 12:33:20 -08:00
Patrick Fic
fc023367cc IO-1608 Remove first name from parts order. 2022-01-03 13:05:47 -08: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
e06fded73c IO-1604 Add invoice final note to job close. 2022-01-03 10:49:01 -08:00
Patrick Fic
ee6b91d6ce IO-1591 Key tag label. 2022-01-03 09:16:22 -08:00
Patrick Fic
db69d2bf23 IO-1608 add name to parts order. 2022-01-03 09:15:46 -08:00
Patrick Fic
03ee20e2b2 IO-1591 Scheduling Modal hours showing as null. 2022-01-03 09:06:35 -08:00
Patrick Fic
0e2be2012e Merged in release/2021-12-31 (pull request #332)
Release/2021 12 31
2022-01-01 16:06:10 +00:00
Patrick Fic
93ea09fb19 Merge branch 'hotfix/2021-12-29' into release/2021-12/31 2021-12-29 22:03:47 -08:00
Patrick Fic
4d836a87ac Merged in hotfix/2021-12-29 (pull request #330)
Expose in service date for CDK.

Approved-by: Patrick Fic
2021-12-30 05:56:00 +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
ebde2516b7 Expose in service date for CDK. 2021-12-29 21:55:02 -08:00
Patrick Fic
817d1c99c7 Merged in hotfix/2021-12-29 (pull request #328)
Cherrypick CDK Fixes.

Approved-by: Patrick Fic
2021-12-30 04:06:46 +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
b29903c644 Cherrypick CDK Fixes. 2021-12-29 20:05:26 -08:00
Patrick Fic
454f69b511 IO-1605 Refactor smart scheduling. 2021-12-29 17:50:17 -06: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
e0d74aecb3 Updated CDK job export to pick vehicle with ID. 2021-12-29 14:23:37 -08: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
072650cf94 Updates to CDK job export for existing customers & incorrect phone. 2021-12-28 15:05:25 -08:00
Patrick Fic
fb92aae8cd Merged in hotfix/2021-12-28 (pull request #324)
hotfix/2021-12-28

Approved-by: Patrick Fic
2021-12-28 22:17:25 +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
93e2bfdc96 Merged in hotfix/2021-12-28 (pull request #322)
Resolve CDK generic customer.

Approved-by: Patrick Fic
2021-12-28 19:24:52 +00:00
Patrick Fic
04e3f2f44f Resolve CDK generic customer. 2021-12-28 11:24:24 -08: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
31c81543ad Update home page. 2021-12-28 10:39:13 -08:00
Patrick Fic
c27e8b9194 IO-1574 Add ignore blocked days to scoreboard. 2021-12-28 10:26:41 -08:00
Patrick Fic
1218deeee7 IO-1600 Number of rental days on contract list. 2021-12-28 10:06:01 -08:00
Patrick Fic
6422b13c1a IO-1257 Accept special characters on upload. 2021-12-28 09:54:31 -08:00
Patrick Fic
5ef1530d37 IO-1542 Automatically set next contact date on intake. 2021-12-28 09:42:23 -08:00
Patrick Fic
4a46bdb9e8 IO-1578 Add employee sorting to prod board. 2021-12-28 09:17:16 -08: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
Patrick Fic
8a42a995f7 IO-1591 added labels to print center. 2021-12-27 13:12:45 -08:00
Patrick Fic
ce513bb178 IO-1602 Add customer portions to CDK posting screen. 2021-12-27 12:40:52 -08:00
Patrick Fic
d0fe352d95 IO-1584 Updated Bill Posting WIP. 2021-12-27 12:25:32 -08:00
Patrick Fic
ffa3c54867 Merged in release/2021-12-23 (pull request #319)
Release/2021 12 23
2021-12-27 16:23:46 +00:00
Patrick Fic
38f190b8a5 Merged in release/2021-12-23 (pull request #318)
release/2021-12-23

Approved-by: Patrick Fic
2021-12-24 17:43:45 +00:00
Patrick Fic
f2c7c9b7ad Remove CI warning. 2021-12-24 09:43:24 -08:00
Patrick Fic
72e0dd9834 Merged in release/2021-12-23 (pull request #317)
IO-1590 Production by Technician IO-1585 File access api

Approved-by: Patrick Fic
2021-12-24 17:32:10 +00:00
Patrick Fic
b67b4c993e IO-1585 Remove unused function. 2021-12-24 09:31:50 -08:00
Patrick Fic
7abc3976e8 IO-1590 Production by Technician IO-1585 File access api 2021-12-24 09:30:26 -08:00
Patrick Fic
3f2f7c1182 Merged in release/2021-12-23 (pull request #316)
release/2021-12-23

Approved-by: Patrick Fic
2021-12-23 01:26:02 +00:00
Patrick Fic
74c6d32849 IO-1596 Add Envelope to print center. 2021-12-22 16:38:10 -08:00
Patrick Fic
76a00794f1 Improe chat response handling for archive/tagging. 2021-12-20 14:46:57 -08:00
Patrick Fic
60c9fcab2d Merged in release/2021-12-17 (pull request #315)
release/2021-12-17

Approved-by: Patrick Fic
2021-12-17 23:54:19 +00:00
Patrick Fic
b8e7c8f341 Merged in release/2021-12-17 (pull request #314)
release/2021-12-17

Approved-by: Patrick Fic
2021-12-17 19:09:27 +00:00
Patrick Fic
6f63fd9a5a Merged in release/2021-12-17 (pull request #313)
release/2021-12-17

Approved-by: Patrick Fic
2021-12-17 17:56:25 +00:00
Patrick Fic
6bee702770 Merged in release/2021-12-17 (pull request #312)
release/2021-12-17

Approved-by: Patrick Fic
2021-12-16 17:05:18 +00:00
Patrick Fic
22829fb649 Merged in release/2021-12-17 (pull request #311)
release/2021-12-17

Approved-by: Patrick Fic
2021-12-16 01:07:30 +00:00
Patrick Fic
20d448c6eb Merged in release/2021-12-17 (pull request #310)
release/2021-12-17

Approved-by: Patrick Fic
2021-12-15 21:35:32 +00:00
Patrick Fic
6a24143a2b Merged in release/2021-12-17 (pull request #309)
release/2021-12-17

Approved-by: Patrick Fic
2021-12-15 21:32:07 +00:00
Patrick Fic
447bc45466 Merged in release/2021-12-17 (pull request #308)
release/2021-12-17

Approved-by: Patrick Fic
2021-12-15 21:29:08 +00:00
Patrick Fic
70094dad99 Merged in release/2021-12-17 (pull request #307)
Re-add Craco & Cypress

Approved-by: Patrick Fic
2021-12-15 00:00:52 +00:00
Patrick Fic
5b6d3d2f71 Merged in release/2021-12-17 (pull request #306)
Circle CI Update

Approved-by: Patrick Fic
2021-12-14 22:57:43 +00:00
Patrick Fic
1e9aa912f2 Merged in release/2021-12-17 (pull request #302)
release/2021-12-17

Approved-by: Patrick Fic
2021-12-14 21:19:54 +00:00
Patrick Fic
3f5b5f6911 Merged in release/2021-12-17 (pull request #301)
Release/2021 12 17
2021-12-13 22:34:16 +00:00
Patrick Fic
f01cff1c6d Merged in release/2021-12-10 (pull request #300)
Release/2021 12 10
2021-12-10 21:51:05 +00:00
121 changed files with 4395 additions and 1367 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
@@ -500,6 +500,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>end</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>note</name>
<definition_loaded>false</definition_loaded>
@@ -521,6 +542,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>start</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>time</name>
<definition_loaded>false</definition_loaded>
@@ -673,6 +715,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>dataconsistency</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>history</name>
<definition_loaded>false</definition_loaded>
@@ -715,6 +778,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>manualevent</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>noarrivingjobs</name>
<definition_loaded>false</definition_loaded>
@@ -4000,6 +4084,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>ignoreblockeddays</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>inhousevendorid</name>
<definition_loaded>false</definition_loaded>
@@ -4045,6 +4150,27 @@
<folder_node>
<name>intake</name>
<children>
<concept_node>
<name>next_contact_hours</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>templates</name>
<definition_loaded>false</definition_loaded>
@@ -9990,6 +10116,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>length</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>localtax</name>
<definition_loaded>false</definition_loaded>
@@ -13120,6 +13267,27 @@
<folder_node>
<name>actions</name>
<children>
<concept_node>
<name>addvacation</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>new</name>
<definition_loaded>false</definition_loaded>
@@ -13508,6 +13676,74 @@
</translation>
</translations>
</concept_node>
<folder_node>
<name>vacation</name>
<children>
<concept_node>
<name>end</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>length</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>start</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>
@@ -13534,6 +13770,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>endmustbeafterstart</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>flat_rate</name>
<definition_loaded>false</definition_loaded>
@@ -14010,6 +14267,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>print</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>refresh</name>
<definition_loaded>false</definition_loaded>
@@ -20474,6 +20752,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>inservicedate</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>journal</name>
<definition_loaded>false</definition_loaded>
@@ -21326,6 +21625,27 @@
</concept_node>
</children>
</folder_node>
<concept_node>
<name>invoice_final_note</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>kmin</name>
<definition_loaded>false</definition_loaded>
@@ -25128,6 +25448,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>
@@ -25149,6 +25490,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>
@@ -26778,6 +27140,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>suspended</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>suspense</name>
<definition_loaded>false</definition_loaded>
@@ -33713,6 +34096,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>envelope_return_address</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>estimate</name>
<definition_loaded>false</definition_loaded>
@@ -33860,6 +34264,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>folder_label_multiple</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>glass_express_checklist</name>
<definition_loaded>false</definition_loaded>
@@ -34049,6 +34474,74 @@
</translation>
</translations>
</concept_node>
<folder_node>
<name>labels</name>
<children>
<concept_node>
<name>count</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>labels</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>position</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>
<concept_node>
<name>mpi_animal_checklist</name>
<definition_loaded>false</definition_loaded>
@@ -35328,6 +35821,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>suspend</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>unsuspend</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>
@@ -36653,6 +37188,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>
@@ -37514,6 +38070,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>
@@ -37619,6 +38217,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>production_by_technician</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_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>

View File

@@ -1,232 +1 @@
[
{
"id": 1,
"name": "Leanne Graham",
"username": "Bret",
"email": "Sincere@april.biz",
"address": {
"street": "Kulas Light",
"suite": "Apt. 556",
"city": "Gwenborough",
"zipcode": "92998-3874",
"geo": {
"lat": "-37.3159",
"lng": "81.1496"
}
},
"phone": "1-770-736-8031 x56442",
"website": "hildegard.org",
"company": {
"name": "Romaguera-Crona",
"catchPhrase": "Multi-layered client-server neural-net",
"bs": "harness real-time e-markets"
}
},
{
"id": 2,
"name": "Ervin Howell",
"username": "Antonette",
"email": "Shanna@melissa.tv",
"address": {
"street": "Victor Plains",
"suite": "Suite 879",
"city": "Wisokyburgh",
"zipcode": "90566-7771",
"geo": {
"lat": "-43.9509",
"lng": "-34.4618"
}
},
"phone": "010-692-6593 x09125",
"website": "anastasia.net",
"company": {
"name": "Deckow-Crist",
"catchPhrase": "Proactive didactic contingency",
"bs": "synergize scalable supply-chains"
}
},
{
"id": 3,
"name": "Clementine Bauch",
"username": "Samantha",
"email": "Nathan@yesenia.net",
"address": {
"street": "Douglas Extension",
"suite": "Suite 847",
"city": "McKenziehaven",
"zipcode": "59590-4157",
"geo": {
"lat": "-68.6102",
"lng": "-47.0653"
}
},
"phone": "1-463-123-4447",
"website": "ramiro.info",
"company": {
"name": "Romaguera-Jacobson",
"catchPhrase": "Face to face bifurcated interface",
"bs": "e-enable strategic applications"
}
},
{
"id": 4,
"name": "Patricia Lebsack",
"username": "Karianne",
"email": "Julianne.OConner@kory.org",
"address": {
"street": "Hoeger Mall",
"suite": "Apt. 692",
"city": "South Elvis",
"zipcode": "53919-4257",
"geo": {
"lat": "29.4572",
"lng": "-164.2990"
}
},
"phone": "493-170-9623 x156",
"website": "kale.biz",
"company": {
"name": "Robel-Corkery",
"catchPhrase": "Multi-tiered zero tolerance productivity",
"bs": "transition cutting-edge web services"
}
},
{
"id": 5,
"name": "Chelsey Dietrich",
"username": "Kamren",
"email": "Lucio_Hettinger@annie.ca",
"address": {
"street": "Skiles Walks",
"suite": "Suite 351",
"city": "Roscoeview",
"zipcode": "33263",
"geo": {
"lat": "-31.8129",
"lng": "62.5342"
}
},
"phone": "(254)954-1289",
"website": "demarco.info",
"company": {
"name": "Keebler LLC",
"catchPhrase": "User-centric fault-tolerant solution",
"bs": "revolutionize end-to-end systems"
}
},
{
"id": 6,
"name": "Mrs. Dennis Schulist",
"username": "Leopoldo_Corkery",
"email": "Karley_Dach@jasper.info",
"address": {
"street": "Norberto Crossing",
"suite": "Apt. 950",
"city": "South Christy",
"zipcode": "23505-1337",
"geo": {
"lat": "-71.4197",
"lng": "71.7478"
}
},
"phone": "1-477-935-8478 x6430",
"website": "ola.org",
"company": {
"name": "Considine-Lockman",
"catchPhrase": "Synchronised bottom-line interface",
"bs": "e-enable innovative applications"
}
},
{
"id": 7,
"name": "Kurtis Weissnat",
"username": "Elwyn.Skiles",
"email": "Telly.Hoeger@billy.biz",
"address": {
"street": "Rex Trail",
"suite": "Suite 280",
"city": "Howemouth",
"zipcode": "58804-1099",
"geo": {
"lat": "24.8918",
"lng": "21.8984"
}
},
"phone": "210.067.6132",
"website": "elvis.io",
"company": {
"name": "Johns Group",
"catchPhrase": "Configurable multimedia task-force",
"bs": "generate enterprise e-tailers"
}
},
{
"id": 8,
"name": "Nicholas Runolfsdottir V",
"username": "Maxime_Nienow",
"email": "Sherwood@rosamond.me",
"address": {
"street": "Ellsworth Summit",
"suite": "Suite 729",
"city": "Aliyaview",
"zipcode": "45169",
"geo": {
"lat": "-14.3990",
"lng": "-120.7677"
}
},
"phone": "586.493.6943 x140",
"website": "jacynthe.com",
"company": {
"name": "Abernathy Group",
"catchPhrase": "Implemented secondary concept",
"bs": "e-enable extensible e-tailers"
}
},
{
"id": 9,
"name": "Glenna Reichert",
"username": "Delphine",
"email": "Chaim_McDermott@dana.io",
"address": {
"street": "Dayna Park",
"suite": "Suite 449",
"city": "Bartholomebury",
"zipcode": "76495-3109",
"geo": {
"lat": "24.6463",
"lng": "-168.8889"
}
},
"phone": "(775)976-6794 x41206",
"website": "conrad.com",
"company": {
"name": "Yost and Sons",
"catchPhrase": "Switchable contextually-based project",
"bs": "aggregate real-time technologies"
}
},
{
"id": 10,
"name": "Clementina DuBuque",
"username": "Moriah.Stanton",
"email": "Rey.Padberg@karina.biz",
"address": {
"street": "Kattie Turnpike",
"suite": "Suite 198",
"city": "Lebsackbury",
"zipcode": "31428-2261",
"geo": {
"lat": "-38.2386",
"lng": "57.2232"
}
},
"phone": "024-648-3804",
"website": "ambrose.net",
"company": {
"name": "Hoeger LLC",
"catchPhrase": "Centralized empowering task-force",
"bs": "target end-to-end models"
}
}
]
[]

View File

@@ -11,10 +11,10 @@
"@sentry/react": "^6.16.1",
"@sentry/tracing": "^6.16.1",
"@splitsoftware/splitio-react": "^1.3.0",
"@stripe/react-stripe-js": "^1.6.0",
"@stripe/react-stripe-js": "^1.7.0",
"@stripe/stripe-js": "^1.22.0",
"@tanem/react-nprogress": "^3.0.82",
"antd": "^4.17.3",
"antd": "^4.17.4",
"apollo-link-logger": "^2.0.0",
"axios": "^0.24.0",
"craco-less": "^1.20.0",
@@ -24,10 +24,10 @@
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"firebase": "^9.6.1",
"graphql": "^16.1.0",
"i18next": "^21.6.0",
"graphql": "^16.2.0",
"i18next": "^21.6.3",
"i18next-browser-languagedetector": "^6.1.2",
"jsoneditor": "^9.5.7",
"jsoneditor": "^9.5.8",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.9.44",
"logrocket": "^2.1.2",
@@ -40,7 +40,7 @@
"rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6",
"react": "^17.0.2",
"react-big-calendar": "^0.38.1",
"react-big-calendar": "^0.38.2",
"react-color": "^2.19.3",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
@@ -49,7 +49,7 @@
"react-grid-layout": "^1.3.0",
"react-i18next": "^11.15.1",
"react-icons": "^4.3.1",
"react-number-format": "^4.8.0",
"react-number-format": "^4.9.0",
"react-redux": "^7.2.6",
"react-resizable": "^3.0.4",
"react-router-dom": "^5.3.0",
@@ -78,7 +78,8 @@
"workbox-range-requests": "^6.4.2",
"workbox-routing": "^6.4.2",
"workbox-strategies": "^6.4.2",
"workbox-streams": "^6.4.2"
"workbox-streams": "^6.4.2",
"yauzl": "^2.10.0"
},
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
@@ -110,11 +111,15 @@
"last 1 safari version"
]
},
"resolutions": {
"react-error-overlay": "6.0.9"
},
"devDependencies": {
"@sentry/webpack-plugin": "^1.18.3",
"@testing-library/cypress": "^8.0.2",
"cypress": "^9.1.1",
"eslint-plugin-cypress": "^2.12.1",
"react-error-overlay": "6.0.9",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.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

@@ -245,7 +245,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

@@ -0,0 +1,135 @@
import { Form, Input, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters";
import BillFormItemsExtendedFormItem from "./bill-form-lines.extended.formitem.component";
export default function BillFormLinesExtended({
lineData,
discount,
form,
responsibilityCenters,
disabled,
}) {
const [search, setSearch] = useState("");
const { t } = useTranslation();
const columns = [
{
title: t("joblines.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
width: "10%",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
},
{
title: t("joblines.fields.oem_partno"),
dataIndex: "oem_partno",
key: "oem_partno",
width: "10%",
sorter: (a, b) => alphaSort(a.oem_partno, b.oem_partno),
},
{
title: t("joblines.fields.part_type"),
dataIndex: "part_type",
key: "part_type",
width: "10%",
filters: [
{
text: t("jobs.labels.partsfilter"),
value: ["PAN", "PAP", "PAL", "PAA", "PAS", "PASL"],
},
{
text: t("joblines.fields.part_types.PAN"),
value: ["PAN", "PAP"],
},
{
text: t("joblines.fields.part_types.PAL"),
value: ["PAL"],
},
{
text: t("joblines.fields.part_types.PAA"),
value: ["PAA"],
},
{
text: t("joblines.fields.part_types.PAS"),
value: ["PAS", "PASL"],
},
],
onFilter: (value, record) => value.includes(record.part_type),
render: (text, record) =>
record.part_type
? t(`joblines.fields.part_types.${record.part_type}`)
: null,
},
{
title: t("joblines.fields.act_price"),
dataIndex: "act_price",
key: "act_price",
width: "10%",
sorter: (a, b) => a.act_price - b.act_price,
shouldCellUpdate: false,
render: (text, record) => (
<>
<CurrencyFormatter>
{record.db_ref === "900510" || record.db_ref === "900511"
? record.prt_dsmk_m
: record.act_price}
</CurrencyFormatter>
{record.part_qty ? `(x ${record.part_qty})` : null}
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
<span
style={{ marginLeft: ".2rem" }}
>{`(${record.prt_dsmk_p}%)`}</span>
) : (
<></>
)}
</>
),
},
{
title: t("billlines.fields.posting"),
dataIndex: "posting",
key: "posting",
render: (text, record, index) => (
<Form.Item noStyle name={["billlineskeys", record.id]}>
<BillFormItemsExtendedFormItem
form={form}
record={record}
index={index}
responsibilityCenters={responsibilityCenters}
discount={discount}
/>
</Form.Item>
),
},
];
const data =
search === ""
? lineData
: lineData.filter(
(l) =>
(l.line_desc &&
l.line_desc.toLowerCase().includes(search.toLowerCase())) ||
(l.oem_partno &&
l.oem_partno.toLowerCase().includes(search.toLowerCase())) ||
(l.act_price &&
l.act_price.toString().startsWith(search.toString()))
);
return (
<Form.Item noStyle name="billlineskeys">
<button onClick={() => console.log(form.getFieldsValue())}>form</button>
<Input onChange={(e) => setSearch(e.target.value)} allowClear />
<Table
pagination={false}
size="small"
columns={columns}
rowKey="id"
dataSource={data}
/>
</Form.Item>
);
}

View File

@@ -0,0 +1,288 @@
import React from "react";
import {
PlusCircleFilled,
MinusCircleFilled,
WarningOutlined,
} from "@ant-design/icons";
import { Form, Button, InputNumber, Input, Select, Switch, Space } from "antd";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import CiecaSelect from "../../utils/Ciecaselect";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillFormItemsExtendedFormItem);
export function BillFormItemsExtendedFormItem({
value,
bodyshop,
form,
record,
index,
disabled,
responsibilityCenters,
discount,
}) {
// const { billlineskeys } = form.getFieldsValue("billlineskeys");
const { t } = useTranslation();
if (!value)
return (
<Button
onClick={() => {
const values = form.getFieldsValue("billlineskeys");
form.setFieldsValue({
...values,
billlineskeys: {
...(values.billlineskeys || {}),
[record.id]: {
joblineid: record.id,
line_desc: record.line_desc,
quantity: record.part_qty || 1,
actual_price: record.act_price,
cost_center: record.part_type
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
? record.part_type
: responsibilityCenters.defaults &&
(responsibilityCenters.defaults.costs[record.part_type] ||
null)
: null,
},
},
});
}}
>
<PlusCircleFilled />
</Button>
);
return (
<Space wrap>
<Form.Item
label={t("billlines.fields.line_desc")}
name={["billlineskeys", record.id, "line_desc"]}
>
<Input disabled={disabled} />
</Form.Item>
<Form.Item
label={t("billlines.fields.quantity")}
name={["billlineskeys", record.id, "quantity"]}
>
<InputNumber precision={0} min={0} disabled={disabled} />
</Form.Item>
<Form.Item
label={t("billlines.fields.actual_price")}
name={["billlineskeys", record.id, "actual_price"]}
>
<CurrencyInput
min={0}
disabled={disabled}
onBlur={(e) => {
const { billlineskeys } = form.getFieldsValue("billlineskeys");
form.setFieldsValue({
billlineskeys: {
...billlineskeys,
[record.id]: {
...billlineskeys[billlineskeys],
actual_cost: !!billlineskeys[billlineskeys].actual_cost
? billlineskeys[billlineskeys].actual_cost
: Math.round(
(parseFloat(e.target.value) * (1 - discount) +
Number.EPSILON) *
100
) / 100,
},
},
});
}}
/>
</Form.Item>
<Form.Item
label={t("billlines.fields.actual_cost")}
name={["billlineskeys", record.id, "actual_cost"]}
>
<CurrencyInput min={0} disabled={disabled} />
</Form.Item>
<Form.Item shouldUpdate>
{() => {
const line = value;
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>
<Form.Item
label={t("billlines.fields.cost_center")}
name={["billlineskeys", record.id, "cost_center"]}
>
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? CiecaSelect(true, false)
: responsibilityCenters.costs.map((item) => (
<Select.Option key={item.name}>{item.name}</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("billlines.fields.location")}
name={["billlineskeys", record.id, "location"]}
>
<Select disabled={disabled}>
{bodyshop.md_parts_locations.map((loc, idx) => (
<Select.Option key={idx} value={loc}>
{loc}
</Select.Option>
))}
</Select>
</Form.Item>
<Form.Item
label={t("billlines.fields.deductedfromlbr")}
name={["billlineskeys", record.id, "deductedfromlbr"]}
valuePropName="checked"
>
<Switch disabled={disabled} />
</Form.Item>
<Form.Item shouldUpdate style={{ display: "inline-block" }}>
{() => {
if (
form.getFieldsValue("billlineskeys").billlineskeys[record.id]
.deductedfromlbr
)
return (
<div>
<Form.Item
label={t("joblines.fields.mod_lbr_ty")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name={[
"billlineskeys",
record.id,
"lbr_adjustment",
"mod_lbr_ty",
]}
>
<Select allowClear>
<Select.Option value="LAA">
{t("joblines.fields.lbr_types.LAA")}
</Select.Option>
<Select.Option value="LAB">
{t("joblines.fields.lbr_types.LAB")}
</Select.Option>
<Select.Option value="LAD">
{t("joblines.fields.lbr_types.LAD")}
</Select.Option>
<Select.Option value="LAE">
{t("joblines.fields.lbr_types.LAE")}
</Select.Option>
<Select.Option value="LAF">
{t("joblines.fields.lbr_types.LAF")}
</Select.Option>
<Select.Option value="LAG">
{t("joblines.fields.lbr_types.LAG")}
</Select.Option>
<Select.Option value="LAM">
{t("joblines.fields.lbr_types.LAM")}
</Select.Option>
<Select.Option value="LAR">
{t("joblines.fields.lbr_types.LAR")}
</Select.Option>
<Select.Option value="LAS">
{t("joblines.fields.lbr_types.LAS")}
</Select.Option>
<Select.Option value="LAU">
{t("joblines.fields.lbr_types.LAU")}
</Select.Option>
<Select.Option value="LA1">
{t("joblines.fields.lbr_types.LA1")}
</Select.Option>
<Select.Option value="LA2">
{t("joblines.fields.lbr_types.LA2")}
</Select.Option>
<Select.Option value="LA3">
{t("joblines.fields.lbr_types.LA3")}
</Select.Option>
<Select.Option value="LA4">
{t("joblines.fields.lbr_types.LA4")}
</Select.Option>
</Select>
</Form.Item>
<Form.Item
label={t("jobs.labels.adjustmentrate")}
name={["billlineskeys", record.id, "lbr_adjustment", "rate"]}
initialValue={bodyshop.default_adjustment_rate}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber precision={2} min={0.01} />
</Form.Item>
</div>
);
return <></>;
}}
</Form.Item>
<Form.Item
label={t("billlines.fields.federal_tax_applicable")}
name={["billlineskeys", record.id, "applicable_taxes", "federal"]}
valuePropName="checked"
>
<Switch disabled={disabled} />
</Form.Item>
<Form.Item
label={t("billlines.fields.state_tax_applicable")}
name={["billlineskeys", record.id, "applicable_taxes", "state"]}
valuePropName="checked"
>
<Switch disabled={disabled} />
</Form.Item>
<Form.Item
label={t("billlines.fields.local_tax_applicable")}
name={["billlineskeys", record.id, "applicable_taxes", "local"]}
valuePropName="checked"
>
<Switch disabled={disabled} />
</Form.Item>
<Button
onClick={() => {
const values = form.getFieldsValue("billlineskeys");
form.setFieldsValue({
...values,
billlineskeys: {
...(values.billlineskeys || {}),
[record.id]: null,
},
});
}}
>
<MinusCircleFilled />
</Button>
</Space>
);
}

View File

@@ -28,6 +28,8 @@ import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import BillFormLines from "./bill-form.lines.component";
import { CalculateBillTotal } from "./bill-form.totals.utility";
import { useTreatments } from "@splitsoftware/splitio-react";
import BillFormLinesExtended from "../bill-form-lines-extended/bill-form-lines-extended.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -49,7 +51,11 @@ export function BillFormComponent({
const { t } = useTranslation();
const client = useApolloClient();
const [discount, setDiscount] = useState(0);
const { Extended_Bill_Posting } = useTreatments(
["Extended_Bill_Posting"],
{},
bodyshop.imexshopid
);
const handleVendorSelect = (props, opt) => {
setDiscount(opt.discount);
};
@@ -357,13 +363,24 @@ export function BillFormComponent({
</Form.Item>
</LayoutFormRow>
<Divider orientation="left">{t("bills.labels.bill_lines")}</Divider>
<BillFormLines
lineData={lineData}
discount={discount}
form={form}
responsibilityCenters={responsibilityCenters}
disabled={disabled}
/>
{Extended_Bill_Posting.treatment === "on" ? (
<BillFormLinesExtended
lineData={lineData}
discount={discount}
form={form}
responsibilityCenters={responsibilityCenters}
disabled={disabled}
/>
) : (
<BillFormLines
lineData={lineData}
discount={discount}
form={form}
responsibilityCenters={responsibilityCenters}
disabled={disabled}
/>
)}
<Form.Item
name="upload"

View File

@@ -1,4 +1,4 @@
import { DeleteFilled, WarningOutlined } from "@ant-design/icons";
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import {
Button,
Form,
@@ -7,7 +7,8 @@ import {
Select,
Space,
Switch,
Table
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(
@@ -74,7 +88,9 @@ export function BillEnterModalLinesComponent({
actual_price: opt.cost,
cost_center: opt.part_type
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
? opt.part_type
? opt.part_type !== "PAE"
? opt.part_type
: null
: responsibilityCenters.defaults &&
(responsibilityCenters.defaults.costs[
opt.part_type
@@ -199,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"),
@@ -486,6 +537,7 @@ const EditableCell = ({
formInput,
formItemProps,
additional,
wrapper,
...restProps
}) => {
if (additional)
@@ -503,7 +555,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

@@ -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,13 @@ export function BillsListTableComponent({
cost: i.actual_cost,
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
};
}),
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

@@ -13,6 +13,7 @@ export default function ChatArchiveButton({ conversation }) {
await updateConversation({
variables: { id: conversation.id, archived: !conversation.archived },
refetchQueries: ["CONVERSATION_LIST_QUERY"],
});
setLoading(false);

View File

@@ -16,6 +16,16 @@ export default function ChatConversationTitleTags({ jobConversations }) {
conversationId: convId,
jobId: jobId,
},
update(cache) {
cache.modify({
id: cache.identify({ id: convId, __typename: "conversations" }),
fields: {
job_conversations(ex) {
return ex.filter((e) => e.jobid !== jobId);
},
},
});
},
});
logImEXEvent("messaging_remove_job_tag", {
conversationId: convId,

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

@@ -11,6 +11,7 @@ import { setModalContext } from "../../redux/modals/modals.actions";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import moment from "moment";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
@@ -86,7 +87,9 @@ export function ContractsList({
} ${record.courtesycar.make} ${record.courtesycar.model}${
record.courtesycar.plate ? ` (${record.courtesycar.plate})` : ""
}${
record.courtesycar.fleetnumber ? ` (${record.courtesycar.fleetnumber})` : ""
record.courtesycar.fleetnumber
? ` (${record.courtesycar.fleetnumber})`
: ""
}`}</Link>
),
},
@@ -133,6 +136,19 @@ export function ContractsList({
<DateTimeFormatter>{record.actualreturn}</DateTimeFormatter>
),
},
{
title: t("contracts.fields.length"),
dataIndex: "length",
key: "length",
render: (text, record) =>
(record.actualreturn &&
record.start &&
`${moment(record.actualreturn)
.diff(moment(record.start), "days", true)
.toFixed(1)} days`) ||
"",
},
];
const handleTableChange = (pagination, filters, sorter) => {

View File

@@ -45,7 +45,7 @@ export function DmsCustomerSelector({ bodyshop }) {
const onUseGeneric = () => {
setVisible(false);
socket.emit(
`${dmsType}selected-customer`,
`${dmsType}-selected-customer`,
bodyshop.cdk_configuration.generic_customer_number
);
setSelectedCustomer(null);

View File

@@ -14,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";
@@ -22,6 +23,7 @@ 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";
@@ -78,10 +80,26 @@ 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,
}).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"),
}}
>
<LayoutFormRow grow>
@@ -154,6 +172,12 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
>
<Input disabled />
</Form.Item>
<Form.Item
name="inservicedate"
label={t("jobs.fields.dms.inservicedate")}
>
<FormDatePicker />
</Form.Item>
</LayoutFormRow>
<Space>
<DmsCdkMakes form={form} socket={socket} job={job} />
@@ -174,6 +198,22 @@ export function DmsPostForm({ bodyshop, socket, job, logsRef }) {
</Form.Item>
<Divider />
<Space size="large" wrap align="center">
<Statistic
title={t("jobs.fields.ded_amt")}
value={Dinero(
job.job_totals.totals.custPayable.deductible
).toFormat()}
/>
<Statistic
title={t("jobs.labels.total_cust_payable")}
value={Dinero(job.job_totals.totals.custPayable.total).toFormat()}
/>
<Statistic
title={t("jobs.labels.net_repairs")}
value={Dinero(job.job_totals.totals.net_repairs).toFormat()}
/>
</Space>
<Form.List name={["payers"]}>
{(fields, { add, remove }) => {
return (

View File

@@ -21,9 +21,9 @@ export const handleUpload = (ev, context) => {
const fileName = ev.file.name || ev.filename;
let key = `${bodyshop.id}/${jobId}/${fileName.replace(
/\.[^/.]+$/,
""
let key = `${bodyshop.id}/${jobId}/${replaceAccents(fileName).replace(
/[^A-Z0-9]+/gi,
"_"
)}-${new Date().getTime()}`;
let extension = fileName.split(".").pop();
uploadToCloudinary(
@@ -187,3 +187,33 @@ export function DetermineFileType(filetype) {
return "auto";
}
function replaceAccents(str) {
// Verifies if the String has accents and replace them
if (str.search(/[\xC0-\xFF]/g) > -1) {
str = str
.replace(/[\xC0-\xC5]/g, "A")
.replace(/[\xC6]/g, "AE")
.replace(/[\xC7]/g, "C")
.replace(/[\xC8-\xCB]/g, "E")
.replace(/[\xCC-\xCF]/g, "I")
.replace(/[\xD0]/g, "D")
.replace(/[\xD1]/g, "N")
.replace(/[\xD2-\xD6\xD8]/g, "O")
.replace(/[\xD9-\xDC]/g, "U")
.replace(/[\xDD]/g, "Y")
.replace(/[\xDE]/g, "P")
.replace(/[\xE0-\xE5]/g, "a")
.replace(/[\xE6]/g, "ae")
.replace(/[\xE7]/g, "c")
.replace(/[\xE8-\xEB]/g, "e")
.replace(/[\xEC-\xEF]/g, "i")
.replace(/[\xF1]/g, "n")
.replace(/[\xF2-\xF6\xF8]/g, "o")
.replace(/[\xF9-\xFC]/g, "u")
.replace(/[\xFE]/g, "p")
.replace(/[\xFD\xFF]/g, "y");
}
return str;
}

View File

@@ -1,14 +1,19 @@
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.
const dateFormat = "MM/DD/YYYY";
const FormDatePicker = (
{ value, onChange, onBlur, onlyFuture, ...restProps },
ref
) => {
export default function FormDatePicker({
value,
onChange,
onBlur,
onlyFuture,
...restProps
}) {
const ref = useRef();
const handleChange = (newDate) => {
if (value !== newDate && onChange) {
onChange(newDate);
@@ -19,17 +24,44 @@ const FormDatePicker = (
if (e.key.toLowerCase() === "t") {
if (onChange) {
onChange(new 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(_a);
};
return (
<div onKeyDown={handleKeyDown}>
<DatePicker
ref={ref}
value={value ? moment(value) : null}
onChange={handleChange}
format={dateFormat}
onBlur={onBlur}
onBlur={onBlur || handleBlur}
disabledTime
{...(onlyFuture && {
disabledDate: (d) => moment().subtract(1, "day").isAfter(d),
@@ -38,6 +70,4 @@ const FormDatePicker = (
/>
</div>
);
};
export default forwardRef(FormDatePicker);
}

View File

@@ -1,5 +1,5 @@
import { useLazyQuery } from "@apollo/client";
import { AutoComplete, Divider, Input, Space } from "antd";
import { AutoComplete, Divider, Space } from "antd";
import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -11,8 +11,7 @@ import AlertComponent from "../alert/alert.component";
export default function GlobalSearch() {
const { t } = useTranslation();
const [callSearch, { loading, error, data }] =
useLazyQuery(GLOBAL_SEARCH_QUERY);
const [callSearch, { error, data }] = useLazyQuery(GLOBAL_SEARCH_QUERY);
const executeSearch = (v) => {
if (v && v.variables.search && v.variables.search !== "") callSearch(v);
@@ -39,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>
@@ -172,8 +172,7 @@ export default function GlobalSearch() {
options={options}
onSearch={handleSearch}
placeholder={t("general.labels.globalsearch")}
>
<Input.Search loading={loading} />
</AutoComplete>
allowClear
></AutoComplete>
);
}

View File

@@ -27,6 +27,7 @@ import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component";
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
import ScheduleAtChange from "./job-at-change.component";
import ScheduleEventColor from "./schedule-event.color.component";
import ScheduleEventNote from "./schedule-event.note.component";
@@ -66,7 +67,10 @@ export function ScheduleEventComponent({
const popoverContent = (
<div style={{ maxWidth: "40vw" }}>
{!event.isintake ? (
<strong>{event.title}</strong>
<Space>
<strong>{event.title}</strong>
<ScheduleEventColor event={event} />
</Space>
) : (
<Space>
<strong>{`${(event.job && event.job.ownr_fn) || ""} ${
@@ -118,7 +122,9 @@ export function ScheduleEventComponent({
</DataLabel>
<ScheduleEventNote event={event} />
</div>
) : null}
) : (
<div>{event.note || ""}</div>
)}
<Divider />
<Space wrap>
{event.job ? (
@@ -140,84 +146,89 @@ export function ScheduleEventComponent({
{t("appointments.actions.preview")}
</Button>
) : null}
<Dropdown
overlay={
<Menu>
<Menu.Item
onClick={() => {
const Template = TemplateList("job").appointment_reminder;
GenerateDocument(
{
name: Template.key,
variables: { id: event.job.id },
},
{
to: event.job && event.job.ownr_ea,
subject: Template.subject,
},
"e",
event.job && event.job.id
);
}}
disabled={event.arrived}
>
{t("general.labels.email")}
</Menu.Item>
<Menu.Item
onClick={() => {
const p = parsePhoneNumber(event.job.ownr_ph1, "CA");
if (p && p.isValid()) {
openChatByPhone({
phone_num: p.formatInternational(),
jobid: event.job.id,
});
setMessage(
t("appointments.labels.reminder", {
shopname: bodyshop.shopname,
date: moment(event.start).format("MM/DD/YYYY"),
time: moment(event.start).format("HH:mm a"),
})
{event.job ? (
<Dropdown
overlay={
<Menu>
<Menu.Item
onClick={() => {
const Template = TemplateList("job").appointment_reminder;
GenerateDocument(
{
name: Template.key,
variables: { id: event.job.id },
},
{
to: event.job && event.job.ownr_ea,
subject: Template.subject,
},
"e",
event.job && event.job.id
);
setVisible(false);
} else {
notification["error"]({
message: t("messaging.error.invalidphone"),
});
}
}}
disabled={event.arrived || !bodyshop.messagingservicesid}
>
{t("general.labels.sms")}
</Menu.Item>
</Menu>
}
>
<Button>{t("appointments.actions.sendreminder")}</Button>
</Dropdown>
}}
disabled={event.arrived}
>
{t("general.labels.email")}
</Menu.Item>
<Menu.Item
onClick={() => {
const p = parsePhoneNumber(event.job.ownr_ph1, "CA");
if (p && p.isValid()) {
openChatByPhone({
phone_num: p.formatInternational(),
jobid: event.job.id,
});
setMessage(
t("appointments.labels.reminder", {
shopname: bodyshop.shopname,
date: moment(event.start).format("MM/DD/YYYY"),
time: moment(event.start).format("HH:mm a"),
})
);
setVisible(false);
} else {
notification["error"]({
message: t("messaging.error.invalidphone"),
});
}
}}
disabled={event.arrived || !bodyshop.messagingservicesid}
>
{t("general.labels.sms")}
</Menu.Item>
</Menu>
}
>
<Button>{t("appointments.actions.sendreminder")}</Button>
</Dropdown>
) : null}
<Button onClick={() => handleCancel(event.id)} disabled={event.arrived}>
{t("appointments.actions.cancel")}
</Button>
<Button
disabled={event.arrived}
onClick={() => {
setVisible(false);
setScheduleContext({
actions: { refetch: refetch },
context: {
jobId: event.job.id,
job: event.job,
previousEvent: event.id,
color: event.color,
alt_transport: event.job && event.job.alt_transport,
note: event.note,
},
});
}}
>
{t("appointments.actions.reschedule")}
</Button>
{event.isintake ? (
<Button
disabled={event.arrived}
onClick={() => {
setVisible(false);
setScheduleContext({
actions: { refetch: refetch },
context: {
jobId: event.job.id,
job: event.job,
previousEvent: event.id,
color: event.color,
alt_transport: event.job && event.job.alt_transport,
note: event.note,
},
});
}}
>
{t("appointments.actions.reschedule")}
</Button>
) : (
<ScheduleManualEvent event={event} />
)}
{event.isintake ? (
<Link
to={{
@@ -235,7 +246,13 @@ export function ScheduleEventComponent({
);
const RegularEvent = event.isintake ? (
<div style={{ display: "flex", flexWrap: "wrap" }}>
<div
style={{
display: "flex",
flexWrap: "wrap",
height: "100%",
}}
>
<Space>
{event.note && <AlertFilled className="production-alert" />}
<strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong>
@@ -262,7 +279,7 @@ export function ScheduleEventComponent({
)}
</div>
) : (
<div>
<div style={{ height: "100%", width: "100%" }}>
<strong>{`${event.title || ""}`}</strong>
</div>
);
@@ -270,7 +287,7 @@ export function ScheduleEventComponent({
return (
<Popover
visible={visible}
onVisibleChange={(vis) => setVisible(vis)}
onVisibleChange={(vis) => !event.vacation && setVisible(vis)}
trigger="click"
content={event.block ? blockContent : popoverContent}
style={{ height: "100%", width: "100%" }}

View File

@@ -79,9 +79,19 @@ export function JobChecklistForm({
...(type === "intake" && {
scheduled_completion: values.scheduled_completion,
}),
...(type === "intake" &&
bodyshop.intakechecklist &&
bodyshop.intakechecklist.next_contact_hours &&
bodyshop.intakechecklist.next_contact_hours > 0 && {
date_next_contact: moment().add(
bodyshop.intakechecklist.next_contact_hours,
"hours"
),
}),
...(type === "deliver" && {
actual_completion: values.actual_completion,
}),
[(type === "intake" && "intakechecklist") ||
(type === "deliver" && "deliverchecklist")]: {
...values,
@@ -95,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,
@@ -161,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")}
@@ -185,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

@@ -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

@@ -89,6 +89,19 @@ export function JobsDetailHeaderActions({
});
};
const handleSuspend = (e) => {
logImEXEvent("production_toggle_alert");
//e.stopPropagation();
updateJob({
variables: {
jobId: job.id,
job: {
suspended: !job.suspended,
},
},
});
};
const statusmenu = (
<Menu key="popovermenu">
<Menu.Item
@@ -199,7 +212,11 @@ export function JobsDetailHeaderActions({
{t("jobs.actions.addtoproduction")}
</Menu.Item>
)}
<Menu.Item key="togglesuspend" onClick={handleSuspend}>
{job.suspended
? t("production.actions.unsuspend")
: t("production.actions.suspend")}
</Menu.Item>
<Menu.Item key="toggleAlert" onClick={handleAlertToggle}>
{job.production_vars && job.production_vars.alert
? t("production.labels.alertoff")

View File

@@ -1,5 +1,9 @@
import { Card, Col, Row, Space, Tag } from "antd";
import { WarningFilled, ExclamationCircleFilled } from "@ant-design/icons";
import {
WarningFilled,
ExclamationCircleFilled,
PauseCircleOutlined,
} from "@ant-design/icons";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -68,6 +72,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
{t("jobs.labels.inproduction")}
</Tag>
)}
{job.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
{job.production_vars && job.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}

View File

@@ -5,45 +5,125 @@ import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import cleanAxios from "../../utils/CleanAxios";
import formatBytes from "../../utils/formatbytes";
import yauzl from "yauzl";
import { useTreatments } from "@splitsoftware/splitio-react";
export default function JobsDocumentsDownloadButton({
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsDocumentsDownloadButton);
export function JobsDocumentsDownloadButton({
bodyshop,
galleryImages,
identifier,
}) {
const { t } = useTranslation();
const [download, setDownload] = useState(null);
const { Direct_Media_Download } = useTreatments(
["Direct_Media_Download"],
{},
bodyshop.imexshopid
);
const imagesToDownload = [
...galleryImages.images.filter((image) => image.isSelected),
// ...galleryImages.other.filter((image) => image.isSelected),
...galleryImages.other.filter((image) => image.isSelected),
];
const handleDownload = () => {
logImEXEvent("jobs_documents_download");
axios
.post("/media/download", {
ids: imagesToDownload.map((_) => _.key),
})
.then((r) => {
// window.open(r.data);
downloadAs(
r.data,
`${identifier || "documents"}.zip`,
(progressEvent) => {
setDownload((currentDownloadState) => {
return {
downloaded: progressEvent.loaded || 0,
speed:
(progressEvent.loaded || 0) -
((currentDownloadState && currentDownloadState.downloaded) ||
0),
};
});
},
() => setDownload(null)
);
});
};
function downloadProgress(progressEvent) {
setDownload((currentDownloadState) => {
return {
downloaded: progressEvent.loaded || 0,
speed:
(progressEvent.loaded || 0) -
((currentDownloadState && currentDownloadState.downloaded) || 0),
};
});
}
const handleDownload = async () => {
logImEXEvent("jobs_documents_download");
const zipUrl = await axios({
url: "/media/download",
method: "POST",
//responseType: "arraybuffer", // Important
data: { ids: imagesToDownload.map((_) => _.key) },
});
const theDownloadedZip = await cleanAxios({
url: zipUrl.data,
method: "GET",
responseType: "arraybuffer",
onDownloadProgress: downloadProgress,
});
setDownload(null);
if (Direct_Media_Download.treatment === "on") {
try {
const parentDir = await window.showDirectoryPicker({
id: "media",
startIn: "downloads",
});
const directory = await parentDir.getDirectoryHandle(identifier, {
create: true,
});
yauzl.fromBuffer(
Buffer.from(theDownloadedZip.data),
{},
(err, zipFile) => {
if (err) throw err;
zipFile.on("entry", (entry) => {
zipFile.openReadStream(entry, async (readErr, readStream) => {
if (readErr) {
zipFile.close();
throw readErr;
}
if (err) throw err;
let fileSystemHandle = await directory.getFileHandle(
entry.fileName,
{
create: true,
}
);
const writable = await fileSystemHandle.createWritable();
readStream.on("data", async function (chunk) {
await writable.write(chunk);
});
readStream.on("end", async function () {
await writable.close();
});
});
});
}
);
} catch (e) {
console.log(e);
standardMediaDownload(theDownloadedZip.data);
}
} else {
standardMediaDownload(theDownloadedZip.data);
}
function standardMediaDownload(bufferData) {
const a = document.createElement("a");
const url = window.URL.createObjectURL(new Blob([bufferData]));
a.href = url;
a.download = `${identifier || "documents"}.zip`;
a.click();
}
};
return (
<>
<Button
@@ -63,25 +143,3 @@ export default function JobsDocumentsDownloadButton({
</>
);
}
const downloadAs = (url, name, onDownloadProgress, onCompleted) => {
cleanAxios
.get(url, {
headers: {
"Content-Type": "application/octet-stream",
},
responseType: "blob",
onDownloadProgress: onDownloadProgress,
})
.then((response) => {
onCompleted && onCompleted();
const a = document.createElement("a");
const url = window.URL.createObjectURL(response.data);
a.href = url;
a.download = name;
a.click();
})
.catch((err) => {
console.log("error", err);
});
};

View File

@@ -1,4 +1,8 @@
import { SyncOutlined, ExclamationCircleFilled } from "@ant-design/icons";
import {
SyncOutlined,
ExclamationCircleFilled,
PauseCircleOutlined,
} from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { Button, Card, Grid, Input, Space, Table } from "antd";
import queryString from "query-string";
@@ -111,6 +115,9 @@ export function JobsList({ bodyshop }) {
{record.production_vars && record.production_vars.alert ? (
<ExclamationCircleFilled className="production-alert" />
) : null}
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
</Space>
</Link>
),

View File

@@ -5,7 +5,7 @@ import {
EyeInvisibleFilled,
WarningFilled,
} from "@ant-design/icons";
import { Button, Card, Space, Table } from "antd";
import { Button, Card, Form, Input, Space, Table } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -14,6 +14,7 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
import { setModalContext } from "../../redux/modals/modals.actions";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { TemplateList } from "../../utils/TemplateConstants";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import NoteUpsertModal from "../note-upsert-modal/note-upsert-modal.container";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
@@ -131,32 +132,42 @@ export function JobNotesComponent({
];
return (
<Card
title={t("jobs.labels.notes")}
extra={
<Button
onClick={() => {
setNoteUpsertContext({
actions: { refetch: refetch },
context: {
jobId: jobId,
},
});
}}
<div>
<LayoutFormRow>
<Form.Item
label={t("jobs.fields.invoice_final_note")}
name="invoice_final_note"
>
{t("notes.actions.new")}
</Button>
}
>
<NoteUpsertModal />
<Input.TextArea disabled={jobRO} />
</Form.Item>
</LayoutFormRow>
<Card
title={t("jobs.labels.notes")}
extra={
<Button
onClick={() => {
setNoteUpsertContext({
actions: { refetch: refetch },
context: {
jobId: jobId,
},
});
}}
>
{t("notes.actions.new")}
</Button>
}
>
<NoteUpsertModal />
<Table
loading={loading}
columns={columns}
rowKey="id"
dataSource={data}
/>
</Card>
<Table
loading={loading}
columns={columns}
rowKey="id"
dataSource={data}
/>
</Card>
</div>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(JobNotesComponent);

View File

@@ -10,7 +10,7 @@ import {
PageHeader,
Popconfirm,
Space,
Table
Table,
} from "antd";
import queryString from "query-string";
import React, { useState } from "react";
@@ -168,7 +168,9 @@ export function PartsOrderListTableComponent({
cost_center: pol.jobline?.part_type
? bodyshop.pbs_serialnumber || bodyshop.cdk_dealerid
? pol.jobline.part_type
? pol.jobline.part_type !== "PAE"
? pol.jobline.part_type
: null
: responsibilityCenters.defaults &&
(responsibilityCenters.defaults.costs[
pol.jobline.part_type

View File

@@ -91,68 +91,76 @@ 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.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 +175,7 @@ export function PartsOrderModalComponent({
total={fields.length}
/>
</Space>
</LayoutFormRow>
</div>
</Form.Item>
))}
</div>

View File

@@ -0,0 +1,84 @@
import { Button, Card, Form, InputNumber, Popover } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(PrintCenterJobsLabels);
export function PrintCenterJobsLabels({ bodyshop, jobId }) {
const [isModalVisible, setIsModalVisible] = useState(false);
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const [form] = Form.useForm();
const handleOk = () => {
form.submit();
setIsModalVisible(false);
};
const handleCancel = () => {
setIsModalVisible(false);
setLoading(false);
};
const handleFinish = async (values) => {
const { sendtype, ...restVals } = values;
setLoading(true);
await GenerateDocument(
{
name: TemplateList("job_special").folder_label_multiple.key,
variables: { id: jobId },
context: restVals,
},
{},
"p",
jobId
);
setLoading(false);
setIsModalVisible(false);
};
const content = (
<Card>
<Form
onFinish={handleFinish}
autoComplete={"off"}
layout="vertical"
form={form}
>
<Form.Item
label={t("printcenter.jobs.labels.position")}
name="position"
>
<InputNumber min={1} precision={0} />
</Form.Item>
<Form.Item label={t("printcenter.jobs.labels.count")} name="count">
<InputNumber min={1} precision={0} max={99} />
</Form.Item>
<Button type="primary" loading={loading} onClick={handleOk}>
{t("general.actions.print")}
</Button>
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
</Form>
</Card>
);
return (
<Popover content={content} visible={isModalVisible}>
<Button onClick={() => setIsModalVisible(true)}>
{t("printcenter.jobs.labels.labels")}
</Button>
</Popover>
);
}

View File

@@ -9,6 +9,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import { TemplateList } from "../../utils/TemplateConstants";
import Jobd3RdPartyModal from "../job-3rd-party-modal/job-3rd-party-modal.component";
import PrintCenterItem from "../print-center-item/print-center-item.component";
import PrintCenterJobsLabels from "../print-center-jobs-labels/print-center-jobs-labels.component";
import PrintCenterSpeedPrint from "../print-center-speed-print/print-center-speed-print.component";
const mapStateToProps = createStructuredSelector({
printCenterModal: selectPrintCenter,
@@ -52,6 +53,7 @@ export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
<Card
extra={
<Space wrap>
<PrintCenterJobsLabels jobId={jobId} />
<Jobd3RdPartyModal jobId={jobId} />
<Input.Search
onChange={(e) => setSearch(e.target.value)}

View File

@@ -1,4 +1,8 @@
import { CalendarOutlined, EyeFilled } from "@ant-design/icons";
import {
CalendarOutlined,
EyeFilled,
PauseCircleOutlined,
} from "@ant-design/icons";
import { Card, Col, Row, Space } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -8,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,
@@ -33,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"
@@ -40,6 +54,9 @@ export default function ProductionBoardCard(
title={
<Space>
<ProductionAlert record={card} key="alert" />
{card.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
<span style={{ fontWeight: "bolder" }}>
{card.ro_number || t("general.labels.na")}
</span>
@@ -138,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

@@ -33,6 +33,7 @@ export function ProductionColumnsComponent({
setColumns([
...columns,
...dataSource({
bodyshop,
technician,
state: tableState,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,

View File

@@ -41,7 +41,11 @@ export default function ProductionListColumnBodyPriority({ record }) {
title={t("production.actions.bodypriority-set")}
>
{new Array(15).fill().map((value, index) => (
<Menu.Item key={index + 1}>{index + 1}</Menu.Item>
<Menu.Item key={index + 1}>
<div style={{ marginLeft: "2rem", marginRight: "2rem" }}>
{index + 1}
</div>
</Menu.Item>
))}
</Menu.SubMenu>
</Menu>

View File

@@ -1,3 +1,5 @@
import { PauseCircleOutlined } from "@ant-design/icons";
import { Space } from "antd";
import i18n from "i18next";
import moment from "moment";
import React from "react";
@@ -16,9 +18,10 @@ 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";
const r = ({ technician, state, activeStatuses }) => {
const r = ({ technician, state, activeStatuses, bodyshop }) => {
return [
{
title: i18n.t("jobs.actions.viewdetail"),
@@ -43,9 +46,19 @@ const r = ({ technician, state, activeStatuses }) => {
technician ? (
<Link to={`/tech/joblookup?selected=${record.id}`}>
{record.ro_number}
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
</Link>
) : (
<Link to={`/manage/jobs/${record.id}`}>{record.ro_number}</Link>
<Link to={`/manage/jobs/${record.id}`}>
<Space>
{record.ro_number}
{record.suspended && (
<PauseCircleOutlined style={{ color: "orangered" }} />
)}
</Space>
</Link>
),
},
{
@@ -96,7 +109,7 @@ const r = ({ technician, state, activeStatuses }) => {
state.sortedInfo.columnKey === "scheduled_completion" &&
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="scheduled_completion" />
<ProductionListDate record={record} field="scheduled_completion" pastIndicator />
),
},
{
@@ -143,7 +156,7 @@ const r = ({ technician, state, activeStatuses }) => {
state.sortedInfo.columnKey === "scheduled_delivery" &&
state.sortedInfo.order,
render: (text, record) => (
<ProductionListDate record={record} field="scheduled_delivery" />
<ProductionListDate record={record} field="scheduled_delivery" pastIndicator/>
),
},
{
@@ -239,6 +252,29 @@ const r = ({ technician, state, activeStatuses }) => {
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",
@@ -353,6 +389,14 @@ const r = ({ technician, state, activeStatuses }) => {
title: i18n.t("jobs.fields.employee_body"),
dataIndex: "employee_body",
key: "employee_body",
sortOrder:
state.sortedInfo.columnKey === "employee_body" &&
state.sortedInfo.order,
sorter: (a, b) =>
alphaSort(
bodyshop.employees.find((e) => e.id === a.employee_body)?.first_name,
bodyshop.employees.find((e) => e.id === b.employee_body)?.first_name
),
render: (text, record) => (
<ProductionListEmployeeAssignment
record={record}
@@ -364,6 +408,14 @@ const r = ({ technician, state, activeStatuses }) => {
title: i18n.t("jobs.fields.employee_prep"),
dataIndex: "employee_prep",
key: "employee_prep",
sortOrder:
state.sortedInfo.columnKey === "employee_prep" &&
state.sortedInfo.order,
sorter: (a, b) =>
alphaSort(
bodyshop.employees.find((e) => e.id === a.employee_prep)?.first_name,
bodyshop.employees.find((e) => e.id === b.employee_prep)?.first_name
),
render: (text, record) => (
<ProductionListEmployeeAssignment
record={record}
@@ -375,6 +427,13 @@ const r = ({ technician, state, activeStatuses }) => {
title: i18n.t("jobs.fields.employee_csr"),
dataIndex: "employee_csr",
key: "employee_csr",
sortOrder:
state.sortedInfo.columnKey === "employee_csr" && state.sortedInfo.order,
sorter: (a, b) =>
alphaSort(
bodyshop.employees.find((e) => e.id === a.employee_csr)?.first_name,
bodyshop.employees.find((e) => e.id === b.employee_csr)?.first_name
),
render: (text, record) => (
<ProductionListEmployeeAssignment record={record} type="employee_csr" />
),
@@ -383,6 +442,16 @@ const r = ({ technician, state, activeStatuses }) => {
title: i18n.t("jobs.fields.employee_refinish"),
dataIndex: "employee_refinish",
key: "employee_refinish",
sortOrder:
state.sortedInfo.columnKey === "employee_refinish" &&
state.sortedInfo.order,
sorter: (a, b) =>
alphaSort(
bodyshop.employees.find((e) => e.id === a.employee_refinish)
?.first_name,
bodyshop.employees.find((e) => e.id === b.employee_refinish)
?.first_name
),
render: (text, record) => (
<ProductionListEmployeeAssignment
record={record}

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,7 +62,7 @@ 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}
@@ -72,17 +87,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

@@ -41,7 +41,11 @@ export default function ProductionListColumnDetailPriority({ record }) {
title={t("production.actions.detailpriority-set")}
>
{new Array(15).fill().map((value, index) => (
<Menu.Item key={index + 1}>{index + 1}</Menu.Item>
<Menu.Item key={index + 1}>
<div style={{ marginLeft: "2rem", marginRight: "2rem" }}>
{index + 1}
</div>
</Menu.Item>
))}
</Menu.SubMenu>
</Menu>

View File

@@ -41,7 +41,11 @@ export default function ProductionListColumnPaintPriority({ record }) {
title={t("production.actions.paintpriority-set")}
>
{new Array(15).fill().map((value, index) => (
<Menu.Item key={index + 1}>{index + 1}</Menu.Item>
<Menu.Item key={index + 1}>
<div style={{ marginLeft: "2rem", marginRight: "2rem" }}>
{index + 1}
</div>
</Menu.Item>
))}
</Menu.SubMenu>
</Menu>

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

@@ -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

@@ -38,6 +38,7 @@ export function ProductionListTable({
.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
technician,
state,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,

View File

@@ -76,6 +76,7 @@ export function ProductionListTable({
matchingColumnConfig.columns.columnKeys.map((k) => {
return {
...ProductionListColumns({
bodyshop,
technician,
state,
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
@@ -87,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,
@@ -264,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

@@ -38,13 +38,11 @@ export default function ProductionListTableContainer() {
updatedJobs.jobs,
(a, b) => a.id === b.id && a.updated_at === b.updated_at
);
console.log(jobDiff);
if (jobDiff.length > 1) {
getUpdatedJobsData(jobDiff.map((j) => j.id));
} else if (jobDiff.length === 1) {
console.log("length was 1");
jobDiff.forEach((job) => {
console.log("Job ", job);
getUpdatedJobData(job.id);
});
}

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

@@ -10,13 +10,18 @@ import Event from "../job-at-change/schedule-event.container";
import HeaderComponent from "./schedule-calendar-header.component";
import "./schedule-calendar.styles.scss";
import JobDetailCards from "../job-detail-cards/job-detail-cards.component";
import { selectProblemJobs } from "../../redux/application/application.selectors";
import { Alert } from "antd";
import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
problemJobs: selectProblemJobs,
});
const localizer = momentLocalizer(moment);
export function ScheduleCalendarWrapperComponent({
bodyshop,
problemJobs,
data,
refetch,
defaultView,
@@ -26,7 +31,7 @@ export function ScheduleCalendarWrapperComponent({
}) {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const { t } = useTranslation();
const handleEventPropStyles = (event, start, end, isSelected) => {
return {
...(event.color
@@ -48,6 +53,18 @@ export function ScheduleCalendarWrapperComponent({
return (
<>
<JobDetailCards />
{problemJobs &&
problemJobs.map((problem) => (
<Alert
key={problem.id}
type="error"
message={t("appointments.labels.dataconsistency", {
ro_number: problem.ro_number,
code: problem.code,
})}
/>
))}
<Calendar
events={data}
defaultView={search.view || defaultView || "week"}

View File

@@ -3,8 +3,9 @@ import { Button, Card, Col, PageHeader, Row, Space } from "antd";
import React from "react";
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
//import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
import ScheduleProductionList from "../schedule-production-list/schedule-production-list.component";
import ScheduleVerifyIntegrity from "../schedule-verify-integrity/schedule-verify-integrity.component";
export default function ScheduleCalendarComponent({ data, refetch }) {
return (
@@ -15,6 +16,7 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
<PageHeader
extra={
<Space wrap>
<ScheduleVerifyIntegrity />
<Button
onClick={() => {
refetch();
@@ -23,9 +25,8 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
<SyncOutlined />
</Button>
<ScheduleProductionList />
{
// <ScheduleManualEvent />
}
<ScheduleManualEvent />
</Space>
}
/>

View File

@@ -10,6 +10,7 @@ import ScheduleCalendarComponent from "./schedule-calendar.component";
import { calculateScheduleLoad } from "../../redux/application/application.actions";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import moment from "moment";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
@@ -26,7 +27,12 @@ export function ScheduleCalendarContainer({ calculateScheduleLoad }) {
const { loading, error, data, refetch } = useQuery(
QUERY_ALL_ACTIVE_APPOINTMENTS,
{
variables: { start: range.start.toDate(), end: range.end.toDate() },
variables: {
start: range.start.toDate(),
end: range.end.toDate(),
startd: range.start,
endd: range.end,
},
skip: !!!range.start || !!!range.end,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
@@ -39,15 +45,30 @@ export function ScheduleCalendarContainer({ calculateScheduleLoad }) {
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
let normalizedData = data.appointments.map((e) => {
//Required becuase Hasura returns a string instead of a date object.
return Object.assign(
{},
e,
{ start: new Date(e.start) },
{ end: new Date(e.end) }
);
});
let normalizedData = [
...data.appointments.map((e) => {
//Required becuase Hasura returns a string instead of a date object.
return Object.assign(
{},
e,
{ start: new Date(e.start) },
{ end: new Date(e.end) }
);
}),
...data.employee_vacation.map((e) => {
//Required becuase Hasura returns a string instead of a date object.
return {
...e,
title: `${
(e.employee.first_name && e.employee.first_name.substr(0, 1)) || ""
} ${e.employee.last_name || ""} OUT`,
color: "red",
start: moment(e.start).startOf("day").toDate(),
end: moment(e.end).startOf("day").toDate(),
vacation: true,
};
}),
];
return (
<ScheduleCalendarComponent

View File

@@ -5,11 +5,14 @@ import { QUERY_APPOINTMENT_BY_DATE } from "../../graphql/appointments.queries";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import moment from "moment";
import { useTranslation } from "react-i18next";
export default function ScheduleDayViewContainer({ day }) {
const { loading, error, data } = useQuery(QUERY_APPOINTMENT_BY_DATE, {
variables: {
start: moment(day).startOf("day"),
end: moment(day).endOf("day"),
startd: moment(day).startOf("day").format("YYYY-MM-DD"),
endd: moment(day).add(1, "day").format("YYYY-MM-DD"),
},
skip: !moment(day).isValid(),
fetchPolicy: "network-only",
@@ -22,15 +25,30 @@ export default function ScheduleDayViewContainer({ day }) {
let normalizedData;
if (data) {
normalizedData = data.appointments.map((e) => {
//Required becuase Hasura returns a string instead of a date object.
return Object.assign(
{},
e,
{ start: new Date(e.start) },
{ end: new Date(e.end) }
);
});
normalizedData = [
...data.appointments.map((e) => {
//Required becuase Hasura returns a string instead of a date object.
return Object.assign(
{},
e,
{ start: new Date(e.start) },
{ end: new Date(e.end) }
);
}),
...data.employee_vacation.map((e) => {
//Required becuase Hasura returns a string instead of a date object.
return {
...e,
title: `${
(e.employee.first_name && e.employee.first_name.substr(0, 1)) || ""
} ${e.employee.last_name || ""} OUT`,
color: "red",
start: moment(e.start).startOf("day").toDate(),
end: moment(e.end).startOf("day").toDate(),
vacation: true,
};
}),
];
}
return (

View File

@@ -5,8 +5,9 @@ import {
Input,
Row,
Select,
Space, Switch,
Typography
Space,
Switch,
Typography,
} from "antd";
import axios from "axios";
import moment from "moment";
@@ -75,10 +76,14 @@ export function ScheduleJobModalComponent({
<Row gutter={[16, 16]}>
<Col span={12}>
<Space>
<Typography.Title level={3}>{lbrHrsData?.jobs_by_pk?.ro_number}</Typography.Title>
<Typography.Title
level={4}
>{`B/R Hrs:${lbrHrsData?.jobs_by_pk.labhrs?.aggregate.sum.mod_lb_hrs}/${lbrHrsData?.jobs_by_pk.larhrs?.aggregate.sum.mod_lb_hrs}`}</Typography.Title>
<Typography.Title level={3}>
{lbrHrsData?.jobs_by_pk?.ro_number}
</Typography.Title>
<Typography.Title level={4}>{`B/R Hrs:${
lbrHrsData?.jobs_by_pk.labhrs?.aggregate?.sum?.mod_lb_hrs || 0
}/${
lbrHrsData?.jobs_by_pk.larhrs?.aggregate?.sum?.mod_lb_hrs || 0
}`}</Typography.Title>
</Space>
<LayoutFormRow grow>
<Form.Item
@@ -183,10 +188,9 @@ export function ScheduleJobModalComponent({
/>
</Col>
<Col span={12}>
<Form.Item shouldUpdate>
<Form.Item shouldUpdate={(prev, cur) => prev.start !== cur.start}>
{() => {
const values = form.getFieldsValue();
return (
<div className="schedule-job-modal">
<ScheduleDayViewContainer day={values.start} />

View File

@@ -1,17 +1,32 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Form, Input, Popover, Space } from "antd";
import { Button, Card, Form, Input, Popover, Select, 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 {
INSERT_APPOINTMENT,
INSERT_MANUAL_APPT,
UPDATE_APPOINTMENT,
} from "../../graphql/appointments.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
export default function ScheduleManualEvent({ event }) {
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScheduleManualEvent);
export function ScheduleManualEvent({ bodyshop, event }) {
const { t } = useTranslation();
const [insertAppointment] = useMutation(INSERT_APPOINTMENT);
const [insertAppointment] = useMutation(INSERT_MANUAL_APPT);
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
@@ -22,26 +37,27 @@ export default function ScheduleManualEvent({ event }) {
useEffect(() => {
if (visibility && event) {
form.setFieldsValue({ event });
form.setFieldsValue(event);
}
}, [visibility, form, event]);
useEffect(() => {
// if (entryData && entryData.scoreboard && entryData.scoreboard[0]) {
// console.log("Setting FOrm");
// // form.setFieldsValue(entryData.scoreboard[0]);
// }
}, [form]);
const handleFinish = async (values) => {
logImEXEvent("job_close_add_to_scoreboard");
logImEXEvent("schedule_manual_event");
setLoading(true);
try {
if (event && event.id) {
updateAppointment();
updateAppointment({
variables: { appid: event.id, app: values },
refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"],
});
} else {
insertAppointment();
insertAppointment({
variables: {
apt: { ...values, isintake: false, bodyshopid: bodyshop.id },
},
refetchQueries: ["QUERY_ALL_ACTIVE_APPOINTMENTS"],
});
}
} catch (error) {
console.log(error);
@@ -56,8 +72,8 @@ export default function ScheduleManualEvent({ event }) {
<div>
<Form form={form} layout="vertical" onFinish={handleFinish}>
<Form.Item
label={t("schedule.fields.note")}
name="note"
label={t("appointments.fields.title")}
name="title"
rules={[
{
required: true,
@@ -67,8 +83,11 @@ export default function ScheduleManualEvent({ event }) {
>
<Input />
</Form.Item>
<Form.Item label={t("appointments.fields.note")} name="note">
<Input />
</Form.Item>
<Form.Item
label={t("schedule.fields.start")}
label={t("appointments.fields.start")}
name="start"
rules={[
{
@@ -80,17 +99,42 @@ export default function ScheduleManualEvent({ event }) {
<FormDateTimePickerComponent />
</Form.Item>
<Form.Item
label={t("schedule.fields.end")}
label={t("appointments.fields.end")}
name="end"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
async validator(rule, value) {
if (value) {
const { start } = form.getFieldsValue();
if (moment(start).isAfter(moment(value))) {
return Promise.reject(
t("employees.labels.endmustbeafterstart")
);
} else {
return Promise.resolve();
}
} else {
return Promise.resolve();
}
},
}),
]}
>
<FormDateTimePickerComponent />
</Form.Item>
<Form.Item label={t("appointments.fields.color")} name="color">
<Select>
{bodyshop.appt_colors.map((col, idx) => (
<Select.Option key={idx} value={col.color.hex}>
{col.label}
</Select.Option>
))}
</Select>
</Form.Item>
<Space wrap>
<Button type="primary" htmlType="submit">
@@ -112,7 +156,9 @@ export default function ScheduleManualEvent({ event }) {
return (
<Popover content={overlay} visible={visibility}>
<Button loading={loading} onClick={handleClick}>
{t("schedule.labels.manualevent")}
{event
? t("appointments.actions.reschedule")
: t("appointments.labels.manualevent")}
</Button>
</Popover>
);

View File

@@ -0,0 +1,60 @@
import { useApolloClient } from "@apollo/client";
import { Button } from "antd";
import moment from "moment";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries";
import { selectCurrentUser } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScheduleVerifyIntegrity);
export function ScheduleVerifyIntegrity({ currentUser }) {
const [loading, setLoading] = useState(false);
const client = useApolloClient();
const handleVerify = async () => {
setLoading(true);
const {
data: { arrJobs, compJobs, prodJobs },
} = await client.query({
query: QUERY_SCHEDULE_LOAD_DATA,
variables: { start: moment(), end: moment().add(180, "days") },
});
//check that the leaving jobs are either in the arriving list, or in production.
const issues = [];
compJobs.forEach((j) => {
const inProdJobs = prodJobs.find((p) => p.id === j.id);
const inArrJobs = arrJobs.find((p) => p.id === j.id);
if (!(inProdJobs || inArrJobs)) {
// NOT FOUND!
issues.push(j);
}
});
console.log(
"The following completing jobs are not in production, or are arriving within the next 180 days. ",
issues
);
setLoading(false);
};
if (currentUser.email === "patrick@imex.prod")
return (
<Button loading={loading} onClick={handleVerify}>
Developer Use Only - Verify Schedule Integrity
</Button>
);
else return null;
}

View File

@@ -43,19 +43,28 @@ export function ScoreboardDisplayComponent({
useEffect(() => {
//Update the locals.
async function setMomentSettings() {
const {
data: { appointments },
} = await client.query({
query: GET_BLOCKED_DAYS,
variables: {
start: moment().startOf("month"),
end: moment().endOf("month"),
},
});
let appointments;
if (!bodyshop.scoreboard_target.ignoreblockeddays) {
const { data } = await client.query({
query: GET_BLOCKED_DAYS,
variables: {
start: moment().startOf("month"),
end: moment().endOf("month"),
},
});
appointments = data.appointments;
}
moment.updateLocale("ca", {
workingWeekdays: translateSettingsToWorkingDays(bodyshop.workingdays),
holidays: appointments.map((h) => moment(h.start).format("MM-DD-YYYY")),
...(appointments
? {
holidays: appointments.map((h) =>
moment(h.start).format("MM-DD-YYYY")
),
}
: {}),
holidayFormat: "MM-DD-YYYY",
});
}

View File

@@ -0,0 +1,124 @@
import { useMutation } from "@apollo/client";
import { Button, Card, Form, notification, Popover, Space } from "antd";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_VACATION } from "../../graphql/employees.queries";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
export default function ShopEmployeeAddVacation({ employee }) {
const { t } = useTranslation();
const [insertVacation] = useMutation(INSERT_VACATION);
const [loading, setLoading] = useState(false);
const [form] = Form.useForm();
const [visibility, setVisibility] = useState(false);
const handleFinish = async (values) => {
logImEXEvent("employee_add_vacation");
setLoading(true);
let result;
result = await insertVacation({
variables: { vacation: { ...values, employeeid: employee.id } },
update(cache, { data }) {
cache.modify({
id: cache.identify({ id: employee.id, __typename: "employees" }),
fields: {
employee_vacations(ex) {
return [data.insert_employee_vacation_one, ...ex];
},
},
});
},
});
if (!!result.errors) {
notification["error"]({
message: t("employees.errors.adding", {
message: JSON.stringify(result.errors),
}),
});
} else {
notification["success"]({
message: t("employees.successes.added"),
});
}
setLoading(false);
setVisibility(false);
};
const overlay = (
<Card>
<Form form={form} layout="vertical" onFinish={handleFinish}>
<Form.Item
label={t("employees.fields.vacation.start")}
name="start"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<FormDatePicker />
</Form.Item>
<Form.Item
label={t("employees.fields.vacation.end")}
name="end"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
async validator(rule, value) {
if (value) {
const { start } = form.getFieldsValue();
if (moment(start).isAfter(moment(value))) {
return Promise.reject(
t("employees.labels.endmustbeafterstart")
);
} else {
return Promise.resolve();
}
} else {
return Promise.resolve();
}
},
}),
]}
>
<FormDatePicker />
</Form.Item>
<Space wrap>
<Button type="primary" htmlType="submit">
{t("general.actions.save")}
</Button>
<Button onClick={() => setVisibility(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
</Card>
);
const handleClick = (e) => {
setVisibility(true);
};
return (
<Popover content={overlay} visible={visibility}>
<Button
loading={loading}
disabled={!employee?.active}
onClick={handleClick}
>
{t("employees.actions.addvacation")}
</Button>
</Popover>
);
}

View File

@@ -1,20 +1,41 @@
import { DeleteFilled } from "@ant-design/icons";
import { Button, Card, Form, Input, InputNumber, Select, Switch } from "antd";
import { useApolloClient, useMutation, useQuery } from "@apollo/client";
import {
Button,
Card,
Form,
Input,
InputNumber,
notification,
Select,
Switch,
Table,
} from "antd";
import { useForm } from "antd/es/form/Form";
import moment from "moment";
import querystring from "query-string";
import React, { useEffect } from "react";
import { useApolloClient } from "@apollo/client";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {
CHECK_EMPLOYEE_NUMBER,
DELETE_VACATION,
INSERT_EMPLOYEES,
QUERY_EMPLOYEE_BY_ID,
QUERY_USERS_BY_EMAIL,
UPDATE_EMPLOYEE,
} from "../../graphql/employees.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CiecaSelect from "../../utils/Ciecaselect";
import { DateFormatter } from "../../utils/DateFormatter";
import AlertComponent from "../alert/alert.component";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import CiecaSelect from "../../utils/Ciecaselect";
import ShopEmployeeAddVacation from "./shop-employees-add-vacation.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -23,20 +44,129 @@ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function ShopEmployeesFormComponent({
bodyshop,
form,
selectedEmployee,
handleFinish,
}) {
export function ShopEmployeesFormComponent({ bodyshop }) {
const { t } = useTranslation();
const [form] = useForm();
const history = useHistory();
const search = querystring.parse(useLocation().search);
const [deleteVacation] = useMutation(DELETE_VACATION);
const { error, data } = useQuery(QUERY_EMPLOYEE_BY_ID, {
variables: { id: search.employeeId },
skip: !search.employeeId || search.employeeId === "new",
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const client = useApolloClient();
useEffect(() => {
if (selectedEmployee) form.resetFields();
}, [selectedEmployee, form]);
if (data && data.employees_by_pk) form.setFieldsValue(data.employees_by_pk);
else {
form.resetFields();
}
}, [form, data, search.employeeId]);
if (!selectedEmployee) return null;
const [updateEmployee] = useMutation(UPDATE_EMPLOYEE);
const [insertEmployees] = useMutation(INSERT_EMPLOYEES);
const handleFinish = (values) => {
if (search.employeeId && search.employeeId !== "new") {
//Update a record.
logImEXEvent("shop_employee_update");
updateEmployee({
variables: {
id: search.employeeId,
employee: {
...values,
user_email: values.user_email === "" ? null : values.user_email,
},
},
})
.then((r) => {
notification["success"]({
message: t("employees.successes.save"),
});
})
.catch((error) => {
notification["error"]({
message: t("employees.errors.save", {
message: JSON.stringify(error),
}),
});
});
} else {
//New record, insert it.
logImEXEvent("shop_employee_insert");
insertEmployees({
variables: { employees: [{ ...values, shopid: bodyshop.id }] },
refetchQueries: ["QUERY_EMPLOYEES"],
}).then((r) => {
search.employeeId = r.data.insert_employees.returning[0].id;
history.push({ search: querystring.stringify(search) });
notification["success"]({
message: t("employees.successes.save"),
});
});
}
};
if (!search.employeeId) return null;
if (error) return <AlertComponent message={error.message} type="error" />;
const columns = [
{
title: t("employees.fields.vacation.start"),
dataIndex: "start",
key: "start",
render: (text, record) => <DateFormatter>{text}</DateFormatter>,
},
{
title: t("employees.fields.vacation.end"),
dataIndex: "end",
key: "end",
render: (text, record) => <DateFormatter>{text}</DateFormatter>,
},
{
title: t("employees.fields.vacation.length"),
dataIndex: "length",
key: "length",
render: (text, record) =>
moment(record.end).diff(moment(record.start), "days", true).toFixed(1),
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
render: (text, record) => (
<Button
onClick={async () => {
await deleteVacation({
variables: { id: record.id },
update(cache) {
cache.modify({
id: cache.identify({
id: data.employees_by_pk.id,
__typename: "employees",
}),
fields: {
employee_vacations(ex, { readField }) {
return ex.filter(
(vacaref) => record.id !== readField("id", vacaref)
);
},
},
});
},
});
}}
>
<DeleteFilled />
</Button>
),
},
];
return (
<Card
@@ -51,15 +181,6 @@ export function ShopEmployeesFormComponent({
autoComplete={"off"}
layout="vertical"
form={form}
initialValues={{
...selectedEmployee,
hire_date: selectedEmployee.hire_date
? moment(selectedEmployee.hire_date)
: null,
termination_date: selectedEmployee.termination_date
? moment(selectedEmployee.termination_date)
: null,
}}
>
<LayoutFormRow>
<Form.Item
@@ -288,6 +409,15 @@ export function ShopEmployeesFormComponent({
}}
</Form.List>
</Form>
<Table
title={() => (
<ShopEmployeeAddVacation employee={data && data.employees_by_pk} />
)}
columns={columns}
rowKey={"id"}
dataSource={data ? data.employees_by_pk.employee_vacations : []}
/>
</Card>
);
}

View File

@@ -1,19 +1,22 @@
import { Button, Table } from "antd";
import queryString from "query-string";
import React from "react";
import { useTranslation } from "react-i18next";
export default function ShopEmployeesListComponent({
loading,
employees,
selectedEmployee,
setSelectedEmployee,
handleDelete,
}) {
import { useHistory, useLocation } from "react-router-dom";
export default function ShopEmployeesListComponent({ loading, employees }) {
const { t } = useTranslation();
const history = useHistory();
const search = queryString.parse(useLocation().search);
const handleOnRowClick = (record) => {
if (record) {
setSelectedEmployee(record);
} else setSelectedEmployee({});
search.employeeId = record.id;
history.push({ search: queryString.stringify(search) });
} else {
delete search.employeeId;
history.push({ search: queryString.stringify(search) });
}
};
const columns = [
{
@@ -41,18 +44,6 @@ export default function ShopEmployeesListComponent({
? t("employees.labels.flat_rate")
: t("employees.labels.straight_time"),
},
// {
// title: t("employees.labels.actions"),
// dataIndex: "actions",
// key: "actions",
// render: (text, record) => (
// <div>
// <Button key="delete" onClick={() => handleDelete(record.id)}>
// {t("general.actions.delete")}
// </Button>
// </div>
// )
// }
];
return (
<div>
@@ -62,7 +53,8 @@ export default function ShopEmployeesListComponent({
<Button
type="primary"
onClick={() => {
setSelectedEmployee({});
search.employeeId = "new";
history.push({ search: queryString.stringify(search) });
}}
>
{t("employees.actions.new")}
@@ -76,10 +68,11 @@ export default function ShopEmployeesListComponent({
dataSource={employees}
rowSelection={{
onSelect: (props) => {
setSelectedEmployee(props);
search.employeeId = props.id;
history.push({ search: queryString.stringify(search) });
},
type: "radio",
selectedRowKeys: [(selectedEmployee && selectedEmployee.id) || null],
selectedRowKeys: [search.employeeId],
}}
onRow={(record, rowIndex) => {
return {

View File

@@ -1,29 +0,0 @@
import React from "react";
import ShopEmployeesFormComponent from "./shop-employees-form.component";
import ShopEmployeesListComponent from "./shop-employees-list.component";
export default function ShopEmployeeComponent({
form,
loading,
employees,
employeeState,
handleFinish,
handleDelete
}) {
const [selectedEmployee, setSelectedEmployee] = employeeState;
return (
<div>
<ShopEmployeesListComponent
employees={employees}
loading={loading}
selectedEmployee={selectedEmployee}
setSelectedEmployee={setSelectedEmployee}
handleDelete={handleDelete}
/>
<ShopEmployeesFormComponent
handleFinish={handleFinish}
form={form}
selectedEmployee={selectedEmployee}
/>
</div>
);
}

View File

@@ -1,120 +1,36 @@
import { useMutation, useQuery } from "@apollo/client";
import { Form, notification } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useQuery } from "@apollo/client";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import {
DELETE_EMPLOYEE,
INSERT_EMPLOYEES,
QUERY_EMPLOYEES,
UPDATE_EMPLOYEE,
} from "../../graphql/employees.queries";
import { QUERY_EMPLOYEES } from "../../graphql/employees.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AlertComponent from "../alert/alert.component";
import ShopEmployeeComponent from "./shop-employees.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,
});
function ShopEmployeesContainer({ bodyshop }) {
const [form] = Form.useForm();
const { t } = useTranslation();
const employeeState = useState(null);
const { loading, error, data, refetch } = useQuery(QUERY_EMPLOYEES, {
const { loading, error, data } = useQuery(QUERY_EMPLOYEES, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const [updateEmployee] = useMutation(UPDATE_EMPLOYEE);
const [insertEmployees] = useMutation(INSERT_EMPLOYEES);
const [deleteEmployee] = useMutation(DELETE_EMPLOYEE);
const handleDelete = (id) => {
logImEXEvent("shop_employee_delete");
deleteEmployee({ variables: { id: id } })
.then((r) => {
notification["success"]({
message: t("employees.successes.delete"),
});
employeeState[1](null);
refetch().then((r) => form.resetFields());
})
.catch((error) => {
notification["error"]({
message: t("employees.errors.delete", {
message: JSON.stringify(error),
}),
});
});
};
const handleFinish = (values) => {
if (employeeState[0].id) {
//Update a record.
logImEXEvent("shop_employee_update");
updateEmployee({
variables: {
id: employeeState[0].id,
employee: {
...values,
user_email: values.user_email === "" ? null : values.user_email,
},
},
})
.then((r) => {
notification["success"]({
message: t("employees.successes.save"),
});
employeeState[1](null);
refetch().then((r) => form.resetFields());
})
.catch((error) => {
notification["error"]({
message: t("employees.errors.save", {
message: JSON.stringify(error),
}),
});
});
} else {
//New record, insert it.
logImEXEvent("shop_employee_insert");
insertEmployees({
variables: { employees: [{ ...values, shopid: bodyshop.id }] },
}).then((r) => {
notification["success"]({
message: t("employees.successes.save"),
});
employeeState[1](null);
refetch().catch((error) => {
notification["error"]({
message: t("employees.errors.save", {
message: JSON.stringify(error),
}),
});
});
});
}
};
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<ShopEmployeeComponent
handleFinish={handleFinish}
handleDelete={handleDelete}
form={form}
loading={loading}
employeeState={employeeState}
employees={data ? data.employees : []}
/>
<div>
<RbacWrapper action="employees:page">
<ShopEmployeesListComponent
employees={data ? data.employees : []}
loading={loading}
/>
<ShopEmployeesFormComponent />
</RbacWrapper>
</div>
);
}
export default connect(mapStateToProps, null)(ShopEmployeesContainer);

View File

@@ -354,6 +354,13 @@ export default function ShopInfoGeneral({ form }) {
>
<InputNumber min={0} max={12} precision={0} />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.ignoreblockeddays")}
name={["scoreboard_target", "ignoreblockeddays"]}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.prodtargethrs")}
name={["prodtargethrs"]}
@@ -472,7 +479,6 @@ export default function ShopInfoGeneral({ form }) {
label={t("bodyshop.fields.md_email_cc", { template: "parts_order" })}
rules={[
{
//message: t("general.validation.required"),
type: "array",
},
@@ -492,7 +498,6 @@ export default function ShopInfoGeneral({ form }) {
label={t("bodyshop.fields.md_ded_notes")}
rules={[
{
//message: t("general.validation.required"),
type: "array",
},

View File

@@ -180,6 +180,12 @@ export default function ShopInfoIntakeChecklistComponent({ form }) {
))}
</Select>
</Form.Item>
<Form.Item
name={["intakechecklist", "next_contact_hours"]}
label={t("bodyshop.fields.intake.next_contact_hours")}
>
<InputNumber min={0} precision={0} />
</Form.Item>
</SelectorDiv>
<LayoutFormRow header={t("bodyshop.labels.deliverchecklist")}>

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")}
@@ -158,11 +166,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

@@ -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

View File

@@ -4,7 +4,21 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
query QUERY_ALL_ACTIVE_APPOINTMENTS(
$start: timestamptz!
$end: timestamptz!
$startd: date!
$endd: date!
) {
employee_vacation(
where: { _or: [{ start: { _gte: $startd } }, { end: { _lte: $endd } }] }
) {
id
start
end
employee {
id
last_name
first_name
}
}
appointments(
where: {
canceled: { _eq: false }
@@ -77,6 +91,26 @@ export const INSERT_APPOINTMENT_BLOCK = gql`
}
`;
export const INSERT_MANUAL_APPT = gql`
mutation INSERT_MANUAL_APPT($apt: appointments_insert_input!) {
insert_appointments_one(object: $apt) {
start
id
end
arrived
title
isintake
block
color
note
canceled
job {
id
}
}
}
`;
export const INSERT_APPOINTMENT = gql`
mutation INSERT_APPOINTMENT(
$app: [appointments_insert_input!]!
@@ -109,7 +143,24 @@ export const INSERT_APPOINTMENT = gql`
`;
export const QUERY_APPOINTMENT_BY_DATE = gql`
query QUERY_APPOINTMENT_BY_DATE($start: timestamptz, $end: timestamptz) {
query QUERY_APPOINTMENT_BY_DATE(
$start: timestamptz
$end: timestamptz
$startd: date!
$endd: date!
) {
employee_vacation(
where: { _or: [{ start: { _gte: $startd } }, { end: { _lte: $endd } }] }
) {
id
start
end
employee {
id
last_name
first_name
}
}
appointments(
where: { start: { _lte: $end, _gte: $start }, canceled: { _eq: false } }
) {
@@ -212,7 +263,9 @@ export const QUERY_APPOINTMENTS_BY_JOBID = gql`
`;
export const QUERY_SCHEDULE_LOAD_DATA = gql`
query QUERY_SCHEDULE_LOAD_DATA($start: timestamptz!, $end: timestamptz!) {
prodJobs: jobs(where: { inproduction: { _eq: true } }) {
prodJobs: jobs(
where: { inproduction: { _eq: true }, suspended: { _eq: false } }
) {
id
labhrs: joblines_aggregate(
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
@@ -234,11 +287,22 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
}
}
compJobs: jobs(
where: { scheduled_completion: { _gte: $start, _lte: $end } }
where: {
_and: [
{ suspended: { _eq: false } }
{
_or: [
{ scheduled_completion: { _gte: $start, _lte: $end } }
{ actual_completion: { _gte: $start, _lte: $end } }
]
}
]
}
) {
id
ro_number
scheduled_completion
actual_completion
labhrs: joblines_aggregate(
where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }
) {
@@ -258,7 +322,12 @@ export const QUERY_SCHEDULE_LOAD_DATA = gql`
}
}
}
arrJobs: jobs(where: { scheduled_in: { _gte: $start, _lte: $end } }) {
arrJobs: jobs(
where: {
scheduled_in: { _gte: $start, _lte: $end }
suspended: { _eq: false }
}
) {
id
scheduled_in
ro_number

View File

@@ -2,7 +2,7 @@ import { gql } from "@apollo/client";
export const QUERY_ALL_ASSOCIATIONS = gql`
query QUERY_ALL_ASSOCIATIONS {
associations {
associations(order_by: { bodyshop: { shopname: asc } }) {
id
active
bodyshop {

View File

@@ -262,6 +262,9 @@ export const QUERY_DELIVER_CHECKLIST = gql`
jobs_by_pk(id: $jobId) {
id
ro_number
actual_completion
actual_delivery
}
}
`;

View File

@@ -42,6 +42,7 @@ export const CONVERSATION_LIST_QUERY = gql`
id
updated_at
unreadcnt
archived
messages_aggregate(
where: { read: { _eq: false }, isoutbound: { _eq: false } }
) {
@@ -131,6 +132,7 @@ export const TOGGLE_CONVERSATION_ARCHIVE = gql`
_set: { archived: $archived }
) {
archived
id
}
}
`;

View File

@@ -3,6 +3,17 @@ import { gql } from "@apollo/client";
export const QUERY_EMPLOYEES = gql`
query QUERY_EMPLOYEES {
employees(order_by: { employee_number: asc }) {
last_name
id
first_name
flat_rate
employee_number
}
}
`;
export const QUERY_EMPLOYEE_BY_ID = gql`
query QUERY_EMPLOYEE_BY_ID($id: uuid!) {
employees_by_pk(id: $id) {
last_name
id
first_name
@@ -14,6 +25,11 @@ export const QUERY_EMPLOYEES = gql`
rates
pin
user_email
employee_vacations(order_by: { start: desc }) {
id
start
end
}
}
}
`;
@@ -55,7 +71,17 @@ export const INSERT_EMPLOYEES = gql`
mutation INSERT_EMPLOYEES($employees: [employees_insert_input!]!) {
insert_employees(objects: $employees) {
returning {
last_name
id
first_name
employee_number
active
termination_date
hire_date
flat_rate
rates
pin
user_email
}
}
}
@@ -98,3 +124,21 @@ export const QUERY_USERS_BY_EMAIL = gql`
}
}
`;
export const INSERT_VACATION = gql`
mutation INSERT_VACATION($vacation: employee_vacation_insert_input!) {
insert_employee_vacation_one(object: $vacation) {
id
start
end
}
}
`;
export const DELETE_VACATION = gql`
mutation DELETE_VACATION($id: uuid!) {
delete_employee_vacation_by_pk(id: $id) {
id
}
}
`;

View File

@@ -9,6 +9,20 @@ export const INSERT_CONVERSATION_TAG = gql`
returning {
jobid
conversationid
id
conversation {
id
job_conversations {
id
jobid
conversationid
job {
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

@@ -42,6 +42,7 @@ export const QUERY_ALL_ACTIVE_JOBS = gql`
status
updated_at
ded_amt
suspended
}
}
`;
@@ -122,6 +123,7 @@ export const QUERY_EXACT_JOB_IN_PRODUCTION = gql`
ro_number
ownr_fn
ownr_ln
category
ownr_co_nm
v_model_yr
v_model_desc
@@ -192,6 +194,7 @@ export const QUERY_EXACT_JOBS_IN_PRODUCTION = gql`
status
ro_number
ownr_fn
category
ownr_ln
ownr_co_nm
v_model_yr
@@ -262,6 +265,7 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
id
updated_at
status
category
ro_number
ownr_fn
ownr_ln
@@ -290,6 +294,7 @@ export const QUERY_JOBS_IN_PRODUCTION = gql`
employee_refinish
employee_prep
employee_csr
suspended
labhrs: joblines_aggregate(
where: {
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]
@@ -483,7 +488,7 @@ export const GET_JOB_BY_PK = gql`
}
alt_transport
intakechecklist
invoice_final_note
loss_desc
kmin
kmout
@@ -654,6 +659,7 @@ export const GET_JOB_BY_PK = gql`
voided
ca_bc_pvrt
ca_customer_gst
suspended
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
id
alt_partm
@@ -828,6 +834,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
ca_gst_registrant
owner_owing
special_coverage_policy
suspended
available_jobs {
id
}
@@ -1034,6 +1041,7 @@ export const UPDATE_JOB = gql`
ro_number
production_vars
lbr_adjustments
suspended
}
}
}
@@ -1816,6 +1824,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
jobs_by_pk(id: $id) {
ro_number
invoice_allocation
invoice_final_note
ins_co_id
dms_allocation
id
@@ -1869,6 +1878,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
scheduled_delivery
actual_delivery
scheduled_in
date_invoiced
actual_in
kmin
kmout
@@ -2032,6 +2042,8 @@ export const QUERY_JOB_EXPORT_DMS = gql`
po_number
clm_no
job_totals
ded_amt
ded_status
ownr_fn
ownr_ln
ownr_co_nm

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

@@ -124,6 +124,7 @@ export const Banner00DataSource = {
button: {
className: "banner0-button",
children: i18n.t("landing.hero.button"),
href: "https://imexsystems.ca",
},
};
export const Content40DataSource = {

View File

@@ -58,13 +58,6 @@ export default class Home extends React.Component {
}, 500);
}
/* 如果不是 dva 2.0 请删除 end */
window.$crisp.push(["set", "session:segments", [["lead"]]]);
window.$crisp.push([
"set",
"session:event",
[[["landing-page", {}, "green"]]],
]);
}
render() {

View File

@@ -13,7 +13,7 @@ import queryString from "query-string";
import React, { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { useHistory, useLocation, Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import SocketIO from "socket.io-client";
import AlertComponent from "../../components/alert/alert.component";
@@ -138,13 +138,20 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
<Row gutter={[16, 16]}>
<Col md={24} lg={10}>
<DmsAllocationsSummary
title={`${data && data.jobs_by_pk && data.jobs_by_pk.ro_number} | ${
data.jobs_by_pk.ownr_fn || ""
} ${data.jobs_by_pk.ownr_ln || ""} ${
data.jobs_by_pk.ownr_co_nm || ""
} | ${data.jobs_by_pk.v_model_yr || ""} ${
data.jobs_by_pk.v_make_desc || ""
} ${data.jobs_by_pk.v_model_desc || ""}`}
title={
<span>
<Link to={`/manage/jobs/${data && data.jobs_by_pk.id}`}>{`${
data && data.jobs_by_pk && data.jobs_by_pk.ro_number
}`}</Link>
{` | ${data.jobs_by_pk.ownr_fn || ""} ${
data.jobs_by_pk.ownr_ln || ""
} ${data.jobs_by_pk.ownr_co_nm || ""} | ${
data.jobs_by_pk.v_model_yr || ""
} ${data.jobs_by_pk.v_make_desc || ""} ${
data.jobs_by_pk.v_model_desc || ""
}`}
</span>
}
socket={socket}
jobId={jobId}
/>

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

@@ -58,3 +58,7 @@ export const insertAuditTrail = ({ jobid, billid, operation }) => ({
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
payload: { jobid, billid, operation },
});
export const setProblemJobs = (problemJobs) => ({
type: ApplicationActionTypes.SET_PROBLEM_JOBS,
payload: problemJobs,
});

View File

@@ -6,6 +6,7 @@ const INITIAL_STATE = {
breadcrumbs: [],
recentItems: [],
selectedHeader: "home",
problemJobs: [],
scheduleLoad: {
load: {},
calculating: false,
@@ -40,6 +41,7 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD:
return {
...state,
problemJobs: [],
scheduleLoad: { ...state.scheduleLoad, calculating: true, error: null },
};
case ApplicationActionTypes.CALCULATE_SCHEDULE_LOAD_SUCCESS:
@@ -76,7 +78,9 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
case ApplicationActionTypes.SET_PARTNER_VERSION:
return { ...state, partnerVersion: action.payload };
case ApplicationActionTypes.SET_PROBLEM_JOBS: {
return { ...state, problemJobs: action.payload };
}
default:
return state;
}

View File

@@ -7,6 +7,7 @@ import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils";
import {
scheduleLoadFailure,
scheduleLoadSuccess,
setProblemJobs,
} from "./application.actions";
import ApplicationActionTypes from "./application.types";
@@ -53,6 +54,8 @@ export function* calculateScheduleLoad({ payload: end }) {
});
arrJobs.forEach((item) => {
if (!item.scheduled_in)
console.log("JOB HAS NO SCHEDULED IN DATE.", item);
const itemDate = moment(item.scheduled_in).format("yyyy-MM-DD");
if (!!load[itemDate]) {
load[itemDate].hoursIn =
@@ -71,8 +74,47 @@ export function* calculateScheduleLoad({ payload: end }) {
}
});
let problemJobs = [];
compJobs.forEach((item) => {
const itemDate = moment(item.scheduled_completion).format("yyyy-MM-DD");
if (!(item.actual_completion || item.scheduled_completion))
console.log("JOB HAS NO COMPLETION DATE.", item);
const inProdJobs = prodJobs.find((p) => p.id === item.id);
const inArrJobs = arrJobs.find((p) => p.id === item.id);
if (!(inProdJobs || inArrJobs)) {
//Job isn't found in production or coming in.
//is it going today or scheduled to go today?
if (
moment(item.actual_completion || item.scheduled_completion).isSame(
moment(),
"day"
)
) {
console.log("Job is going today anyways, ignore it.", item);
return;
}
if (
moment(item.actual_completion || item.scheduled_completion).isBefore(
moment(),
"day"
)
) {
console.log("Job should have already gone. Ignoring it.", item);
return;
}
problemJobs.push({
...item,
code: "Job is scheduled for completion, but it is not marked in production nor is it an arriving job in this period. Check the scheduled in and completion dates",
});
return;
}
const itemDate = moment(
item.actual_completion || item.scheduled_completion
).format("yyyy-MM-DD");
if (!!load[itemDate]) {
load[itemDate].hoursOut =
(load[itemDate].hoursOut || 0) +
@@ -116,7 +158,7 @@ export function* calculateScheduleLoad({ payload: end }) {
);
}
}
yield put(setProblemJobs(problemJobs));
yield put(scheduleLoadSuccess(load));
} catch (error) {
yield put(scheduleLoadFailure(error));

View File

@@ -44,3 +44,7 @@ export const selectOnline = createSelector(
[selectApplication],
(application) => application.online
);
export const selectProblemJobs = createSelector(
[selectApplication],
(application) => application.problemJobs
);

View File

@@ -11,5 +11,6 @@ const ApplicationActionTypes = {
SET_PARTNER_VERSION: "SET_PARTNER_VERSION",
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
};
export default ApplicationActionTypes;

View File

@@ -48,3 +48,7 @@ initMessageListener(store);
export const persistor = persistStore(store);
const e = { store, persistStore };
export default e;
if (window.Cypress) {
window.store = store;
}

View File

@@ -270,7 +270,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
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

@@ -37,7 +37,9 @@
"fields": {
"alt_transport": "Alt. Trans.",
"color": "Appointment Color",
"note": "Appt. Note",
"end": "End",
"note": "Note",
"start": "Start",
"time": "Appointment Time",
"title": "Title"
},
@@ -47,8 +49,10 @@
"blocked": "Blocked",
"cancelledappointment": "Canceled appointment for: ",
"completingjobs": "Completing Jobs",
"dataconsistency": "{{ro_number}} has a data consistency issue. It has been excluded for scheduling purposes. CODE: {{code}}.",
"history": "History",
"inproduction": "Jobs In Production",
"manualevent": "Add Manual Appointment",
"noarrivingjobs": "No jobs are arriving.",
"nocompletingjobs": "No jobs scheduled for completion.",
"nodateselected": "No date has been selected.",
@@ -253,9 +257,11 @@
"enforce_class": "Enforce Class on Conversion?",
"enforce_referral": "Enforce Referrals",
"federal_tax_id": "Federal Tax ID (GST/HST)",
"ignoreblockeddays": "Scoreboard - Ignore Blocked Days",
"inhousevendorid": "In House Vendor ID",
"insurance_vendor_id": "Insurance Vendor ID",
"intake": {
"next_contact_hours": "Automatic Next Contact Date - Hours from Intake",
"templates": "Intake Templates"
},
"invoice_federal_tax_rate": "Invoices - Federal Tax Rate",
@@ -614,6 +620,7 @@
"fuelout": "Fuel Out",
"kmend": "Mileage End",
"kmstart": "Mileage Start",
"length": "Length",
"localtax": "Local Taxes",
"refuelcharge": "Refuel Charge (per liter/gallon)",
"scheduledreturn": "Scheduled Return",
@@ -830,6 +837,7 @@
},
"employees": {
"actions": {
"addvacation": "Add Vacation",
"new": "New Employee",
"newrate": "New Rate"
},
@@ -851,10 +859,16 @@
"pin": "Tech Console PIN",
"rate": "Rate",
"termination_date": "Termination Date",
"user_email": "User Email"
"user_email": "User Email",
"vacation": {
"end": "Vacation End",
"length": "Vacation Length",
"start": "Vacation Start"
}
},
"labels": {
"actions": "Actions",
"endmustbeafterstart": "End date must be after start date.",
"flat_rate": "Flat Rate",
"name": "Name",
"rate_type": "Rate Type",
@@ -890,6 +904,7 @@
"deselectall": "Deselect All",
"edit": "Edit",
"login": "Login",
"print": "Print",
"refresh": "Refresh",
"remove": "Remove",
"reset": "Reset your changes.",
@@ -1248,6 +1263,7 @@
"dms_model": "DMS Model",
"dms_wip_acctnumber": "Cost WIP DMS Acct #",
"id": "DMS ID",
"inservicedate": "In Service Date",
"journal": "Journal #",
"name1": "Customer Name",
"payer": {
@@ -1293,6 +1309,7 @@
"required": "Required?",
"type": "Type"
},
"invoice_final_note": "Note to Display on Final Invoice",
"kmin": "Mileage In",
"kmout": "Mileage Out",
"la1": "LA1",
@@ -1489,7 +1506,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",
@@ -1572,6 +1591,7 @@
"subletstotal": "Sublets Total",
"subtotal": "Subtotal",
"supplementnote": "The job had a supplement imported.",
"suspended": "SUSPENDED",
"suspense": "Suspense",
"total_cost": "Total Cost",
"total_cust_payable": "Total Customer Amount Payable",
@@ -1627,7 +1647,7 @@
"slogan": "A whole new kind of shop management system."
},
"hero": {
"button": "Coming Soon",
"button": "Learn More",
"title": "A whole new kind of shop management system."
},
"labels": {
@@ -2015,6 +2035,7 @@
"csi_invitation": "CSI Invitation",
"csi_invitation_action": "CSI Invite",
"diagnostic_authorization": "Diagnostic Authorization",
"envelope_return_address": "#10 Envelope Return Address Label",
"estimate": "Estimate Only",
"estimate_detail": "Estimate Details",
"estimate_followup": "Estimate Followup",
@@ -2022,6 +2043,7 @@
"filing_coversheet_portrait": "Filing Coversheet (Portrait)",
"final_invoice": "Final Invoice",
"fippa_authorization": "FIPPA Authorization",
"folder_label_multiple": "Folder Label Multiple",
"glass_express_checklist": "Glass Express Checklist",
"guarantee": "Repair Guarantee",
"individual_job_note": "Job Note RO # {{ro_number}}",
@@ -2031,6 +2053,11 @@
"job_costing_ro": "Job Costing",
"job_notes": "Job Notes",
"key_tag": "Key Tag",
"labels": {
"count": "Count",
"labels": "Labels",
"position": "Starting Position"
},
"mpi_animal_checklist": "MPI - Animal Checklist",
"mpi_eglass_auth": "MPI - eGlass Auth",
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet",
@@ -2086,7 +2113,7 @@
},
"subjects": {
"jobs": {
"parts_order": "Parts Order PO: {{ro_number}}"
"parts_order": "Parts Order PO: {{ro_number}} - {{name}}"
}
},
"vendors": {
@@ -2105,7 +2132,9 @@
"paintpriority-set": "Set Paint Priority",
"remove": "Remove from Production",
"removecolumn": "Remove Column",
"saveconfig": "Save Configuration"
"saveconfig": "Save Configuration",
"suspend": "Suspend",
"unsuspend": "Unsuspend"
},
"errors": {
"boardupdate": "Error encountered updating job. {{message}}",
@@ -2192,6 +2221,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",
@@ -2229,15 +2259,19 @@
"open_orders_estimator": "Open Orders by Estimator",
"open_orders_ins_co": "Open Orders by Insurance Company",
"open_orders_status": "Open Orders by Status",
"parts_backorder": "Backordered Parts",
"parts_backorder": "IOU Parts List",
"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",

View File

@@ -37,7 +37,9 @@
"fields": {
"alt_transport": "",
"color": "",
"end": "",
"note": "",
"start": "",
"time": "",
"title": "Título"
},
@@ -47,8 +49,10 @@
"blocked": "",
"cancelledappointment": "Cita cancelada para:",
"completingjobs": "",
"dataconsistency": "",
"history": "",
"inproduction": "",
"manualevent": "",
"noarrivingjobs": "",
"nocompletingjobs": "",
"nodateselected": "No se ha seleccionado ninguna fecha.",
@@ -253,9 +257,11 @@
"enforce_class": "",
"enforce_referral": "",
"federal_tax_id": "",
"ignoreblockeddays": "",
"inhousevendorid": "",
"insurance_vendor_id": "",
"intake": {
"next_contact_hours": "",
"templates": ""
},
"invoice_federal_tax_rate": "",
@@ -614,6 +620,7 @@
"fuelout": "",
"kmend": "",
"kmstart": "",
"length": "",
"localtax": "",
"refuelcharge": "",
"scheduledreturn": "",
@@ -830,6 +837,7 @@
},
"employees": {
"actions": {
"addvacation": "",
"new": "Nuevo empleado",
"newrate": ""
},
@@ -851,10 +859,16 @@
"pin": "",
"rate": "",
"termination_date": "Fecha de conclusión",
"user_email": ""
"user_email": "",
"vacation": {
"end": "",
"length": "",
"start": ""
}
},
"labels": {
"actions": "",
"endmustbeafterstart": "",
"flat_rate": "",
"name": "",
"rate_type": "",
@@ -890,6 +904,7 @@
"deselectall": "",
"edit": "Editar",
"login": "",
"print": "",
"refresh": "",
"remove": "",
"reset": " Restablecer a original.",
@@ -1248,6 +1263,7 @@
"dms_model": "",
"dms_wip_acctnumber": "",
"id": "",
"inservicedate": "",
"journal": "",
"name1": "",
"payer": {
@@ -1293,6 +1309,7 @@
"required": "",
"type": ""
},
"invoice_final_note": "",
"kmin": "Kilometraje en",
"kmout": "Kilometraje",
"la1": "",
@@ -1489,7 +1506,9 @@
"difference": "",
"diskscan": "",
"dms": {
"damageto": "",
"defaultstory": "",
"invoicedatefuture": "",
"kmoutnotgreaterthankmin": "",
"logs": "",
"notallocated": "",
@@ -1572,6 +1591,7 @@
"subletstotal": "",
"subtotal": "",
"supplementnote": "",
"suspended": "",
"suspense": "",
"total_cost": "",
"total_cust_payable": "",
@@ -2015,6 +2035,7 @@
"csi_invitation": "",
"csi_invitation_action": "",
"diagnostic_authorization": "",
"envelope_return_address": "",
"estimate": "",
"estimate_detail": "",
"estimate_followup": "",
@@ -2022,6 +2043,7 @@
"filing_coversheet_portrait": "",
"final_invoice": "",
"fippa_authorization": "",
"folder_label_multiple": "",
"glass_express_checklist": "",
"guarantee": "",
"individual_job_note": "",
@@ -2031,6 +2053,11 @@
"job_costing_ro": "",
"job_notes": "",
"key_tag": "",
"labels": {
"count": "",
"labels": "",
"position": ""
},
"mpi_animal_checklist": "",
"mpi_eglass_auth": "",
"mpi_final_acct_sheet": "",
@@ -2105,7 +2132,9 @@
"paintpriority-set": "",
"remove": "",
"removecolumn": "",
"saveconfig": ""
"saveconfig": "",
"suspend": "",
"unsuspend": ""
},
"errors": {
"boardupdate": "",
@@ -2192,6 +2221,7 @@
"attendance_summary": "",
"credits_not_received_date": "",
"csi": "",
"estimates_written_converted": "",
"estimator_detail": "",
"estimator_summary": "",
"export_payables": "",
@@ -2233,11 +2263,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": "",

View File

@@ -37,7 +37,9 @@
"fields": {
"alt_transport": "",
"color": "",
"end": "",
"note": "",
"start": "",
"time": "",
"title": "Titre"
},
@@ -47,8 +49,10 @@
"blocked": "",
"cancelledappointment": "Rendez-vous annulé pour:",
"completingjobs": "",
"dataconsistency": "",
"history": "",
"inproduction": "",
"manualevent": "",
"noarrivingjobs": "",
"nocompletingjobs": "",
"nodateselected": "Aucune date n'a été sélectionnée.",
@@ -253,9 +257,11 @@
"enforce_class": "",
"enforce_referral": "",
"federal_tax_id": "",
"ignoreblockeddays": "",
"inhousevendorid": "",
"insurance_vendor_id": "",
"intake": {
"next_contact_hours": "",
"templates": ""
},
"invoice_federal_tax_rate": "",
@@ -614,6 +620,7 @@
"fuelout": "",
"kmend": "",
"kmstart": "",
"length": "",
"localtax": "",
"refuelcharge": "",
"scheduledreturn": "",
@@ -830,6 +837,7 @@
},
"employees": {
"actions": {
"addvacation": "",
"new": "Nouvel employé",
"newrate": ""
},
@@ -851,10 +859,16 @@
"pin": "",
"rate": "",
"termination_date": "Date de résiliation",
"user_email": ""
"user_email": "",
"vacation": {
"end": "",
"length": "",
"start": ""
}
},
"labels": {
"actions": "",
"endmustbeafterstart": "",
"flat_rate": "",
"name": "",
"rate_type": "",
@@ -890,6 +904,7 @@
"deselectall": "",
"edit": "modifier",
"login": "",
"print": "",
"refresh": "",
"remove": "",
"reset": " Rétablir l'original.",
@@ -1248,6 +1263,7 @@
"dms_model": "",
"dms_wip_acctnumber": "",
"id": "",
"inservicedate": "",
"journal": "",
"name1": "",
"payer": {
@@ -1293,6 +1309,7 @@
"required": "",
"type": ""
},
"invoice_final_note": "",
"kmin": "Kilométrage en",
"kmout": "Kilométrage hors",
"la1": "",
@@ -1489,7 +1506,9 @@
"difference": "",
"diskscan": "",
"dms": {
"damageto": "",
"defaultstory": "",
"invoicedatefuture": "",
"kmoutnotgreaterthankmin": "",
"logs": "",
"notallocated": "",
@@ -1572,6 +1591,7 @@
"subletstotal": "",
"subtotal": "",
"supplementnote": "",
"suspended": "",
"suspense": "",
"total_cost": "",
"total_cust_payable": "",
@@ -2015,6 +2035,7 @@
"csi_invitation": "",
"csi_invitation_action": "",
"diagnostic_authorization": "",
"envelope_return_address": "",
"estimate": "",
"estimate_detail": "",
"estimate_followup": "",
@@ -2022,6 +2043,7 @@
"filing_coversheet_portrait": "",
"final_invoice": "",
"fippa_authorization": "",
"folder_label_multiple": "",
"glass_express_checklist": "",
"guarantee": "",
"individual_job_note": "",
@@ -2031,6 +2053,11 @@
"job_costing_ro": "",
"job_notes": "",
"key_tag": "",
"labels": {
"count": "",
"labels": "",
"position": ""
},
"mpi_animal_checklist": "",
"mpi_eglass_auth": "",
"mpi_final_acct_sheet": "",
@@ -2105,7 +2132,9 @@
"paintpriority-set": "",
"remove": "",
"removecolumn": "",
"saveconfig": ""
"saveconfig": "",
"suspend": "",
"unsuspend": ""
},
"errors": {
"boardupdate": "",
@@ -2192,6 +2221,7 @@
"attendance_summary": "",
"credits_not_received_date": "",
"csi": "",
"estimates_written_converted": "",
"estimator_detail": "",
"estimator_summary": "",
"export_payables": "",
@@ -2233,11 +2263,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": "",

View File

@@ -30,6 +30,9 @@ export const CalculateLoad = (currentLoad, buckets, jobsIn, jobsOut) => {
const bucketId = CheckJobBucket(buckets, job);
if (bucketId) {
newLoad[bucketId].count = newLoad[bucketId].count - 1;
if (newLoad[bucketId].count < 0) {
console.log("***ERROR: NEGATIVE LOAD", bucketId, job);
}
} else {
console.log(
"[Util Out Job]Uh oh, this job doesn't fit in a bucket!",

View File

@@ -364,6 +364,14 @@ export const TemplateList = (type, context) => {
disabled: false,
group: "ro",
},
envelope_return_address: {
title: i18n.t("printcenter.jobs.envelope_return_address"),
description: "All Jobs Notes",
subject: i18n.t("printcenter.jobs.envelope_return_address"),
key: "envelope_return_address",
disabled: false,
group: "ro",
},
sgi_certificate_of_repairs: {
title: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"),
description: "Thank You Letter by RO",
@@ -456,6 +464,12 @@ export const TemplateList = (type, context) => {
key: "special_thirdpartypayer",
disabled: false,
},
folder_label_multiple: {
title: i18n.t("printcenter.jobs.folder_label_multiple"),
description: "Folder Label Multiple",
key: "folder_label_multiple",
disabled: false,
},
csi_invitation_action: {
title: i18n.t("printcenter.jobs.csi_invitation_action"),
description: "CSI invite",
@@ -495,6 +509,11 @@ export const TemplateList = (type, context) => {
key: "parts_order",
subject: i18n.t("printcenter.subjects.jobs.parts_order", {
ro_number: context && context.job && context.job.ro_number,
name: (
(context && context.job && context.job.ownr_ln) ||
(context && context.job && context.job.ownr_co_nm) ||
""
).trim(),
}),
disabled: false,
},
@@ -1447,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"
@@ -1571,6 +1608,22 @@ 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: "",
subject: i18n.t("reportcenter.templates.production_by_technician"),
key: "production_by_technician",
//idtype: "vendor",
disabled: false,
},
}
: {}),
...(!type || type === "special"
@@ -1582,6 +1635,28 @@ export const TemplateList = (type, context) => {
key: "ca_bc_etf_table",
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

@@ -2214,10 +2214,10 @@
shallowequal "^1.1.0"
unfetch "^4.1.0"
"@stripe/react-stripe-js@^1.6.0":
version "1.6.0"
resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.6.0.tgz#e3adf6a6ea6d839193164fa3cfe73cf52db3a080"
integrity sha512-tMmsPD+wkpiiVJZgQ1E06tklG5MZHG462s6OWja9abpxq76kerAxMFN+KdhUg0LIEY79THbzvH3s/WGHasnV3w==
"@stripe/react-stripe-js@^1.7.0":
version "1.7.0"
resolved "https://registry.yarnpkg.com/@stripe/react-stripe-js/-/react-stripe-js-1.7.0.tgz#83c993a09a903703205d556617f9729784a896c3"
integrity sha512-L20v8Jq0TDZFL2+y+uXD751t6q9SalSFkSYZpmZ2VWrGZGK7HAGfRQ804dzYSSr5fGenW6iz6y7U0YKfC/TK3g==
dependencies:
prop-types "^15.7.2"
@@ -3186,10 +3186,10 @@ ansi-styles@^5.0.0:
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
antd@^4.17.3:
version "4.17.3"
resolved "https://registry.yarnpkg.com/antd/-/antd-4.17.3.tgz#48e2cfaec75cb414782a16918c0f322af1f2d509"
integrity sha512-enA6rsOAGtw0uN+khzvPoCui9j6m1ZvtAHY2IWC/mOUIwfycC8iuToND9ptAqeNF5yX8RZhFubmcc7Xeqk6wWg==
antd@^4.17.4:
version "4.17.4"
resolved "https://registry.yarnpkg.com/antd/-/antd-4.17.4.tgz#fa409aa45f4ea2992fd6832889b4ac6d82d4786a"
integrity sha512-aiWi7TxAc7qAxbL412GSKpkWkL/wIhQe6ABuLLCiE1vqXnGTvav2Z0PiOUdFclZcfz2M2IofsUl2pLVN9I8iCg==
dependencies:
"@ant-design/colors" "^6.0.0"
"@ant-design/icons" "^4.7.0"
@@ -3219,7 +3219,7 @@ antd@^4.17.3:
rc-picker "~2.5.17"
rc-progress "~3.1.0"
rc-rate "~2.9.0"
rc-resize-observer "^1.1.0"
rc-resize-observer "^1.1.2"
rc-select "~13.2.1"
rc-slider "~9.7.4"
rc-steps "~4.1.0"
@@ -7037,10 +7037,10 @@ graphql-tag@^2.12.3:
dependencies:
tslib "^2.1.0"
graphql@^16.1.0:
version "16.1.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.1.0.tgz#83bebeae6e119766d04966f09de9305be7fd44e5"
integrity sha512-+PIjmhqGHMIxtnlEirRXDHIzs0cAHAozKG5M2w2N4TnS8VzCxO3bbv1AW9UTeycBfl2QsPduxcVrBvANFKQhiw==
graphql@^16.2.0:
version "16.2.0"
resolved "https://registry.yarnpkg.com/graphql/-/graphql-16.2.0.tgz#de3150e80f1fc009590b92a9d16ab1b46e12b656"
integrity sha512-MuQd7XXrdOcmfwuLwC2jNvx0n3rxIuNYOxUtiee5XOmfrWo613ar2U8pE7aHAKh8VwfpifubpD9IP+EdEAEOsA==
growly@^1.3.0:
version "1.3.0"
@@ -7403,10 +7403,10 @@ i18next-browser-languagedetector@^6.1.2:
dependencies:
"@babel/runtime" "^7.14.6"
i18next@^21.6.0:
version "21.6.0"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.6.0.tgz#257abf455b24136640a20728b44cf59f60cdeb5c"
integrity sha512-RjNuACL35wWZgtkyMcjcCmK7R72u3P6jTNbGKzrvHGI9M0iK5Vn1DsBIwOByppaXLIbe0viJ79Nz2h8w1UwPoQ==
i18next@^21.6.3:
version "21.6.3"
resolved "https://registry.yarnpkg.com/i18next/-/i18next-21.6.3.tgz#6ab6ab1020e1f3bda71c4d1dc216ac39663d6641"
integrity sha512-2uuRGslNQ8m7TRllsVs4cZuei5X9OgoPRB/Sj5oadUpxZaW+NYv3srn7zR+h8bCMhkux9z8HtnJdQM5Mz+Govw==
dependencies:
"@babel/runtime" "^7.12.0"
@@ -8724,10 +8724,10 @@ json5@^2.1.2:
dependencies:
minimist "^1.2.5"
jsoneditor@^9.5.7:
version "9.5.7"
resolved "https://registry.yarnpkg.com/jsoneditor/-/jsoneditor-9.5.7.tgz#b74d0c2cf64febe102b1b0a91d693f48d1f90917"
integrity sha512-RWI1YqIhb2XzLcnSHMta7LROItQg49FhSd7KNPWr81HyCj2pc0m25no4BpzygmxW9320r9PnYKJwZsqL2PpdrQ==
jsoneditor@^9.5.8:
version "9.5.8"
resolved "https://registry.yarnpkg.com/jsoneditor/-/jsoneditor-9.5.8.tgz#27f21b8d4faea3cca4f7be87ab4afa707c91d26a"
integrity sha512-X8ZEKIS5O7r/fWd6ZW1JspvoVqRQcGBiz038EPL6HNBADpRbUv5g4s6VIpMGtvIMzTzO9ywVjMCwu6MZ+p6tgg==
dependencies:
ace-builds "^1.4.13"
ajv "^6.12.6"
@@ -11524,7 +11524,7 @@ rc-rate@~2.9.0:
classnames "^2.2.5"
rc-util "^5.0.1"
rc-resize-observer@^1.0.0, rc-resize-observer@^1.1.0:
rc-resize-observer@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-1.1.1.tgz#ef666e38065f550730176404bae2ce8ca5fb1ac4"
integrity sha512-5A3B9ha297ItltzXl812WFE36SyRDTNclfrXE3FL1pEwXkBh7iSEzxjzfwsPeMcF9ahy3ZoxLgLuRksXBGGD6A==
@@ -11534,6 +11534,16 @@ rc-resize-observer@^1.0.0, rc-resize-observer@^1.1.0:
rc-util "^5.15.0"
resize-observer-polyfill "^1.5.1"
rc-resize-observer@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-1.1.2.tgz#214bc5d0de19e0a5fcd7d8352d9c1560dd7531b7"
integrity sha512-Qp+1x6D88FxyWBFRYP95IV9A1o0xlkC6qhiTX3YakE+o86QH9IzC7UVnltwmm4Q8uYH+E3F/HRmEiuxXJECdSw==
dependencies:
"@babel/runtime" "^7.10.1"
classnames "^2.2.1"
rc-util "^5.15.0"
resize-observer-polyfill "^1.5.1"
rc-scroll-anim@^2.7.6:
version "2.7.6"
resolved "https://registry.yarnpkg.com/rc-scroll-anim/-/rc-scroll-anim-2.7.6.tgz#f7e6622f2930ca3e1e258f7275bc2e1c26ce791c"
@@ -11738,10 +11748,10 @@ react-beautiful-dnd@^13.0.0:
redux "^4.0.4"
use-memo-one "^1.1.1"
react-big-calendar@^0.38.1:
version "0.38.1"
resolved "https://registry.yarnpkg.com/react-big-calendar/-/react-big-calendar-0.38.1.tgz#cd95bacf45c3064d2609fd261f321bf7757ef584"
integrity sha512-9xwBekBxsfwQJb8/4KpPgsS2mWnzOcXibuVtJ8MqI04W8mvAoIgnSAO0WtJBJfI0QMaeX+Ak12XwIZ88zZ/fGw==
react-big-calendar@^0.38.2:
version "0.38.2"
resolved "https://registry.yarnpkg.com/react-big-calendar/-/react-big-calendar-0.38.2.tgz#f0e25800fb93d370e5ccdd4aba1e7c0fc45045e6"
integrity sha512-p8RYw7VxuXXxOXC3i/XHzALw/8GPkS69OsPEeivsA7hKa7JDE/J87kqcXaeSPEar124EaCCaP4/nbLtd6c0tlA==
dependencies:
"@babel/runtime" "^7.1.5"
clsx "^1.0.4"
@@ -11832,10 +11842,10 @@ react-draggable@^4.0.0, react-draggable@^4.0.3:
clsx "^1.1.1"
prop-types "^15.6.0"
react-error-overlay@^6.0.9:
version "6.0.10"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.10.tgz#0fe26db4fa85d9dbb8624729580e90e7159a59a6"
integrity sha512-mKR90fX7Pm5seCOfz8q9F+66VCc1PGsWSBxKbITjfKVQHMNF2zudxHnMdJiB1fRCb+XsbQV9sO9DCkgsMQgBIA==
react-error-overlay@6.0.9, react-error-overlay@^6.0.9:
version "6.0.9"
resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-6.0.9.tgz#3c743010c9359608c375ecd6bc76f35d93995b0a"
integrity sha512-nQTTcUu+ATDbrSD1BZHr5kgSD4oF8OFjxun8uAaL8RwPBacGBNPf/yAuVVdx17N8XNzRDMrZ9XcKZHCjPW+9ew==
react-grid-gallery@^0.5.5:
version "0.5.5"
@@ -11895,10 +11905,10 @@ react-lifecycles-compat@^3.0.4:
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
react-number-format@^4.8.0:
version "4.8.0"
resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-4.8.0.tgz#2ec5efbe7f45c4b1b8951d34774f30e4c69040a4"
integrity sha512-oGGiQpqzvKTR5PD2/AJbyUsci8jyupaoKxpuSPevjpWHMhFkUtmo390t+EIpJOgnuAHZogLu6PHiXgb/OXETKA==
react-number-format@^4.9.0:
version "4.9.0"
resolved "https://registry.yarnpkg.com/react-number-format/-/react-number-format-4.9.0.tgz#fd1defb74cc6ccc43a9e579f67d752c03ba897e4"
integrity sha512-HC4ZfvZSm6Gqq77/D4gz823XkvqK4AWAg4PxPv9Paz08hryAOnDjZk89iWmRLafeSOYG3TOx37ypHXMRez8q+w==
dependencies:
prop-types "^15.7.2"

View File

@@ -1773,6 +1773,88 @@
_eq: X-Hasura-User-Id
- active:
_eq: true
- table:
schema: public
name: employee_vacation
object_relationships:
- name: employee
using:
foreign_key_constraint_on: employeeid
insert_permissions:
- role: user
permission:
check:
employee:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- id
- created_at
- updated_at
- employeeid
- start
- end
backend_only: false
select_permissions:
- role: user
permission:
columns:
- end
- start
- created_at
- updated_at
- employeeid
- id
filter:
employee:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
update_permissions:
- role: user
permission:
columns:
- end
- start
- created_at
- updated_at
- employeeid
- id
filter:
employee:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
check: null
delete_permissions:
- role: user
permission:
filter:
employee:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
- table:
schema: public
name: employees
@@ -1791,6 +1873,13 @@
table:
schema: public
name: allocations
- name: employee_vacations
using:
foreign_key_constraint_on:
column: employeeid
table:
schema: public
name: employee_vacation
- name: jobsByEmployeeBody
using:
foreign_key_constraint_on:
@@ -2694,6 +2783,7 @@
- intakechecklist
- invoice_allocation
- invoice_date
- invoice_final_note
- iouparent
- job_totals
- kanbanparent
@@ -2779,6 +2869,7 @@
- state_tax_rate
- status
- storage_payable
- suspended
- tax_lbr_rt
- tax_levies_rt
- tax_paint_mat_rt
@@ -2948,6 +3039,7 @@
- intakechecklist
- invoice_allocation
- invoice_date
- invoice_final_note
- iouparent
- job_totals
- kanbanparent
@@ -3033,6 +3125,7 @@
- state_tax_rate
- status
- storage_payable
- suspended
- tax_lbr_rt
- tax_levies_rt
- tax_paint_mat_rt
@@ -3212,6 +3305,7 @@
- intakechecklist
- invoice_allocation
- invoice_date
- invoice_final_note
- iouparent
- job_totals
- kanbanparent
@@ -3297,6 +3391,7 @@
- state_tax_rate
- status
- storage_payable
- suspended
- tax_lbr_rt
- tax_levies_rt
- tax_paint_mat_rt

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