Compare commits

..

442 Commits

Author SHA1 Message Date
swtmply
692f5c0167 IO-2327 timetickets test cases 2023-08-30 12:11:31 +08:00
swtmply
7fc9bd5b35 IO-2327 timetickets test cases 2023-08-29 11:17:36 +08:00
swtmply
86fdcdd397 IO-2327 timetickets test cases 2023-08-28 14:30:49 +08:00
swtmply
623b27dbab IO-2327 timetickets test cases 2023-08-25 11:56:24 +08:00
swtmply
c4a961fc03 IO-2327 timetickets test cases 2023-08-24 12:03:36 +08:00
swtmply
eb075d732a IO-2327 moved the describe to different files 2023-08-24 04:55:28 +08:00
swtmply
e7887ea822 IO-2327 added auto part ordering 2023-08-23 04:22:40 +08:00
swtmply
e74ba0942e IO-2327 posting bills test cases 2023-08-21 15:07:04 +08:00
swtmply
e89145e85f IO-2327 added some test cases on posting bills 2023-08-18 11:51:56 +08:00
swtmply
807dbf0248 IO-2327 parts ordering test case 2023-08-17 12:02:41 +08:00
swtmply
5104b27611 IO-2327 posting bill test cases 2023-08-16 10:57:55 +08:00
swtmply
619e7ca469 IO-2327 posting bill test cases 2023-08-15 12:05:10 +08:00
swtmply
8bddfdc104 IO-2327 posting bills test cases 2023-08-14 08:40:13 +08:00
swtmply
413af6eb7c IO-2327 posting bills test cases 2023-08-11 20:08:43 +08:00
swtmply
7b8691935d IO-2327 posting bill cases 2023-08-10 12:12:51 +08:00
swtmply
73baad21e2 IO-2327 posting bills test cases 2023-08-10 12:12:39 +08:00
swtmply
ffbd98eb4f IO-2327 posting bills test cases 2023-08-09 19:33:25 +08:00
swtmply
2cf4853960 IO-2327 posting bills test cases 2023-08-08 12:44:36 +08:00
swtmply
6db0947454 IO-2327 tax assertion 2023-08-05 12:01:49 +08:00
swtmply
59d9087ca7 IO-2327 tax assertion 2023-08-05 12:01:35 +08:00
swtmply
e4671b9bd6 IO-2327 posting bills test cases 2023-08-04 12:08:34 +08:00
swtmply
353d6d010c IO-2327 part ordering test cases 2023-08-04 04:36:16 +08:00
swtmply
b7366d7fc6 IO-2327 checklists test cases 2023-08-02 11:56:03 +08:00
swtmply
d12ff11deb IO-2327 updated test cases for intake checklist 2023-07-29 17:21:00 +08:00
swtmply
8b4ce18409 slider test component 2023-07-28 12:08:29 +08:00
swtmply
e4890c5fb8 job converting and importing test cases 2023-07-28 10:08:26 +08:00
swtmply
72c9b5a11e IO-2327 job import test cases 2023-07-21 11:38:57 +08:00
swtmply
835b90bc4b IO-2327 job importing test cases 2023-07-21 04:25:53 +08:00
swtmply
6562fd57df IO-2327 timetickets validation 2023-07-11 06:16:07 +08:00
swtmply
0a8c2bf385 IO-2327 payments test cases 2023-07-05 07:32:46 +08:00
swtmply
fc09871a47 IO-2327 posting bills tests cases 2023-07-03 09:06:58 +08:00
swtmply
39f7a3c870 IO-2327 posting bills test caes 2023-06-26 10:34:33 +08:00
swtmply
12d07f8318 IO-2327 parts ordering send by email 2023-06-22 06:43:45 +08:00
swtmply
7ae48db212 IO-2327 Intake checklist tests 2023-06-20 09:12:36 +08:00
swtmply
aa064cc573 IO-2327 job importing tests 2023-06-19 07:54:49 +08:00
swtmply
63f86e4627 IO-2327 importing job from available jobs table 2023-06-15 03:17:36 +08:00
swtmply
76b17cd249 IO-2327 converting a job tests 2023-06-15 00:22:17 +08:00
swtmply
8f8fdf7548 IO-2327 tests for login and reset password 2023-06-14 04:17:25 +08:00
Patrick Fic
058edca573 Remove old cypress config & migrate. 2023-06-07 08:35:30 -07:00
Patrick Fic
0669282432 Merged in release/2023-06-02 (pull request #840)
Release/2023 06 02
2023-06-02 20:08:34 +00:00
John Allen Delos Reyes
bf4dc7e158 Merged in feature/IO-2314-problem-job-hyperlink (pull request #835)
IO-2314 Added hyperlink to ro number

Approved-by: Patrick Fic
2023-06-02 15:55:13 +00:00
Allan Carr
5de2036fdb Merged in feature/IO-2316-Predefined-Email-Address-for-CC (pull request #833)
IO-2316 Predefined Email address for CC field

Approved-by: Patrick Fic
2023-06-02 14:37:28 +00:00
Allan Carr
1629663e15 Merged in feature/IO-2315-DMS-Posting-Sheet-Restriction (pull request #832)
IO-2315 DMS Posting Sheet

Approved-by: Patrick Fic
2023-06-02 14:36:32 +00:00
swtmply
e25f2db2b1 IO-2314 Added hyperlink to ro number 2023-06-02 22:27:53 +08:00
Allan Carr
cbf5d268ea IO-2315 DMS Posting Sheet
Restrict to DMS setups only
2023-06-01 20:32:30 -07:00
Patrick Fic
a92a95a9fa Merge branch 'release/2023-06-02' of bitbucket.org:snaptsoft/bodyshop into release/2023-06-02 2023-06-01 18:31:12 -07:00
Patrick Fic
0be7bf2c8e Merge branch 'feature/IO-2310-active-jobs-ro-sort' into release/2023-06-02 2023-06-01 18:29:53 -07:00
Patrick Fic
56b810dd40 IO-2310 Change RO sort. 2023-06-01 18:29:41 -07:00
Allan Carr
33cfa531b8 IO-2316 Predefined Email address for CC field 2023-06-01 17:34:55 -07:00
John Allen Delos Reyes
000ded6649 Merged in feature/IO-2281-table-colors (pull request #829)
IO-2281 Added striped table colors

Approved-by: Patrick Fic
2023-06-01 22:28:30 +00:00
Patrick Fic
72181e1ff7 Add in missing filter for job search select on job clock on. 2023-05-31 14:33:59 -07:00
swtmply
d73b1d2220 IO-2281 Added striped table colors 2023-06-01 02:34:18 +08:00
Allan Carr
8645b434c8 Merged in feature/IO-2299-RBAC-for-Voiding-File (pull request #827)
IO-2299 RBAC for Voiding File

Approved-by: Patrick Fic
2023-05-31 15:30:15 +00:00
Allan Carr
38a13bd082 IO-2299 RBAC for Voiding File 2023-05-30 20:14:33 -07:00
Allan Carr
3bc5f5d626 Merged in feature/IO-2233-CDK-VEHICLE-LICENSE-PLATE (pull request #826)
IO-2233 Check for null and if stripped string is size 0 send null instead

Approved-by: Patrick Fic
2023-05-31 00:33:20 +00:00
Allan Carr
86a2351316 IO-2233 Check for null and if stripped string is size 0 send null instead 2023-05-30 17:23:22 -07:00
Patrick Fic
de62e994bd Merged in release/2023-05-26 (pull request #823)
Resolve error thrown when entering payment from accounting menu.
2023-05-26 22:11:09 +00:00
Patrick Fic
d0d8354395 Merged in feature/IO-2298-payment-export (pull request #821)
Resolve error thrown when entering payment from accounting menu.
2023-05-26 21:57:39 +00:00
Patrick Fic
6d2c3c81c7 Resolve error thrown when entering payment from accounting menu. 2023-05-26 14:57:04 -07:00
Patrick Fic
e2e02945cc Merged in release/2023-05-26 (pull request #820)
Brought in shop estimator & made list of estimators unique.
2023-05-26 19:04:20 +00:00
Patrick Fic
404efcaf05 Merged in feature/IO-2264-schedule-filter (pull request #818)
Brought in shop estimator & made list of estimators unique.
2023-05-26 19:03:54 +00:00
Patrick Fic
9b96460e4f Brought in shop estimator & made list of estimators unique. 2023-05-26 12:03:33 -07:00
Patrick Fic
618eff4973 Merged in release/2023-05-26 (pull request #817)
Add manual appointment handling.
2023-05-26 18:53:21 +00:00
Patrick Fic
109c34bebd Merged in feature/IO-2264-schedule-filter (pull request #816)
Add manual appointment handling.
2023-05-26 18:53:09 +00:00
Patrick Fic
1aab5aa740 Add manual appointment handling. 2023-05-26 11:52:43 -07:00
Patrick Fic
484cb8e39e Merged in release/2023-05-26 (pull request #815)
Release/2023 05 26
2023-05-26 18:27:21 +00:00
Patrick Fic
1ca483d4b0 Merged in feature/IO-2298-payment-export (pull request #814)
Update labels.
2023-05-26 18:22:23 +00:00
Patrick Fic
094160ebf3 Update labels. 2023-05-26 11:18:59 -07:00
John Allen Delos Reyes
e7be4c6e61 Merged in feature/IO-2298-payment-export (pull request #809)
IO-2298 added mark for export button on payment

Approved-by: Patrick Fic
2023-05-26 18:08:52 +00:00
John Allen Delos Reyes
708fe63852 Merged in feature/IO-2208-chat-affix (pull request #810)
IO-2208 added more conversations on initial load

Approved-by: Patrick Fic
2023-05-26 18:01:00 +00:00
John Allen Delos Reyes
3f579f49b9 Merged in feature/IO-2264-schedule-filter (pull request #811)
IO-2264 removed filter function for estimators

Approved-by: Patrick Fic
2023-05-26 18:00:52 +00:00
swtmply
c15e69f079 IO-2298 added mark for export button on payment 2023-05-27 00:17:44 +08:00
swtmply
ddb919e2cc IO-2264 removed filter function for estimators 2023-05-26 22:56:36 +08:00
swtmply
27e0f497bb IO-2208 added more conversations on initial load 2023-05-26 20:12:58 +08:00
John Allen Delos Reyes
3a06e813a8 Merged in feature/IO-2280-config-anchors (pull request #808)
IO-2280 added config anchors

Approved-by: Patrick Fic
2023-05-25 23:41:50 +00:00
swtmply
c2aaf8844f IO-2280 added config anchors 2023-05-26 02:24:34 +08:00
John Allen Delos Reyes
488f79ddc8 Merged in feature/IO-2264-schedule-filter (pull request #806)
Feature/IO-2264 schedule filter

Approved-by: Patrick Fic
2023-05-25 17:44:11 +00:00
John Allen Delos Reyes
5704e54e48 Merged in feature/IO-2298-payment-export (pull request #802)
IO-2298 added export buttons on payments

Approved-by: Patrick Fic
2023-05-25 17:41:54 +00:00
swtmply
3c4902f71f IO-2298 removed import destructure and added context reload 2023-05-26 00:48:28 +08:00
swtmply
5fb62aa16b IO-2264 added filter by estimators for the schedule 2023-05-26 00:15:35 +08:00
Allan Carr
dd469bad12 Merged in feature/IO-2233-CDK-VEHICLE-LICENSE-PLATE (pull request #803)
IO-2233 CDK Vehicle License Plate Standardization

Approved-by: Patrick Fic
2023-05-24 21:05:21 +00:00
Allan Carr
a716535795 Merged in feature/IO-2259-Company-Name-with-only-Space (pull request #804)
IO-2259 Trim Company Name before seeing if ""

Approved-by: Patrick Fic
2023-05-24 21:05:00 +00:00
Allan Carr
c2ec476324 IO-2259 Trim Company Name before seeing if "" 2023-05-24 11:15:55 -07:00
Patrick Fic
3ecd29c640 Add missing filter on job search select. 2023-05-24 11:13:55 -07:00
Allan Carr
01443c478d IO-2233 CDK Vehicle License Plate Standardization
Remove All special character and spaces and covert to Upper Case
2023-05-24 10:34:05 -07:00
swtmply
209245187f IO-2298 added export buttons on payments 2023-05-25 01:28:22 +08:00
John Allen Delos Reyes
3a9e989d70 Merged in feature/IO-2208-chat-affix (pull request #801)
IO-2208 replaced cache first to network only

Approved-by: Patrick Fic
2023-05-24 15:24:57 +00:00
swtmply
8839fc0293 IO-2208 replaced cache first to network only 2023-05-24 23:07:49 +08:00
Allan Carr
028bf3c7a0 Merged in feature/IO-2292-MPI-Signature-Sheets (pull request #800)
IO-2292 MPI Signature Sheets

Approved-by: Patrick Fic
2023-05-24 14:58:04 +00:00
Allan Carr
ed05754368 IO-2292 MPI Signature Sheets 2023-05-23 17:56:48 -07:00
Allan Carr
7c3043988b Merged in feature/IO-2295-Individual-Note-Print (pull request #799)
IO-2295 Individual Note Print File Name contained {{ro_number}}

Approved-by: Patrick Fic
2023-05-23 23:32:33 +00:00
John Allen Delos Reyes
a16f0df7de Merged in feature/IO-2297-datetime-improvement (pull request #798)
IO-2297 added optimistic response and click outside function

Approved-by: Patrick Fic
2023-05-23 23:32:11 +00:00
John Allen Delos Reyes
2562151f6e Merged in feature/IO-2208-chat-affix (pull request #797)
Feature/IO-2208 chat affix

Approved-by: Patrick Fic
2023-05-23 23:28:31 +00:00
Allan Carr
44f2287b07 IO-2295 Individual Note Print File Name contained {{ro_number}} 2023-05-23 13:38:55 -07:00
swtmply
5e96ccdd99 IO-2297 added optimistic response and click outside function 2023-05-24 03:17:41 +08:00
Patrick Fic
8f91416623 Merged in release/2023-05-19 (pull request #796)
Release/2023 05 19
2023-05-19 17:17:47 +00:00
Allan Carr
a18dbbb6c4 Merged in feature/IO-2293-Autohouse-Job-Cost-Dinero-Type-Cast (pull request #794)
IO-2293 Autohouse & Job Costing Dinero Type Casting

Approved-by: Patrick Fic
2023-05-19 17:02:05 +00:00
Allan Carr
57d8ca5829 IO-2293 Autohouse & Job Costing Dinero Type Casting
All type cast to Dinero round to integer to prevent cast errors
2023-05-19 09:39:37 -07:00
Patrick Fic
4c6a2d6d63 Merged in release/2023-05-19 (pull request #793)
Release/2023 05 19
2023-05-18 22:05:08 +00:00
swtmply
c5c47e9bfc IO-2208 remove unused variables and imports 2023-05-19 00:14:28 +08:00
swtmply
d66fdfb2e0 IO-2208 added virtualization to conversation list 2023-05-18 23:04:10 +08:00
Patrick Fic
5861d0e9b6 IO-2289 Add betterment description to translation 2023-05-17 13:46:37 -07:00
Patrick Fic
e36904794b IO-2274 Add typing to notes. 2023-05-17 13:31:15 -07:00
Patrick Fic
1c89d12034 IO-2286 Resolve duplication of cost centers when sorting labor allocations. 2023-05-17 12:36:15 -07:00
Patrick Fic
5e36a4ae89 IO-2275 Remove PVRT and GST References in USA App. 2023-05-17 12:09:21 -07:00
Patrick Fic
f553307587 Added rounding to job costing for adjustments. 2023-05-17 10:11:27 -07:00
Patrick Fic
2c80c81197 Added tasks presets to database schema. 2023-05-17 07:39:23 -07:00
Patrick Fic
9d865cf130 Merged in release/2023-05-19 (pull request #789)
IO-2258 Scoreboard Chart
2023-05-16 18:34:13 +00:00
Allan Carr
8e119ce0dd Merged in feature/IO-2258-Scoreboard-Chart-Inconsistencies (pull request #788)
IO-2258 Scoreboard Chart

Approved-by: Patrick Fic
2023-05-16 18:33:34 +00:00
Allan Carr
fe49161718 IO-2258 Scoreboard Chart
Add in target paint and body to the overall calculation to bump the start value up to the target instead of zero.
2023-05-15 15:18:24 -07:00
Patrick Fic
040e366335 Merged in release/2023-05-12 (pull request #786)
Release/2023 05 12
2023-05-12 18:18:04 +00:00
Patrick Fic
4655663dd8 Merged in release/2023-05-12 (pull request #785)
Release/2023 05 12
2023-05-12 17:14:31 +00:00
Allan Carr
6e1fbda79b Merged in feature/IO-2261-opensearch-replacements (pull request #784)
IO-2261 Add missing fields to return querys

Approved-by: Patrick Fic
2023-05-12 17:14:04 +00:00
Allan Carr
b2f616f1eb IO-2261 Add missing fields to return querys
Customer info missing on return
2023-05-12 10:12:49 -07:00
swtmply
4bc8ff26d2 IO-2208 Added pagination and subscription to chat 2023-05-13 00:32:51 +08:00
Patrick Fic
76eec7bebc Merged in release/2023-05-12 (pull request #783)
IO-2261 Remove duplicate fields and add created_at into return querys
2023-05-12 16:32:51 +00:00
Allan Carr
aa5f405e1b Merged in feature/IO-2261-opensearch-replacements (pull request #782)
IO-2261 Remove duplicate fields and add created_at into return querys

Approved-by: Patrick Fic
2023-05-12 15:26:05 +00:00
Allan Carr
ca9752d119 IO-2261 Remove duplicate fields and add created_at into return querys 2023-05-12 08:23:16 -07:00
Patrick Fic
45b4af5225 Merged in release/2023-05-12 (pull request #781)
Release/2023 05 12
2023-05-11 23:39:04 +00:00
Allan Carr
d2d310cf57 Merged in feature/IO-2190-Autohouse (pull request #780)
IO-2190 Paint Costs for JC if Cost/Hr is 0

Approved-by: Patrick Fic
2023-05-11 23:38:42 +00:00
Allan Carr
5d1a7657a9 Merged in feature/IO-2265-Autohouse-Extract (pull request #779)
IO-2190 Allow for 0 in JC/Hr rate

Approved-by: Patrick Fic
2023-05-11 23:38:25 +00:00
Allan Carr
5cb17994cd IO-2190 Allow for 0 in JC/Hr rate 2023-05-11 16:09:30 -07:00
Allan Carr
dab78e3dc9 IO-2190 Paint Costs for JC if Cost/Hr is 0 2023-05-11 16:07:28 -07:00
Patrick Fic
1232f28b3d Merged in release/2023-05-12 (pull request #778)
IO-2261 Modify Opensearch Data after payment update
2023-05-11 21:42:57 +00:00
Allan Carr
8e8d40d4b0 Merged in feature/IO-2261-opensearch-replacements (pull request #777)
IO-2261 Modify Opensearch Data after payment update

Approved-by: Patrick Fic
2023-05-11 21:42:24 +00:00
Allan Carr
7fae408454 IO-2261 Modify Opensearch Data after payment update 2023-05-11 14:38:09 -07:00
Patrick Fic
1cdafaa2cc Merged in release/2023-05-12 (pull request #776)
Release/2023 05 12
2023-05-11 14:50:33 +00:00
Allan Carr
b9ca7ef2e3 Merged in feature/IO-2271-Country-Region (pull request #775)
Feature/IO-2271 Country Region

Approved-by: Patrick Fic
2023-05-11 14:50:07 +00:00
Allan Carr
60867ae4dc IO-2271 Country Region Print Center Modification
Allow items to be hidden by Country Code only from region
2023-05-10 18:03:42 -07:00
Allan Carr
b0ddb62ac0 Merged in feature/IO-2265-Autohouse-Extract (pull request #773)
IO-2265 Autohouse Extract Remove Customer Info

Approved-by: Patrick Fic
2023-05-10 15:24:27 +00:00
Allan Carr
39a4646339 IO-2271 Country Region for Print Center
Hide reports based on Country Code for Region
2023-05-09 18:11:06 -07:00
Allan Carr
584322819f IO-2265 Autohouse Extract Remove Customer Info 2023-05-09 16:51:55 -07:00
Patrick Fic
051ee347a9 Merged in release/2023-05-05 (pull request #768)
Release/2023 05 05
2023-05-05 22:51:16 +00:00
Patrick Fic
acf1b387de Merged in release/2023-05-05 (pull request #767)
Release/2023 05 05
2023-05-05 22:03:21 +00:00
John Allen Delos Reyes
d350515c90 Merged in feature/IO-1722-job-size-color (pull request #763)
Feature/IO-1722 job size color

Approved-by: Patrick Fic
2023-05-05 22:02:55 +00:00
Allan Carr
121e579388 Merged in feature/IO-2261-opensearch-replacements (pull request #766)
IO-2261 Missing loader handler for owner_owing, correct payment

Approved-by: Patrick Fic
2023-05-05 22:02:03 +00:00
Allan Carr
cf5ebb8130 IO-2261 Missing loader for owner_owing 2023-05-05 14:24:00 -07:00
Allan Carr
2dabf3c811 IO-2261 Missing loader handler for owner_owing, correct payment 2023-05-05 14:03:05 -07:00
swtmply
04509fa587 IO-1722 adjusted logic for card color 2023-05-06 04:56:03 +08:00
swtmply
56fef0f43c IO-1722 Added reset button 2023-05-06 04:55:21 +08:00
Patrick Fic
42702ef015 Merged in release/2023-05-05 (pull request #765)
Release/2023 05 05
2023-05-05 20:19:03 +00:00
Allan Carr
25bee3cfdf Merged in feature/IO-2261-opensearch-replacements (pull request #764)
Feature/IO-2261 opensearch replacements

Approved-by: Patrick Fic
2023-05-05 20:14:02 +00:00
Allan Carr
baf06fee6c Merge branch 'feature/IO-2261-opensearch-replacements' of https://bitbucket.org/snaptsoft/bodyshop into feature/IO-2261-opensearch-replacements 2023-05-05 13:10:39 -07:00
Allan Carr
a3557bbc86 IO-2261 Add Delete function, Correct Payment edit, add Totals to Bills handler 2023-05-05 13:10:07 -07:00
swtmply
3e00e7981d IO-1722 added placement to popover 2023-05-06 03:34:55 +08:00
swtmply
12fa270a1a IO-1722 adjusted background colors 2023-05-06 03:34:22 +08:00
Patrick Fic
de250b152a Change all open search events to trigger on all fields. 2023-05-05 12:20:25 -07:00
Patrick Fic
c45741257f Add export trigger for os_bills event. 2023-05-05 12:16:39 -07:00
swtmply
3988386c79 IO-1722 replaced hex to rgba 2023-05-06 02:56:02 +08:00
Patrick Fic
492032c1e2 Merged in release/2023-05-05 (pull request #762)
Release/2023 05 05
2023-05-05 17:56:36 +00:00
Allan Carr
8222e56485 Merged in feature/IO-2261-opensearch-replacements (pull request #761)
IO-2261 Add in missing field "date" for Payments

Approved-by: Patrick Fic
2023-05-05 17:56:14 +00:00
Patrick Fic
49a61e1564 Merge branch 'feature/IO-2261-opensearch-replacements' of https://bitbucket.org/snaptsoft/bodyshop into feature/IO-2261-opensearch-replacements 2023-05-05 10:55:59 -07:00
Patrick Fic
eed18aa1c5 Add event triggers on export fields 2023-05-05 10:55:42 -07:00
Allan Carr
2de3f8b022 IO-2261 Add in missing field "date" for Payments 2023-05-05 10:54:41 -07:00
Patrick Fic
91476c7ad3 Merged in release/2023-05-05 (pull request #760)
Release/2023 05 05
2023-05-05 17:44:50 +00:00
John Allen Delos Reyes
00eb7926f9 Merged in feature/IO-2266-duplicate-option (pull request #759)
IO-2266 fixed key issue causing option to duplicate

Approved-by: Patrick Fic
2023-05-05 17:44:19 +00:00
Patrick Fic
854ad21b20 Update autohouse header auth. 2023-05-05 10:16:41 -07:00
swtmply
3c3f9521f6 IO-2266 fixed key issue causing option to duplicate 2023-05-05 23:43:48 +08:00
Patrick Fic
561bcf10d9 Merged in release/2023-05-05 (pull request #758)
IO-2261 All Jobs, Bills, Payments
2023-05-05 14:49:55 +00:00
Allan Carr
75d9faa05b Merged in feature/IO-2261-opensearch-replacements (pull request #757)
IO-2261 All Jobs, Bills, Payments

Approved-by: Patrick Fic
2023-05-05 14:49:23 +00:00
Allan Carr
ac72177fbb IO-2261 All Jobs, Bills, Payments
Adjust Pagination settings and on clear delete search.page to reset. Adjust payments for bodyshopid as it was pointed at bill instead of payment
2023-05-04 18:27:47 -07:00
Allan Carr
088faf152c Merged in release/2023-05-05 (pull request #756)
Release/2023 05 05

Approved-by: Patrick Fic
2023-05-04 22:33:12 +00:00
Patrick Fic
90cba9ed24 Merge branch 'feature/IO-1722-job-size-color' into release/2023-05-05 2023-05-04 15:32:30 -07:00
Patrick Fic
a0702785c5 Revert "Revert "Merged in feature/IO-1722-job-size-color (pull request #746)""
This reverts commit 7dc3c00628.
2023-05-04 15:32:21 -07:00
Allan Carr
6898d609fe IO-2261 Add Loading effect 2023-05-04 15:17:37 -07:00
Allan Carr
d70893e2ba Merged in feature/IO-2261-opensearch-replacements (pull request #748)
Feature/IO-2261 opensearch replacements
2023-05-04 22:16:49 +00:00
Patrick Fic
6155b8bf24 Merged in release/2023-05-05 (pull request #754)
Add firebase auth to JSR call.
2023-05-04 21:04:16 +00:00
Patrick Fic
239dc5c62d Merged in release/2023-05-05 (pull request #753)
Release/2023 05 05
2023-05-04 20:36:18 +00:00
Patrick Fic
2586855f11 Add firebase auth to JSR call. 2023-05-04 13:33:16 -07:00
Patrick Fic
b3b3c4c737 Merged in release/2023-05-05 (pull request #752)
Resolve health check
2023-05-04 20:26:04 +00:00
Patrick Fic
abe5fadeea Resolve health check 2023-05-04 13:25:45 -07:00
swtmply
08e5543536 IO-1722 adjusted condition for text color 2023-05-05 04:00:44 +08:00
Patrick Fic
f1a10e0df4 Merged in release/2023-05-05 (pull request #750)
Release/2023 05 05
2023-05-04 19:57:22 +00:00
Patrick Fic
7dc3c00628 Revert "Merged in feature/IO-1722-job-size-color (pull request #746)"
This reverts commit 7594f53e88, reversing
changes made to b861957342.
2023-05-04 12:50:28 -07:00
Patrick Fic
1c5c403d65 Merged in release/2023-05-05 (pull request #749)
Release/2023 05 05
2023-05-04 19:34:56 +00:00
John Allen Delos Reyes
7594f53e88 Merged in feature/IO-1722-job-size-color (pull request #746)
Feature/IO-1722 job size color

Approved-by: Patrick Fic
2023-05-04 19:03:30 +00:00
Patrick Fic
b861957342 Additional security hardening. 2023-05-04 11:59:39 -07:00
swtmply
2bf24ff5a1 IO-1722 refactor color function 2023-05-04 23:00:37 +08:00
Allan Carr
6e5fcbfdbd IO-2267 Remove Sort for Linked Table fields 2023-05-03 16:30:48 -07:00
Allan Carr
759a8ac58c IO-2261 Opensearch for Payments, Bills and All Jobs 2023-05-03 16:29:01 -07:00
swtmply
833baca9cc Merge branch 'feature/IO-1722-job-size-color' of https://bitbucket.org/snaptsoft/bodyshop into feature/IO-1722-job-size-color 2023-05-04 01:27:24 +08:00
swtmply
889ef61185 IO-1722 fixed spacing on translations 2023-05-04 01:25:17 +08:00
swtmply
add1eddbc1 IO-1722 added translations 2023-05-04 01:18:16 +08:00
Allan Carr
1c63aa39c4 Merged in release/2023-05-05 (pull request #744)
Release/2023 05 05
2023-05-03 17:10:48 +00:00
swtmply
0cabd80b94 IO-1722 job size color in production card 2023-05-04 00:39:50 +08:00
Patrick Fic
7f756bab88 Merged in feature/IO-2190-Autohouse (pull request #743)
IO-2190 Autohouse & Job Costing
2023-05-02 21:53:40 +00:00
Allan Carr
99b847822f Merged in feature/IO-2190-Autohouse (pull request #742)
IO-2190 Autohouse & Job Costing

Approved-by: Patrick Fic
2023-05-02 21:52:50 +00:00
Allan Carr
f66d9b8c09 IO-2190 Autohouse & Job Costing
Insure that amount going into Dinero is Integer
2023-05-02 14:51:47 -07:00
Patrick Fic
5660de42af Add support email to server side emails. 2023-05-02 12:31:18 -07:00
Patrick Fic
ccbe92c275 Merged in feature/IO-2190-Autohouse (pull request #741)
IO-2190 Correct lookup location for MAPA Hrs
2023-05-02 17:06:12 +00:00
Allan Carr
b0ea8a71fb Merged in feature/IO-2190-Autohouse (pull request #738)
IO-2190 Correct lookup location for MAPA Hrs

Approved-by: Patrick Fic
2023-05-01 22:30:13 +00:00
Allan Carr
060871306f Merged in feature/IO-2257-PBS-EXPORT-CREDIT (pull request #737)
IO-2257 PBS Export of Credits

Approved-by: Patrick Fic
2023-05-01 22:30:00 +00:00
Allan Carr
2eb4e142ff IO-2190 Correct lookup location for MAPA Hrs 2023-05-01 13:48:18 -07:00
Patrick Fic
adf8cf9e8d Additional hasura indexes. 2023-05-01 13:05:59 -07:00
Patrick Fic
852fd9c388 IO-2261 Sample open search replacement. 2023-05-01 13:04:10 -07:00
Patrick Fic
e921f28105 Merged in release/2023-04-28 (pull request #736)
Additional database indexing.
2023-04-28 16:51:46 +00:00
Patrick Fic
6db68b76db Merged in release/2023-04-28 (pull request #735)
Additional database indexing.
2023-04-28 16:51:31 +00:00
Patrick Fic
51ebfd86e7 Additional database indexing. 2023-04-28 09:37:49 -07:00
Allan Carr
79a90bb9ee IO-2257 PBS Export of Credits
Amounts less then 0 (ie all credits) would not get pushed to transactionObject. Expand to be both sides of zero to allow for credits within transactionObject
2023-04-27 17:13:37 -07:00
Patrick Fic
1664f9c935 Merged in release/2023-04-28 (pull request #733)
Resolve Loader script issue.
2023-04-27 22:21:15 +00:00
Patrick Fic
23fcdd6375 Merged in release/2023-04-28 (pull request #732)
Release/2023 04 28
2023-04-27 22:21:03 +00:00
Patrick Fic
ea7c22daec Resolve Loader script issue. 2023-04-27 15:20:41 -07:00
Patrick Fic
10ffb33ec9 Merged in release/2023-04-28 (pull request #731)
Remove unneeded imports.
2023-04-27 21:33:39 +00:00
Patrick Fic
f9e023f922 Remove unneeded imports. 2023-04-27 14:19:27 -07:00
Patrick Fic
c7832bdd82 Merged in release/2023-04-28 (pull request #730)
Release/2023 04 28
2023-04-27 21:13:54 +00:00
Patrick Fic
329bdbe22d Merged in release/2023-04-28 (pull request #729)
Release/2023 04 28
2023-04-27 20:54:32 +00:00
Patrick Fic
2df046c39d IO-1053 Fix issues with scoreboard updates. 2023-04-27 13:52:01 -07:00
Patrick Fic
46065f1986 IO-1933 Resolve tech efficency component. 2023-04-27 13:13:16 -07:00
Allan Carr
66b3fb6988 Merged in release/2023-04-28 (pull request #728)
IO-2190 Factor Paint Costs from Scale over to Job Costing & Autohouse

Approved-by: Patrick Fic
2023-04-27 19:47:13 +00:00
swtmply
5eda224393 IO-1053 Added Job Total to graph 2023-04-28 01:42:22 +08:00
Patrick Fic
e387abcd14 IO-1741 IO-2147 IO-1933 Bug fixes for multiple tickets. 2023-04-27 10:20:46 -07:00
Allan Carr
4c2d4e20a6 IO-2190 Factor Paint Costs from Scale over to Job Costing & Autohouse
Add use_paint_scale_data to bodyshop query
2023-04-27 09:40:12 -07:00
Patrick Fic
3d26c2e94e Merged in release/2023-04-28 (pull request #727)
Release/2023 04 28
2023-04-27 16:32:43 +00:00
swtmply
d9e2ef9300 IO-1933 Added statistics on tech page 2023-04-27 22:19:32 +08:00
swtmply
b00fdadc1b IO-1741 Added touched property on calculate button 2023-04-27 22:19:00 +08:00
swtmply
4d5e06b9fc IO-2157 Added save to job notes button 2023-04-27 22:18:45 +08:00
Patrick Fic
ad42dd1295 Merged in release/2023-04-28 (pull request #726)
IO-2210 Remove unused state.
2023-04-26 23:52:34 +00:00
Patrick Fic
1f4c1c9e92 IO-2210 Remove unused state. 2023-04-26 16:51:53 -07:00
Patrick Fic
2938b9c94c Merged in release/2023-04-28 (pull request #725)
IO-2210 Search Improvements
2023-04-26 23:45:43 +00:00
Patrick Fic
2227acab3a IO-2210 Search Improvements 2023-04-26 16:45:20 -07:00
Patrick Fic
bde17446ad Merged in release/2023-04-28 (pull request #724)
Release/2023 04 28
2023-04-26 19:49:54 +00:00
Patrick Fic
081165b6f5 Resolve missing items from merge conflicts. 2023-04-26 12:46:41 -07:00
Allan Carr
7e717c0b1f Merge branch 'release/2023-04-28' of https://bitbucket.org/snaptsoft/bodyshop into release/2023-04-28
# Conflicts:
#	yarn.lock
2023-04-26 12:23:50 -07:00
Allan Carr
e242aaa9f5 IO-2190 Factor Paint Costs from Scale over to Job Costing & Autohouse
If Paint Scale Data exist in MixData then use the LiquidCost is use_paint_scale_data is set to true else fall back to cost calculations
2023-04-26 12:05:14 -07:00
Patrick Fic
fe60538acf Merged in release/2023-04-28 (pull request #723)
Resolve missing items from merge conflicts.
2023-04-26 17:21:20 +00:00
Patrick Fic
2db88f57df Resolve missing items from merge conflicts. 2023-04-26 10:20:24 -07:00
Patrick Fic
7461e58000 Merged in release/2023-04-28 (pull request #722)
Release/2023 04 28
2023-04-26 16:44:29 +00:00
Patrick Fic
cd0b7a4e56 Add additional indexes. 2023-04-26 09:42:43 -07:00
Allan Carr
2a3b4e89ab Merge branch 'release/2023-04-28' of https://bitbucket.org/snaptsoft/bodyshop into release/2023-04-28
# Conflicts:
#	hasura/metadata/tables.yaml
2023-04-26 09:13:34 -07:00
Allan Carr
1d77eda3e2 IO-2190 Add extra field in for this item 2023-04-26 09:02:36 -07:00
swtmply
490e1f696a IO-1741 Added touched property on calculate button 2023-04-26 22:33:30 +08:00
swtmply
b96c618f54 IO-2157 Added save to job notes button 2023-04-26 22:32:46 +08:00
Patrick Fic
1afda01d34 Merged in release/2023-04-28 (pull request #721)
Release/2023 04 28
2023-04-26 02:22:48 +00:00
Patrick Fic
b9d11580d4 IO-2210 Opensearch implementation. 2023-04-25 18:47:11 -07:00
Patrick Fic
27fadb9ae2 Additional payroll schema tracking. 2023-04-25 10:16:56 -07:00
Patrick Fic
4c5a2cefe9 Payroll schema updates. 2023-04-25 09:33:17 -07:00
Patrick Fic
175692559c Merged in release/2023-04-28 (pull request #720)
Release/2023 04 28
2023-04-24 21:45:57 +00:00
Patrick Fic
57b27f73c3 Added a search filter for greater than 3 characters. 2023-04-24 14:45:08 -07:00
Patrick Fic
7badb09ba1 Timeticket Approval Queue schema updates for payroll updates. 2023-04-24 13:39:59 -07:00
Patrick Fic
c39f1d824a Merged in release/2023-04-21 (pull request #719)
Release/2023 04 21
2023-04-21 20:18:25 +00:00
Patrick Fic
132cf98a37 IO-2086 Resolve local media server pulling full size image for thumbnail. 2023-04-21 12:21:28 -07:00
Patrick Fic
59f71d53cd Merged in release/2023-04-21 (pull request #718)
Release/2023 04 21
2023-04-21 18:42:34 +00:00
Patrick Fic
f4b3a990d7 Additional schema changes for payroll. 2023-04-21 08:42:47 -07:00
swtmply
20371ea00d Merge branch 'release/2023-04-21' of https://bitbucket.org/snaptsoft/bodyshop into release/2023-04-21 2023-04-21 22:28:00 +08:00
swtmply
3086a654a1 IO-1461 made the sider sticky 2023-04-21 22:27:48 +08:00
Patrick Fic
60768c8847 Timeticket approval queue aggregation permissions. 2023-04-20 15:28:14 -07:00
Patrick Fic
a68f8d6880 Payroll schema changes. 2023-04-20 15:05:23 -07:00
Allan Carr
0cad64ff6d Merged in release/2023-04-21 (pull request #717)
IO-2335 Exported GSR to Excel

Approved-by: Patrick Fic
2023-04-20 19:58:58 +00:00
Allan Carr
522665256e IO-2335 Exported GSR to Excel
Change idtype to reporttype to prevent issues later on down the road if we extend more reports to excel format
2023-04-20 12:43:06 -07:00
swtmply
d3fe2c9d06 IO-2011 removed column background color 2023-04-21 03:37:36 +08:00
swtmply
f080e84985 IO-2242 added email on vendor query 2023-04-21 02:56:11 +08:00
Allan Carr
2a0ad46eea IO-2235
Changes in Prettier
2023-04-20 09:46:21 -07:00
Allan Carr
3a83160b33 Merged in release/2023-04-21 (pull request #716)
IO-2235 Exported GSR to Excel

Approved-by: Patrick Fic
2023-04-20 16:45:28 +00:00
Allan Carr
7725080a11 IO-2235 Exported GSR to Excel
For Morrey Body Shop
2023-04-20 09:33:22 -07:00
Allan Carr
701c532e48 Merged in release/2023-04-21 (pull request #715)
Release/2023 04 21

Approved-by: Patrick Fic
2023-04-19 23:14:52 +00:00
Allan Carr
1932795f55 Merge branch 'release/2023-04-21' of https://bitbucket.org/snaptsoft/bodyshop into release/2023-04-21 2023-04-19 15:44:41 -07:00
Allan Carr
cc7bd1c792 IO-2243 DMS Posting Sheet Report
Requested by Morrey Auto Body
2023-04-19 15:44:09 -07:00
Patrick Fic
7799d93f3d Merged in release/2023-04-21 (pull request #714)
Disable job status transition tracking.
2023-04-19 21:26:37 +00:00
Patrick Fic
a7cf36d5f8 Disable job status transition tracking. 2023-04-19 14:26:12 -07:00
Patrick Fic
54cc02068c Merged in release/2023-04-21 (pull request #713)
Release/2023 04 21
2023-04-19 21:11:45 +00:00
Patrick Fic
f575870685 Added missed limit on global search. 2023-04-19 14:10:33 -07:00
Patrick Fic
171b61b92f Included a limit on global search query to limit performance impact. 2023-04-19 14:08:57 -07:00
Patrick Fic
de44116940 Merged in release/2023-04-21 (pull request #712)
Resolve status transition error message.
2023-04-19 20:08:39 +00:00
Patrick Fic
3c0a883326 Resolve status transition error message. 2023-04-19 13:08:18 -07:00
Patrick Fic
14d6cc94dd Merged in release/2023-04-21 (pull request #711)
IO-2086 Resolve cloudinary based thumbnail retrieval.
2023-04-19 18:45:22 +00:00
Patrick Fic
c0d9bacf1d Merged in release/2023-04-21 (pull request #710)
Release/2023 04 21
2023-04-19 18:44:50 +00:00
Patrick Fic
d787821345 IO-2086 Resolve cloudinary based thumbnail retrieval. 2023-04-19 11:43:24 -07:00
Patrick Fic
ed6eab4c38 Merged in release/2023-04-14 (pull request #708)
Remove erroneous console log.
2023-04-14 20:05:43 +00:00
Patrick Fic
b12c9407d9 Remove erroneous console log. 2023-04-14 13:05:09 -07:00
Patrick Fic
300aee5b02 Merged in release/2023-04-14 (pull request #707)
Release/2023 04 14
2023-04-14 20:04:15 +00:00
Patrick Fic
357f40bdc2 Merged in release/2023-04-14 (pull request #706)
Release/2023 04 14
2023-04-14 20:03:58 +00:00
Patrick Fic
0a93551db4 Merge branch 'release/2023-04-14' of https://bitbucket.org/snaptsoft/bodyshop into release/2023-04-14
# Conflicts:
#	client/src/components/scoreboard-timetickets/scoreboard-timetickets.component.jsx
2023-04-14 12:30:22 -07:00
Patrick Fic
8602ccbb8a IO-2240 Resolve efficiency calculation issue. 2023-04-14 12:29:11 -07:00
swtmply
d7b0e3046b IO-2240 Adjusted calculation for total efficiency 2023-04-15 03:26:56 +08:00
Patrick Fic
30689a8ca6 Merged in release/2023-04-14 (pull request #705)
Release/2023 04 14
2023-04-14 18:39:19 +00:00
swtmply
0e06b449cb IO-2240 Adjusted the location of the calculation 2023-04-15 01:18:29 +08:00
swtmply
8e8208dd9a IO-2240 Moved the calculation within the function 2023-04-15 01:11:44 +08:00
swtmply
79e2fecb24 IO-2239 Removed total title 2023-04-14 23:33:14 +08:00
swtmply
86e909e4e9 Merge branch 'release/2023-04-14' of https://bitbucket.org/snaptsoft/bodyshop into release/2023-04-14 2023-04-14 02:53:14 +08:00
swtmply
c7ff893397 IO-1412 Fixed column resize drag speed 2023-04-14 02:53:04 +08:00
Patrick Fic
3981b8684c IO-2244 Round difference calculation to 1 decimal point for productive hours claiming enforcement. 2023-04-13 10:11:15 -07:00
swtmply
1fb856f95f Merge branch 'release/2023-04-14' of https://bitbucket.org/snaptsoft/bodyshop into release/2023-04-14 2023-04-14 00:41:00 +08:00
swtmply
c62c3fa938 IO-2240 total for productive hours in summary 2023-04-14 00:40:50 +08:00
Patrick Fic
554ec37ace Merged in release/2023-04-14 (pull request #704)
Release/2023 04 14
2023-04-13 16:36:16 +00:00
Patrick Fic
be4feca990 Clean up unneeded import. 2023-04-13 09:26:24 -07:00
Patrick Fic
ec45454b3d IO-2244 Restrict claimable productive hours based on remaining hours 2023-04-13 09:25:52 -07:00
swtmply
0e78cb47f9 Merge branch 'release/2023-04-14' of https://bitbucket.org/snaptsoft/bodyshop into release/2023-04-14 2023-04-13 22:04:01 +08:00
Patrick Fic
e5b8d003ec IO-2137 Adjust PBS company name logic for posting. 2023-04-13 22:03:35 +08:00
Patrick Fic
2eb81dde37 Add ppc field to job lines. 2023-04-13 22:03:35 +08:00
Patrick Fic
614549a545 Include employee team changes from Payroll Based Changes. 2023-04-13 22:03:35 +08:00
Patrick Fic
5717727d2a IO-2243 Capture successful CDK posting details. 2023-04-13 22:03:35 +08:00
swtmply
f3714cea1e IO-2239 Added totals on cards in job scoreboards 2023-04-13 22:03:35 +08:00
swtmply
a3375e6152 IO-2238 Changed vehicle column to be a hyperlink 2023-04-13 22:03:24 +08:00
Patrick Fic
f48fb7130e IO-2137 Adjust PBS company name logic for posting. 2023-04-12 13:16:18 -07:00
Patrick Fic
1460fa6fd7 Add ppc field to job lines. 2023-04-12 13:13:51 -07:00
Patrick Fic
3d9a07bd39 Include employee team changes from Payroll Based Changes. 2023-04-12 13:08:39 -07:00
Patrick Fic
bd4aa4027a IO-2243 Capture successful CDK posting details. 2023-04-12 13:02:45 -07:00
swtmply
9fa995f002 IO-2239 Added totals on cards in job scoreboards 2023-04-12 03:13:19 +08:00
swtmply
3ed48b26f1 IO-2338 Changed vehicle column to be a hyperlink 2023-04-12 01:44:45 +08:00
swtmply
d585cacdfc IO-1412 Fixed column resize 2023-04-12 00:57:13 +08:00
Patrick Fic
55ddaca328 Merged in release/2023-04-07 (pull request #703)
Release/2023 04 07
2023-04-06 00:22:52 +00:00
Patrick Fic
3fc7af9780 Merged in release/2023-04-07 (pull request #702)
Release/2023 04 07
2023-04-06 00:22:37 +00:00
Patrick Fic
146bf95e51 IO-2086 Resolve additional gallery import issue & tech login issue. 2023-04-05 17:22:18 -07:00
Patrick Fic
58defad2ea IO-2086 Resolve imports for additional classes with gallery. 2023-04-05 17:21:47 -07:00
Patrick Fic
9c40a03a06 Merged in release/2023-04-07 (pull request #701)
Release/2023 04 07
2023-04-05 22:09:28 +00:00
John Allen Delos Reyes
a1b6ccc23d Merged in release/2023-04-07 (pull request #700)
Release/2023 04 07

Approved-by: Patrick Fic
2023-04-05 21:21:39 +00:00
Patrick Fic
9b4247d6f6 Revert "fixed column resize"
This reverts commit 6e7d1abd70.
2023-04-05 14:20:15 -07:00
Patrick Fic
8a6d94f193 Resolve time ticket posting issue. 2023-04-05 14:02:34 -07:00
swtmply
6e7d1abd70 fixed column resize 2023-04-05 02:35:07 +08:00
swtmply
6bd74aae87 document galleries 2023-04-01 03:26:08 +08:00
Patrick Fic
f21caa10fc Merged in release/2023-03-21 (pull request #697)
IO-2213 Resolve issue with filtering on schedule screen.
2023-03-21 16:10:03 +00:00
Patrick Fic
3d164eb070 IO-2213 Resolve issue with filtering on schedule screen. 2023-03-21 09:09:03 -07:00
Patrick Fic
88ab3a21e2 Merged in release/2023-03-17 (pull request #695)
Release/2023 03 17
2023-03-17 18:44:06 +00:00
Patrick Fic
dde6f17029 Merged in release/2023-03-17 (pull request #694)
Language updates & default for lost sale reason.
2023-03-17 18:43:52 +00:00
Patrick Fic
45bc1893a0 Language updates & default for lost sale reason. 2023-03-17 11:23:29 -07:00
Allan Carr
1aceef9153 Merged in release/2023-03-17 (pull request #693)
IO-2193 Delete Owner & Vehicle

Approved-by: Patrick Fic
2023-03-17 01:06:10 +00:00
Allan Carr
d053e682d7 IO-2193 Delete Owner & Vehicle
Pass error message on deleting to translation
2023-03-16 17:58:12 -07:00
Allan Carr
984a4a4cf6 IO-2193 Delete Owner & Vehicle 2023-03-16 16:44:12 -07:00
Patrick Fic
678892d134 Merged in release/2023-03-17 (pull request #692)
IO-2177 Add enforce category on conversion.
2023-03-16 21:51:01 +00:00
Patrick Fic
9438ef9683 IO-2177 Add enforce category on conversion. 2023-03-16 14:15:29 -07:00
Allan Carr
dc187bbf24 Merged in release/2023-03-17 (pull request #691)
Release/2023 03 17

Approved-by: Patrick Fic
2023-03-16 18:51:11 +00:00
Allan Carr
10a354e479 IO-2220 Customer List Report
Customer List report for Loewen Body Shop
2023-03-16 11:04:12 -07:00
Patrick Fic
31092c20a9 Updated hasura metadata 2023-03-16 07:59:40 -07:00
Patrick Fic
09aae78715 Merged in release/2023-03-17 (pull request #690)
Release/2023 03 17
2023-03-15 18:53:49 +00:00
Patrick Fic
86beaf049c IO-2219 Resolve employees not needing job to post time ticket. 2023-03-14 14:41:14 -07:00
Patrick Fic
3bc0653230 IO-2215 Critical Parts scanning.
IO-2215 Critical Parts Scanning

IO-2215 critical parts scanning
2023-03-14 12:30:55 -07:00
Patrick Fic
b950b3f825 IO-2215 Critical Parts Scanning 2023-03-14 08:41:35 -07:00
Patrick Fic
3891fbefdf IO-2215 Critical parts scanning 2023-03-14 08:41:26 -07:00
Patrick Fic
73bcc72fc3 IO-2213 Add Ins Co filtering to several pages. 2023-03-13 14:37:05 -07:00
Patrick Fic
f3911859c7 IO-2214 Add lost sale reason tracking. 2023-03-13 12:07:25 -07:00
Patrick Fic
12e3d61cfb Merged in release/2023-03-10 (pull request #689)
Release/2023 03 10
2023-03-10 22:55:07 +00:00
Patrick Fic
c42276ab3a Merged in release/2023-03-10 (pull request #688)
Release/2023 03 10
2023-03-10 18:51:03 +00:00
Patrick Fic
4cba91e097 IO-2203 Resolve RO staying after payment modal close. 2023-03-09 11:23:46 -08:00
Patrick Fic
c695aea12e Autohouse change to respect flat rate employees 2023-03-09 11:08:47 -08:00
Patrick Fic
111f554dea IO-9 Remove license decode button. 2023-03-08 16:26:47 -08:00
Patrick Fic
fe2a731b5f Merged in release/2023-03-03 (pull request #687)
Release/2023 03 03
2023-03-06 19:48:13 +00:00
Patrick Fic
61e6511547 Add missing field to query. 2023-03-06 11:42:58 -08:00
Patrick Fic
85346e203b Update to env file. 2023-03-06 11:41:00 -08:00
Patrick Fic
41d25cbc52 Merged in release/2023-03-03 (pull request #686)
release/2023-03-03

Approved-by: Patrick Fic
2023-03-03 17:28:32 +00:00
Patrick Fic
c565e2199d IO-2195 Add additional check for conversation query. 2023-03-03 09:28:10 -08:00
Patrick Fic
085c27ad20 Merged in release/2023-03-03 (pull request #685)
release/2023-03-03

Approved-by: Patrick Fic
2023-03-03 17:08:45 +00:00
Patrick Fic
8fa0946cfa IO-2195 Conversation query performance improvements. 2023-03-03 09:00:44 -08:00
Patrick Fic
a1ab254d6f Update hasura metadata. 2023-03-02 20:38:10 -08:00
Patrick Fic
38e6b5010e Merged in release/2023-03-03 (pull request #684)
release/2023-03-03

Approved-by: Patrick Fic
2023-03-03 04:05:30 +00:00
Patrick Fic
99d3943955 Improve changing active shop profile methods to avoid multiple queries. 2023-03-02 20:04:54 -08:00
Patrick Fic
2415b4c2b4 IO-2194 Static control numbers for CDK. 2023-03-02 19:39:24 -08:00
Patrick Fic
99c7ba1fbc Disable ARMS on server side. 2023-03-02 19:38:37 -08:00
Patrick Fic
36e593f806 Io-2192 Add predefined vehicles list. 2023-03-02 19:06:08 -08:00
Patrick Fic
d825c04850 Package update modifications & IO-2192 Add ability to preselect make and model when creating a job. 2023-03-02 08:50:05 -08:00
Patrick Fic
ca7dfacec4 Package updates. 2023-03-01 13:01:48 -08:00
Patrick Fic
1138540518 Merged in release/2023-02-24 (pull request #682)
Release/2023 02 24
2023-02-27 18:11:31 +00:00
Allan Carr
242c275e7d Merged in release/2023-02-24 (pull request #680)
Release/2023 02 24

Approved-by: Patrick Fic
2023-02-23 19:44:40 +00:00
Allan Carr
59db305cb8 IO-2186 Production Over Time
Add new report requested by Loewen Body Shop
2023-02-23 11:14:00 -08:00
Patrick Fic
fea69fe3a5 Add payment response object. 2023-02-22 08:15:58 -08:00
Patrick Fic
43e4ff911e Merged in release/2023-02-17 (pull request #679)
Release/2023 02 17
2023-02-17 23:39:24 +00:00
Allan Carr
1dc6130fdf Merged in release/2023-02-17 (pull request #678)
Release/2023 02 17

Approved-by: Patrick Fic
2023-02-17 18:37:20 +00:00
Allan Carr
ae4cff98e7 IO-2175 Category Dropdown Clear
Allow for clear and correct for Select allowClear sending undefined and convert that to null. Overload UndefinedtoNull to have an array of keys
2023-02-16 17:26:57 -08:00
Allan Carr
3650cacb51 IO-2173 Job Line Discount
Correct for when new line is added with no part
2023-02-16 17:23:09 -08:00
Patrick Fic
c2bf6841e1 Documentation updates. 2023-02-14 15:05:09 -08:00
Allan Carr
910afbf48d Merged in release/2023-02-17 (pull request #676)
Release/2023 02 17

Approved-by: Patrick Fic
2023-02-14 22:03:01 +00:00
Allan Carr
f41b94d16d Merge branch 'release/2023-02-17' of https://bitbucket.org/snaptsoft/bodyshop into release/2023-02-17 2023-02-14 11:32:21 -08:00
Allan Carr
24da0207e5 IO-2173 Client Fusion - Job Line Discount not allowed
Adjust client job-line-upsert-modal to allow for discounts as markups were only allowed before. Convert prt_dsmk_p over to prt_dsmk_m on save of modal for both markup/discount for handling and viewing
2023-02-14 11:30:09 -08:00
Patrick Fic
bf34765e6b Additional Hasura Indexes. 2023-02-13 11:21:59 -08:00
Patrick Fic
4c98a347f5 Additional indexes for performance improvements. 2023-02-13 10:37:10 -08:00
Patrick Fic
840e760619 Merged in release/2023-02-10 (pull request #675)
Release/2023 02 10
2023-02-10 23:34:55 +00:00
Patrick Fic
9d9edfd674 Merged in release/2023-02-10 (pull request #674)
Improve display of job lines preset display.
2023-02-10 23:22:47 +00:00
Patrick Fic
739265ee6a Improve display of job lines preset display. 2023-02-10 09:08:39 -08:00
Allan Carr
62a5b49836 Merged in release/2023-02-10 (pull request #673)
Release/2023 02 10

Approved-by: Patrick Fic
2023-02-09 17:56:24 +00:00
Allan Carr
038aaf249e IO-2160 Purchase & Return Ratio by Vendor Reports 2023-02-08 18:37:10 -08:00
Patrick Fic
e0eb4657d2 Resolve null displays of vehicle names. 2023-02-06 12:45:19 -08:00
Patrick Fic
02a6ccd481 Add indexes to export logs. 2023-02-06 10:39:47 -08:00
Patrick Fic
79e75a5e73 Merged in release/2023-02-03 (pull request #672)
IO-2162 Resolve display issues on scheduling modal .

Approved-by: Patrick Fic
2023-02-06 18:09:14 +00:00
Patrick Fic
e93e138f78 Merged in release/2023-02-03 (pull request #671)
release/2023-02-03

Approved-by: Patrick Fic
2023-02-06 18:09:03 +00:00
Patrick Fic
8d22248f4b IO-2162 Resolve display issues on scheduling modal . 2023-02-06 09:02:33 -08:00
Patrick Fic
bb8024ba9c Merged in release/2023-02-03 (pull request #670)
release/2023-02-03

Approved-by: Patrick Fic
2023-02-04 01:20:00 +00:00
Patrick Fic
4639e31e55 Merged in release/2023-02-03 (pull request #669)
release/2023-02-03

Approved-by: Patrick Fic
2023-02-04 01:19:46 +00:00
Patrick Fic
a960963e36 IO-2162 Smart Scheduling updates. 2023-02-03 17:19:30 -08:00
Patrick Fic
e8fde14f9b Merged in release/2023-02-03 (pull request #668)
release/2023-02-03

Approved-by: Patrick Fic
2023-02-03 22:58:21 +00:00
Patrick Fic
3be50b5067 IO-2162 Improved UI for scheduled problem jobs. 2023-02-03 13:48:49 -08:00
Patrick Fic
90e87adc34 Merged in release/2023-02-03 (pull request #667)
release/2023-02-03

Approved-by: Patrick Fic
2023-02-03 20:34:27 +00:00
Patrick Fic
563c1d2402 IO-2162 Resolve smart scheduling issues. 2023-02-03 11:48:07 -08:00
Patrick Fic
81053b3cbf Merged in release/2023-02-03 (pull request #666)
release/2023-02-03

Approved-by: Patrick Fic
2023-02-02 21:47:46 +00:00
Patrick Fic
2108a4e96c IO-2163 Revise report key. 2023-02-02 13:47:28 -08:00
Patrick Fic
f4290bf20c Merged in release/2023-02-03 (pull request #665)
release/2023-02-03

Approved-by: Patrick Fic
2023-02-02 19:06:20 +00:00
Patrick Fic
c04a690dc3 IO-2162 Update smart scheduling server side. 2023-02-02 11:06:05 -08:00
Patrick Fic
5371657aa4 Merged in release/2023-02-03 (pull request #664)
release/2023-02-03

Approved-by: Patrick Fic
2023-02-02 18:42:40 +00:00
Patrick Fic
20e84668a5 IO-2162 remove unnecessary conditions. 2023-02-02 10:42:16 -08:00
Patrick Fic
599f4e143c Merged in release/2023-02-03 (pull request #663)
release/2023-02-03

Approved-by: Patrick Fic
2023-02-02 18:10:12 +00:00
Patrick Fic
2e40583d31 IO-2164 IO-2163 Additional GSR reports. 2023-02-02 10:08:55 -08:00
Patrick Fic
07c307e17b IO-2162 Updated smart schedule graph logic. 2023-02-02 10:04:11 -08:00
Patrick Fic
94440e5c48 Merged in master (pull request #662)
Merged in release/2023-01-27 (pull request #661)
2023-01-27 23:14:44 +00:00
Patrick Fic
176774a888 Merged in release/2023-01-27 (pull request #661)
Release/2023 01 27
2023-01-27 22:29:51 +00:00
Patrick Fic
aa5d6f2090 Merged in release/2023-01-27 (pull request #660)
IO-2156 Resolve AH detail line validation issue.
2023-01-27 21:21:21 +00:00
Patrick Fic
7b3dcf295e IO-2156 Resolve AH detail line validation issue. 2023-01-27 13:20:53 -08:00
Patrick Fic
6bde1b1baf Merged in release/2023-01-27 (pull request #659)
Release/2023 01 27
2023-01-27 17:02:10 +00:00
Patrick Fic
380dbd8b96 IO-2150 Additional report. 2023-01-27 09:00:44 -08:00
Patrick Fic
a34c2a5bc2 IO-2155 IO-2149 Add report templates. 2023-01-27 08:54:55 -08:00
Patrick Fic
d8ba40979e IO-2154 Add expected jobsin production count. 2023-01-27 08:35:45 -08:00
Patrick Fic
5cd0527e16 Improved global search. 2023-01-27 08:00:54 -08:00
Patrick Fic
25513ae5b5 IO-2151 Resolve issue removing CSR on conversion. 2023-01-26 11:24:53 -08:00
Patrick Fic
d89acbd49d Remove log statement. 2023-01-26 11:12:21 -08:00
Patrick Fic
a54862a309 Add cost center to employee time tickets summary. 2023-01-26 11:07:08 -08:00
Patrick Fic
423157dfcc Add misc labor cost for autohouse. 2023-01-26 08:32:43 -08:00
Patrick Fic
6607c80aca IO-2146 Remove Sublet from jobline status db view. 2023-01-24 09:31:25 -08:00
Patrick Fic
162aeca7c8 Merged in release/2022-01-20 (pull request #657)
Release/2022 01 20
2023-01-20 21:49:59 +00:00
Patrick Fic
1583ed2d61 IO-2143 Add truncation for long vehicle notes. 2023-01-20 13:46:20 -08:00
Patrick Fic
c78b13baa3 Update label for consistency. 2023-01-20 13:46:09 -08:00
Patrick Fic
fc404b1f3b Merged in release/2022-01-20 (pull request #655)
Release/2022 01 20
2023-01-19 21:11:58 +00:00
Patrick Fic
6528a0c700 Update missed in last commit. 2023-01-19 13:06:43 -08:00
Patrick Fic
79f032ecaf Profile page improvements. 2023-01-19 13:05:00 -08:00
Patrick Fic
42b4534d21 IO-2141 Add detail only line for autohous exports. 2023-01-19 11:04:40 -08:00
Patrick Fic
32de0ddeb6 Merged in release/2022-01-13 (pull request #654)
Remove sentry tracing.
2023-01-09 17:46:42 +00:00
Patrick Fic
c6ba3fd8f0 Merged in release/2022-01-13 (pull request #653)
Release/2022 01 13
2023-01-09 17:36:25 +00:00
Patrick Fic
76025b5db1 Remove sentry tracing. 2023-01-09 09:36:00 -08:00
Patrick Fic
ea27fcd476 Merged in release/2023-01-06 (pull request #652)
Release/2023 01 06
2023-01-06 19:26:18 +00:00
Patrick Fic
968816b4a6 IO-2134 Add option to choose CSR on job conversion. 2023-01-06 10:12:07 -08:00
Patrick Fic
9de076f060 IO-2132 Updated approach to ATS summary. 2023-01-06 09:56:23 -08:00
Patrick Fic
08d334e93a Resolve rounding for QBO Receivables Multi Payer. 2023-01-05 17:06:47 -08:00
Patrick Fic
119904ca2b Merged in release/2023-01-06 (pull request #651)
Updates to Sentry & removal of Stripe.
2023-01-04 16:33:10 +00:00
Patrick Fic
af009a0bb3 Updates to Sentry & removal of Stripe. 2023-01-03 12:23:25 -08:00
Patrick Fic
f7e1b023df Merged in release/2023-01-06 (pull request #650)
Release/2023 01 06
2023-01-02 22:56:05 +00:00
Patrick Fic
f8e74d9bad IO-2089 Incorporate Lbr Adjustments into scoreboard. 2023-01-02 14:48:10 -08:00
Patrick Fic
f6bf1ce793 IO-2109 Resolve label printing validation issue. 2023-01-02 14:15:59 -08:00
Patrick Fic
9413bc60cf IO-2134 Add CSR to conversion. 2023-01-02 13:56:35 -08:00
Patrick Fic
34f5fad365 IO-2132 Add Weekly ATS Summary 2023-01-02 11:46:23 -08:00
Patrick Fic
d69ce2d2a9 Merged in release/2022-12-30 (pull request #649)
Release/2022 12 30
2022-12-30 19:30:25 +00:00
Patrick Fic
5855569194 Merged in release/2022-12-30 (pull request #648)
IO-2133 Manual Line Highlighting
2022-12-28 18:05:20 +00:00
Patrick Fic
b0755a0cde IO-2133 Manual Line Highlighting 2022-12-27 13:32:24 -08:00
Patrick Fic
4f852e7493 Merged in release/2022-12-30 (pull request #647)
release/2022-12-30

Approved-by: Patrick Fic
2022-12-26 18:34:28 +00:00
Patrick Fic
a551258895 IO-2129 Add reports to report center. 2022-12-26 10:33:47 -08:00
Patrick Fic
deec40a89c Merged in release/2022-12-16 (pull request #642)
Remove unneeded import to fix CI.
2022-12-16 01:09:42 +00:00
Patrick Fic
145dd9bec6 Merged in release/2022-12-16 (pull request #641)
IO-2131 Resolve job search select filtering issue.
2022-12-16 01:02:45 +00:00
Patrick Fic
b8e5d4412f Merged in release/2022-12-16 (pull request #640)
Release/2022 12 16
2022-12-15 18:20:28 +00:00
Patrick Fic
277fb8f839 Merged in release/2022-12-09 (pull request #637)
IO-2123 Add cc inventory print.
2022-12-09 19:35:49 +00:00
Patrick Fic
d3d5485846 Merged in release/2022-12-09 (pull request #636)
Release/2022 12 09
2022-12-09 16:19:58 +00:00
Patrick Fic
55091d61d6 Merged in release/2022-12-02 (pull request #634)
Release/2022 12 02
2022-11-30 18:26:32 +00:00
Patrick Fic
5b5df8a3a1 Merged in release/2022-11-25 (pull request #630)
Release/2022 11 25
2022-11-25 16:42:19 +00:00
Patrick Fic
ccf48cfcf1 Merged in release/2022-11-10 (pull request #624)
release/2022-11-10

Approved-by: Patrick Fic
2022-11-10 18:47:48 +00:00
Patrick Fic
c89342b6ef Merge branch 'release/2022-11-10' into test
* release/2022-11-10:
  CI Test
2022-11-09 16:35:48 -08:00
Patrick Fic
e97ceb7cbe Merged in release/2022-11-10 (pull request #623)
CI Test
2022-11-10 00:30:20 +00:00
Patrick Fic
de34cbd937 Merged in release/2022-11-10 (pull request #622)
Release/2022 11 10
2022-11-10 00:23:10 +00:00
Patrick Fic
cf7a1b0168 Merged in release/2022-11-10 (pull request #621)
Add .circleci/config.yml
2022-11-08 19:10:04 +00:00
377 changed files with 53149 additions and 55517 deletions

4
.gitignore vendored
View File

@@ -117,4 +117,6 @@ logs/oAuthClient-log.log
.node-persist/**
/*.env.*
/*.env.*
client/cypress/e2e/[1,2]-*

View File

@@ -1,14 +1,3 @@
Yarn Dependency Management:
To force upgrades for some packages:
yarn upgrade-interactive --latest
To Start Hasura CLI:
npx hasura console
Migrating to Staging:
npx hasura migrate apply --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
npx hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret 'Test-ImEXOnlineBySnaptSoftware!'
NGROK TEsting:
./ngrok.exe http http://localhost:4000 -host-header="localhost:4000"
@@ -21,4 +10,4 @@ hasura migrate apply --version "1620771761757" --skip-execution --endpoint https
hasura migrate status --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
Generate the license file:
$ generate-license-file --input package.json --output third-party-licenses.txt --overwrite
$ generate-license-file --input package.json --output third-party-licenses.txt --overwrite

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,6 @@ REACT_APP_CLOUDINARY_API_KEY=957865933348715
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BG3tzU7L2BXlGZ_3VLK4PNaRceoEXEnmHfxcVbRMF5o5g05ejslhVPki9kBM9cBBT-08Ad9kN3HSpS6JmrWD6h4'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
REACT_APP_AXIOS_BASE_API_URL=http://localhost:4000
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc

View File

@@ -1,3 +1,4 @@
GENERATE_SOURCEMAP=false
REACT_APP_GRAPHQL_ENDPOINT=https://db.imex.online/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.imex.online/v1/graphql
REACT_APP_GA_CODE=231103507

View File

@@ -1,9 +1,17 @@
const { defineConfig } = require("cypress");
module.exports = defineConfig({
experimentalStudio: true,
env: {
FIREBASE_USERNAME: "cypress@imex.test",
FIREBASE_PASSWORD: "cypress",
},
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
baseUrl: "http://localhost:3000",
},
});

4
client/cypress.env.json Normal file
View File

@@ -0,0 +1,4 @@
{
"graphql_dev_endpoint": "https://db.dev.bodyshop.app/v1/graphql",
"uploaded_by_email": "john@imex.dev"
}

View File

@@ -1,18 +0,0 @@
/// <reference types="cypress" />
context("Actions", () => {
beforeEach(() => {});
// https://on.cypress.io/interacting-with-elements
it("Attempt a failed login.", () => {
cy.visit("http://localhost:3000");
cy.get(".ant-btn").contains("Sign In").click();
cy.get("#email").type("fake@email.com");
cy.get("#password").type("fakepassword");
cy.get(".ant-btn").contains("Login").click();
cy.should("have.text", "user");
});
it("Attempt a failed login.", () => {});
});

View File

@@ -0,0 +1,232 @@
import moment from "moment";
import job from "../../fixtures/jobs/job-3.json";
describe(
"Adding job to checklist",
{
defaultCommandTimeout: 10000,
},
() => {
beforeEach(() => {
cy.visit("/manage/jobs");
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "QUERY_BODYSHOP") {
req.alias = "bodyshop";
}
});
cy.get('[data-cy="active-jobs-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("active-jobs-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@active-jobs-table")
.contains(job.clm_no)
.first()
.parent()
.find('[data-cy="active-job-link"]')
.click();
});
it("adds checklists to the job and set the job to production", () => {
const tomorrow = moment(new Date()).format("YYYY-MM-DD");
cy.get('[data-cy="job-actions-button"]').click();
// Go to intake
cy.get('[data-cy="job-intake-button"]').should("not.be.disabled").click();
cy.url().should("include", "/intake");
// Fill out the form
cy.get('[data-cy="checklist-form"]').should("be.visible");
cy.wait("@bodyshop").then(({ response }) => {
const bodyshop = response.body.data.bodyshops[0];
// intakechecklist
const checklists = bodyshop.intakechecklist.form;
checklists.forEach((item, index) => {
if (item.type === "text") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find("input:text")
.type("Random Word");
} else if (item.type === "textarea") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find("textarea")
.type("Random Word");
} else if (item.type === "checkbox") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find("input:checkbox")
.check();
} else if (item.type === "slider") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find(".ant-slider-dot:eq(1)")
.click({ force: true });
} else if (item.type === "rate") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find(".ant-rate > li")
.eq(3)
.find("div[role='radio']")
.click({ force: true });
}
});
});
// Check if `Add Job to Production` is switched to on
cy.get('[data-cy="add-to-production-switch"]').should(
"have.attr",
"aria-checked",
"true"
);
// Select dates for completion and delivery
cy.get("#scheduled_completion").find(".ant-picker-input").first().click();
cy.get(`[title="${tomorrow}"]`).should("be.visible").click();
// Add time selection
cy.get("#scheduled_delivery").find(".ant-picker-input").first().click();
cy.get(`[title="${tomorrow}"]`)
.should("be.visible")
.click({ multiple: true, force: true });
// Add time selection
// Add note
cy.get('[data-cy="checklist-production-note"]').type("automated testing");
// Submit the form
cy.get('[data-cy="checklist-submit-button"]').click();
cy.url().should("include", "/manage/jobs");
cy.contains("In Production");
});
it("adds checklists to the job and remove the job to production", () => {
const tomorrow = moment(new Date()).format("YYYY-MM-DD");
cy.get('[data-cy="job-actions-button"]').click();
// Go to deliver
cy.get('[data-cy="job-deliver"]').should("not.be.disabled").click();
cy.url().should("include", "/deliver");
// Fill out the form
cy.get('[data-cy="checklist-form"]').should("be.visible");
cy.wait("@bodyshop").then(({ response }) => {
const bodyshop = response.body.data.bodyshops[0];
// deliverchecklist
const checklists = bodyshop.deliverchecklist.form;
checklists.forEach((item, index) => {
if (item.type === "text") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find("input:text")
.type("Random Word");
} else if (item.type === "textarea") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find("textarea")
.type("Random Word");
} else if (item.type === "checkbox") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find("input:checkbox")
.check();
} else if (item.type === "slider") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find(".ant-slider-dot:eq(1)")
.click({ force: true });
} else if (item.type === "rate") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find(".ant-rate > li")
.eq(3)
.find("div[role='radio']")
.click({ force: true });
}
});
});
// Select dates for completion and delivery
cy.get("#actual_completion").find(".ant-picker-input").first().click();
cy.get(`[title="${tomorrow}"]`).should("be.visible").click();
cy.get("#actual_delivery").find(".ant-picker-input").first().click();
cy.get(`[title="${tomorrow}"]`)
.should("be.visible")
.click({ multiple: true, force: true });
cy.get('[data-cy="remove-from-production"]').should(
"have.attr",
"aria-checked",
"true"
);
// Submit the form
cy.get('[data-cy="checklist-submit-button"]').click();
// Job checklist completed.
cy.url().should("include", "/manage/jobs");
cy.contains("Delivered");
});
it("renders and check the checklists correctly", () => {
// Click the actions button
cy.get('[data-cy="job-actions-button"]').click();
// Go to checklists
cy.get('[data-cy="job-checklist"]').should("not.be.disabled").click();
cy.wait("@bodyshop").then(({ response }) => {
const bodyshop = response.body.data.bodyshops[0];
// intakechecklist
const intakechecklist = bodyshop.intakechecklist.form;
// deliverchecklist
const deliverchecklist = bodyshop.deliverchecklist.form;
const checklists = [...intakechecklist, ...deliverchecklist];
cy.get('[data-cy="intake-checklist"]')
.should("be.visible")
.find("input")
.should("be.disabled");
cy.get('[data-cy="deliver-checklist"]')
.should("be.visible")
.find("input")
.should("be.disabled");
checklists.forEach((item, index) => {
if (item.type === "text") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find("input:text")
.should("have.value", "Random Word");
} else if (item.type === "textarea") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find("textarea")
.should("have.value", "Random Word");
} else if (item.type === "checkbox") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find("input:checkbox")
.should("be.checked");
} else if (item.type === "slider") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find(".ant-slider-handle")
.should("have.attr", "aria-valuenow", item.max / 2);
} else if (item.type === "rate") {
cy.get('[data-cy="config-form-components"] > div')
.eq(index)
.find(".ant-rate > .ant-rate-star-full")
.should("have.length", 3);
}
});
});
});
}
);

View File

@@ -0,0 +1,149 @@
import job from "../../fixtures/jobs/job-3.json";
const errorMessages = {
class: "Class is required. ",
referral_source: "Referral Source is required. ",
employee_csr: "Customer Service Rep. is required. ",
category: "Category is required. ",
};
describe(
"Converting a job with ",
{
defaultCommandTimeout: 10000,
},
() => {
beforeEach(() => {
cy.visit("/manage/jobs");
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "QUERY_BODYSHOP") {
req.alias = "bodyshop";
}
});
cy.get('[data-cy="active-jobs-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("active-jobs-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@active-jobs-table")
.contains(job.clm_no)
.first()
.parent()
.find('[data-cy="active-job-link"]')
.click();
cy.get('[data-cy="job-convert-button"]').click();
});
it("shows the error messages of required fields", () => {
cy.wait("@bodyshop").then(({ response }) => {
const bodyshop = response.body.data.bodyshops[0];
const data = {
ins_cos: bodyshop.md_ins_cos,
config: {
class: bodyshop.enforce_class,
referral_source: bodyshop.enforce_referral,
employee_csr: bodyshop.enforce_conversion_csr,
category: bodyshop.enforce_conversion_category,
},
};
cy.get('[data-cy="convert-button"]').click();
cy.get("#ins_co_nm_help")
.find(".ant-form-item-explain-error")
.should("have.text", "Insurance Company Name is required. ");
for (const id in data.config) {
if (data.config[id]) {
cy.get(`#${id}_help`)
.find(".ant-form-item-explain-error")
.should("have.text", errorMessages[id]);
}
}
});
});
it("shows error of required fields when insurance company is selected", () => {
cy.wait("@bodyshop").then(({ response }) => {
const bodyshop = response.body.data.bodyshops[0];
const data = {
ins_cos: bodyshop.md_ins_cos,
config: {
class: bodyshop.enforce_class,
referral_source: bodyshop.enforce_referral,
employee_csr: bodyshop.enforce_conversion_csr,
category: bodyshop.enforce_conversion_category,
},
};
cy.get(".ant-select-selection-search").find("#ins_co_nm").click();
cy.get("#ins_co_nm_list")
.next()
.find(".ant-select-item-option-content")
.first()
.click();
cy.get("#ca_gst_registrant").should("have.class", "ant-switch").click();
cy.get("#driveable").should("have.class", "ant-switch").click();
cy.get("#towin").should("have.class", "ant-switch").click();
cy.get('[data-cy="convert-button"]').click();
for (const id in data.config) {
if (data.config[id]) {
cy.get(`#${id}_help`)
.find(".ant-form-item-explain-error")
.should("have.text", errorMessages[id]);
}
}
});
});
it("checks for the job to convert", () => {
cy.wait("@bodyshop").then(({ response }) => {
const bodyshop = response.body.data.bodyshops[0];
const data = {
ins_cos: bodyshop.md_ins_cos,
config: {
class: bodyshop.enforce_class,
referral_source: bodyshop.enforce_referral,
employee_csr: bodyshop.enforce_conversion_csr,
category: bodyshop.enforce_conversion_category,
},
};
cy.get(".ant-select-selection-search").find("#ins_co_nm").click();
cy.get("#ins_co_nm_list")
.next()
.find(".ant-select-item-option-content")
.first()
.click();
for (const id in data.config) {
if (data.config[id]) {
cy.get(".ant-select-selection-search").find(`#${id}`).click();
cy.get(`#${id}_list`)
.next()
.find(".ant-select-item-option-content")
.first()
.click();
}
}
cy.get("#ca_gst_registrant").should("have.class", "ant-switch").click();
cy.get("#driveable").should("have.class", "ant-switch").click();
cy.get("#towin").should("have.class", "ant-switch").click();
// cy.get('[data-cy="convert-button"]').click();
// cy.get(".ant-notification-notice-message").contains("successfully");
});
});
}
);

View File

@@ -0,0 +1,500 @@
import job from "../../fixtures/jobs/job-3.json";
import job2 from "../../fixtures/jobs/job-4.json";
import jobSupplement from "../../fixtures/jobs/job-3-supplement.json";
import jobMetadata from "../../fixtures/jobs/job-3-jobmetadata.json";
import jobSupplementMetadata from "../../fixtures/jobs/job-3-supplment-jobmetadata.json";
import Dinero from "dinero.js";
const createJobEstimate = (job, bodyshopid) => {
return {
owner: {
data: {
shopid: bodyshopid,
...job.owner.data,
},
},
vehicle: {
data: {
shopid: bodyshopid,
...job.vehicle.data,
},
},
shopid: bodyshopid,
...job,
};
};
describe(
"Importing an available job",
{
defaultCommandTimeout: 10000,
requestTimeout: 10000,
},
() => {
// assuming that user is logged in
beforeEach(() => {
cy.visit("/manage/available");
// intercept bodyshop query for id
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "QUERY_BODYSHOP") {
req.alias = "bodyshop";
}
});
cy.wait("@bodyshop").then(({ response }) => {
const id = response.body.data.bodyshops[0].id;
cy.wrap(id).as("bodyshopid");
});
});
it("Enters a job programatically", () => {
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "QUERY_AVAILABLE_JOBS") {
req.alias = "availableJobs";
}
});
cy.wait("@availableJobs").then(({ request }) => {
const token = request.headers.authorization;
cy.get("@bodyshopid").then((bodyshopid) => {
const job_est_data = createJobEstimate(job, bodyshopid);
cy.insertAvailableJob({
bodyshopid,
job,
token,
job_est_data,
});
});
});
});
it("creates a new owner record for the job", () => {
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "QUERY_AVAILABLE_JOBS") {
req.alias = "availableJobs";
}
});
cy.wait("@availableJobs");
cy.get('[data-cy="available-jobs-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.contains(job.clm_no)
.parent()
.as("row");
cy.get("@row")
.find('[data-cy="add-job-as-new-button"]')
.should("be.enabled")
.click();
cy.get('[data-cy="new_owner_checkbox"]').should("be.checked");
cy.get('[data-cy="existing-owners-ok-button"]')
.should("be.enabled")
.click();
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "INSERT_JOB") {
req.alias = "insertJob";
}
});
cy.wait("@insertJob").then(({ response }) => {
const id = response.body.data.insert_jobs.returning[0].id;
cy.get(".ant-notification-notice-message")
.contains("Job created successfully. Click to view.")
.click();
cy.url().should("include", `/manage/jobs/${id}`);
});
cy.get('[data-cy="tab-totals"]').should("be.visible").click();
cy.get('[data-cy="job-totals-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("totals-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@totals-table")
.eq(0)
.find("td:not(.ant-table-selection-column)")
.eq(1)
.invoke("text")
.should(
"be.equal",
Dinero({
amount: jobMetadata.totals.subtotal.amount,
}).toFormat()
);
});
it("imports a supplement for an existing job", () => {
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "QUERY_AVAILABLE_JOBS") {
req.alias = "availableJobs";
}
});
cy.wait("@availableJobs").then(({ request }) => {
const token = request.headers.authorization;
cy.get("@bodyshopid").then((bodyshopid) => {
const job_est_data = createJobEstimate(jobSupplement, bodyshopid);
cy.insertAvailableJob({
bodyshopid,
job: jobSupplement,
token,
job_est_data,
});
});
});
cy.get('[data-cy="refetch-available-jobs-button"]')
.should("not.be.disabled")
.click();
cy.get('[data-cy="available-jobs-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.contains(jobSupplement.clm_no)
.parent()
.as("row");
cy.get('[data-cy="add-job-as-supplement"]').should("be.enabled").click();
cy.get('[data-cy="existing-jobs-table"]')
.find(".ant-table-tbody tr")
.should("not.have.class", "ant-table-placeholder")
.first()
.click();
cy.get('[data-cy="existing-jobs-ok-button"]')
.should("not.be", "disabled")
.click();
cy.get(".ant-notification-notice-message")
.contains("Job supplemented successfully.")
.click();
cy.url().should("include", "/manage/jobs");
cy.get('[data-cy="tab-totals"]').should("be.visible").click();
cy.get('[data-cy="job-totals-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("totals-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@totals-table")
.eq(0)
.find("td:not(.ant-table-selection-column)")
.eq(1)
.invoke("text")
.should(
"be.equal",
Dinero({
amount: jobSupplementMetadata.totals.subtotal.amount,
}).toFormat()
);
});
it("imports a supplement and override estimate header", () => {
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "QUERY_AVAILABLE_JOBS") {
req.alias = "availableJobs";
}
});
cy.wait("@availableJobs").then(({ request }) => {
const token = request.headers.authorization;
cy.get("@bodyshopid").then((bodyshopid) => {
const job_est_data = createJobEstimate(jobSupplement, bodyshopid);
cy.insertAvailableJob({
bodyshopid,
job: jobSupplement,
token,
job_est_data,
});
});
});
cy.get('[data-cy="refetch-available-jobs-button"]')
.should("not.be.disabled")
.click();
cy.get('[data-cy="available-jobs-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.contains(jobSupplement.clm_no)
.parent()
.as("row");
cy.get('[data-cy="add-job-as-supplement"]').should("be.enabled").click();
cy.get('[data-cy="existing-jobs-table"]')
.find(".ant-table-tbody tr")
.should("not.have.class", "ant-table-placeholder")
.first()
.click();
// click override
cy.get('[data-cy="override-header-checkbox"]').check();
cy.get('[data-cy="existing-jobs-ok-button"]')
.should("not.be", "disabled")
.click();
cy.get(".ant-notification-notice-message")
.contains("Job supplemented successfully.")
.click();
cy.url().should("include", "/manage/jobs");
cy.get('[data-cy="tab-totals"]').should("be.visible").click();
cy.get('[data-cy="job-totals-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("totals-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@totals-table")
.eq(0)
.find("td:not(.ant-table-selection-column)")
.eq(1)
.invoke("text")
.should(
"be.equal",
Dinero({
amount: jobSupplementMetadata.totals.subtotal.amount,
}).toFormat()
);
});
it("imports a job with an existing owner", () => {
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "QUERY_AVAILABLE_JOBS") {
req.alias = "availableJobs";
}
});
cy.wait("@availableJobs").then(({ request }) => {
const token = request.headers.authorization;
cy.get("@bodyshopid").then((bodyshopid) => {
const job_est_data = createJobEstimate(job2, bodyshopid);
cy.insertAvailableJob({
bodyshopid,
job: job2,
token,
job_est_data,
});
});
});
cy.get('[data-cy="refetch-available-jobs-button"]')
.should("not.be.disabled")
.click();
cy.get('[data-cy="add-job-as-new-button"]').should("be.enabled").click();
cy.get('[data-cy="existing-owner-table"]', { timeout: 20000 })
.find(".ant-table-tbody tr")
.should("not.have.class", "ant-table-placeholder")
.then(($table) => {
cy.wrap($table).first().click();
cy.get('[data-cy="new_owner_checkbox"]').should("not.be", "checked");
});
cy.get('[data-cy="existing-owners-ok-button"]').click();
cy.get(".ant-notification-notice-message").contains(
"Job created successfully. Click to view."
);
cy.visit("/manage/owners");
// Navigate to owner records
cy.get('[data-cy="owners-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("owners-table")
.should("not.have.class", "ant-table-placeholder");
// Get owner name
cy.get("@owners-table")
.contains(`${job2.owner.data.ownr_fn} ${job2.owner.data.ownr_ln}`)
.click();
// check list if claim number is there
cy.get('[data-cy="owner-jobs-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("owner-jobs-table")
.should("not.have.class", "ant-table-placeholder");
// Get owner name
cy.get("@owner-jobs-table").contains(job2.clm_no).should("exist");
});
}
);
const errorMessages = {
class: "Class is required. ",
referral_source: "Referral Source is required. ",
employee_csr: "Customer Service Rep. is required. ",
category: "Category is required. ",
};
describe(
"Converting an active job",
{
defaultCommandTimeout: 10000,
},
() => {
beforeEach(() => {
cy.visit("/manage/jobs");
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "QUERY_BODYSHOP") {
req.alias = "bodyshop";
}
});
cy.get('[data-cy="active-jobs-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("active-jobs-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@active-jobs-table")
.contains(job.clm_no)
.first()
.parent()
.find('[data-cy="active-job-link"]')
.click();
cy.get('[data-cy="job-convert-button"]').click();
});
it("shows the error messages of required fields", () => {
cy.wait("@bodyshop").then(({ response }) => {
const bodyshop = response.body.data.bodyshops[0];
const data = {
ins_cos: bodyshop.md_ins_cos,
config: {
class: bodyshop.enforce_class,
referral_source: bodyshop.enforce_referral,
employee_csr: bodyshop.enforce_conversion_csr,
category: bodyshop.enforce_conversion_category,
},
};
cy.get('[data-cy="convert-button"]').click();
cy.get("#ins_co_nm_help")
.find(".ant-form-item-explain-error")
.should("have.text", "Insurance Company Name is required. ");
for (const id in data.config) {
if (data.config[id]) {
cy.get(`#${id}_help`)
.find(".ant-form-item-explain-error")
.should("have.text", errorMessages[id]);
}
}
});
});
it("shows error of required fields when insurance company is selected", () => {
cy.wait("@bodyshop").then(({ response }) => {
const bodyshop = response.body.data.bodyshops[0];
const data = {
ins_cos: bodyshop.md_ins_cos,
config: {
class: bodyshop.enforce_class,
referral_source: bodyshop.enforce_referral,
employee_csr: bodyshop.enforce_conversion_csr,
category: bodyshop.enforce_conversion_category,
},
};
cy.get(".ant-select-selection-search").find("#ins_co_nm").click();
cy.get("#ins_co_nm_list")
.next()
.find(".ant-select-item-option-content")
.first()
.click();
cy.get("#ca_gst_registrant").should("have.class", "ant-switch").click();
cy.get("#driveable").should("have.class", "ant-switch").click();
cy.get("#towin").should("have.class", "ant-switch").click();
cy.get('[data-cy="convert-button"]').click();
for (const id in data.config) {
if (data.config[id]) {
cy.get(`#${id}_help`)
.find(".ant-form-item-explain-error")
.should("have.text", errorMessages[id]);
}
}
});
});
it("checks for the job to convert", () => {
cy.wait("@bodyshop").then(({ response }) => {
const bodyshop = response.body.data.bodyshops[0];
const data = {
ins_cos: bodyshop.md_ins_cos,
config: {
class: bodyshop.enforce_class,
referral_source: bodyshop.enforce_referral,
employee_csr: bodyshop.enforce_conversion_csr,
category: bodyshop.enforce_conversion_category,
},
};
cy.get(".ant-select-selection-search").find("#ins_co_nm").click();
cy.get("#ins_co_nm_list")
.next()
.find(".ant-select-item-option-content")
.first()
.click();
for (const id in data.config) {
if (data.config[id]) {
cy.get(".ant-select-selection-search").find(`#${id}`).click();
cy.get(`#${id}_list`)
.next()
.find(".ant-select-item-option-content")
.first()
.click();
}
}
cy.get("#ca_gst_registrant").should("have.class", "ant-switch").click();
cy.get("#driveable").should("have.class", "ant-switch").click();
cy.get("#towin").should("have.class", "ant-switch").click();
cy.get('[data-cy="convert-button"]').click();
cy.get(".ant-notification-notice-message").contains(
"Job converted successfully."
);
});
});
}
);

View File

@@ -0,0 +1,32 @@
describe("logging in to the application", () => {
// FIXME error message
it("logs in the using wrong credentials", () => {
cy.login("fakeusername", "veryverylongpassword_123@#");
cy.contains("invalid-email");
});
it("logs in the using wrong password", () => {
cy.login("john@imex.dev", "veryverylongpassword_123@#");
cy.contains(
"The email and password combination you provided is incorrect."
);
});
it("logs in a non-existent credentials", () => {
cy.login("franz@imex.dev", "veryverylongpassword_123@#");
cy.contains("A user with this email does not exist.");
});
// TODO create disabled account
// it("logs in with a disabled account", () => {
// cy.login("disabled_account@imex.dev", "john123");
// cy.contains("User account disabled.");
// });
// TODO log in to the application
// it("logs in the using the right credentials", () => {
// cy.login("john@imex.dev", "john123");
// cy.url().should('include', '/manage')
// });
});

View File

@@ -0,0 +1,17 @@
describe("resetting user password", () => {
it("resets forgotten password with an invalid email", () => {
cy.passwordReset("franz");
cy.contains("Email is not a valid email");
});
// FIXME error message
it("resets forgotten password with a user that does not exist", () => {
cy.passwordReset("franz@imex.dev");
cy.contains("user-not-found");
});
it("resets forgotten password using the right credentials", () => {
cy.passwordReset("john@imex.dev");
cy.contains("A password reset link has been sent to you.");
});
});

View File

@@ -0,0 +1,131 @@
import job from "../../fixtures/jobs/job-3.json";
import job2 from "../../fixtures/jobs/job-4.json";
import moment from "moment";
describe(
"Ordering parts for the job",
{
defaultCommandTimeout: 10000,
},
() => {
beforeEach(() => {
cy.visit("/manage/jobs");
cy.get('[data-cy="active-jobs-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("active-jobs-table")
.should("not.have.class", "ant-table-placeholder");
});
it("order parts for the job", () => {
const today = moment(new Date()).format("YYYY-MM-DD");
cy.get("@active-jobs-table")
.contains(job.clm_no)
.first()
.parent()
.find('[data-cy="active-job-link"]')
.click();
// Go to repair data tab
cy.get('[data-cy="tab-repairdata"]').should("be.visible").click();
// Click on filter parts only
cy.get('[data-cy="filter-parts-button"]')
.should("not.be.disabled")
.click();
// Select multiple rows
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find(".ant-checkbox-input")
.click();
// Click Order Parts
cy.get('[data-cy="order-parts-button"]')
.should("not.be.disabled")
.click();
// Modal should be visible
cy.get('[data-cy="parts-order-modal"]').should("be.visible");
// Fill required fields
cy.get(".ant-select-selection-search").find(`#vendorid`).click();
cy.get(`#vendorid_list`)
.next()
.find(".ant-select-item-option-content")
.first()
.click({ force: true });
cy.get("#deliver_by").click();
cy.get(`[title="${today}"]`).should("be.visible").click({ force: true });
cy.get('[data-cy="part-order-select-none"]').check();
cy.get('[data-cy="order-part-submit"]').should("not.be.disabled").click();
cy.get(".ant-notification-notice-message").contains(
"Parts order created successfully."
);
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find(".ant-table-cell")
.eq(15)
.contains("Ordered");
});
it.only("order multiple parts for the job", () => {
const today = moment(new Date()).format("YYYY-MM-DD");
cy.get("@active-jobs-table")
.contains(job2.clm_no)
.first()
.parent()
.find('[data-cy="active-job-link"]')
.click();
// Go to repair data tab
cy.get('[data-cy="tab-repairdata"]').should("be.visible").click();
// Click on filter parts only
cy.get('[data-cy="filter-parts-button"]')
.should("not.be.disabled")
.click();
// Select multiple rows
cy.get('[data-cy="repair-data-table"]')
.find("input:checkbox")
.first()
.click();
// Click Order Parts
cy.get('[data-cy="order-parts-button"]')
.should("not.be.disabled")
.click();
// Modal should be visible
cy.get('[data-cy="parts-order-modal"]').should("be.visible");
// Fill required fields
cy.get(".ant-select-selection-search").find(`#vendorid`).click();
cy.get(`#vendorid_list`)
.next()
.find(".ant-select-item-option-content")
.first()
.click({ force: true });
cy.get("#deliver_by").click();
cy.get(`[title="${today}"]`).should("be.visible").click({ force: true });
cy.get('[data-cy="part-order-comments"]').type("testing from cypress");
cy.get('[data-cy="part-order-select-none"]').check();
cy.get('[data-cy="order-part-submit"]').should("not.be.disabled").click();
cy.get(".ant-notification-notice-message").contains(
"Parts order created successfully."
);
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find(".ant-table-cell")
.eq(15)
.contains("Ordered");
});
}
);

View File

@@ -0,0 +1,89 @@
describe(
"Entering payment for the job",
{
defaultCommandTimeout: 5000,
},
() => {
beforeEach(() => {
cy.visit("/manage");
cy.get("body").then(($body) => {
if ($body.text().includes("Login")) {
// Log in
cy.get('[data-cy="username"]').type("john@imex.dev");
cy.get('[data-cy="password"]').type("john123");
cy.get('[data-cy="sign-in-button"]').click();
}
cy.get(".ant-table-tbody")
.should("be.visible")
.find("tr")
.should("not.have.class", "ant-table-placeholder");
cy.get(".ant-table-row")
.not(':contains("Open")')
.first()
.find("a")
.first()
.click();
cy.url().should("include", "/manage/jobs");
// Go to totals data tab
cy.get('[data-cy="tab-totals"]').should("be.visible").click();
});
});
it("enters a payment manually", () => {
cy.get('[data-cy="job-payment-button"]').should("be.visible").click();
// fill out form
cy.get('[data-cy="payment-amount"]').type(100);
cy.get('[data-cy="payment-transactionid"]').type("QBD-P-03");
cy.get('[data-cy="payment-memo"]').type("e2e testing");
cy.get('[data-cy="payment-date"]').click();
cy.get('[title="2023-07-03"]').should("be.visible").click();
cy.antdSelect("payer");
cy.antdSelect("type");
cy.get('[data-cy="payment-form-save"]').click();
});
// TODO Add payment using intellipay
it("marks payment as exported", () => {
cy.get('[data-cy="payments-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("payments-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@payments-table")
.first()
.find('[data-cy="edit-payment-button"]')
.click();
cy.get('[data-cy="payment-markexported"]')
.should("not.be.disabled")
.click();
});
it("marks payment for re-export", () => {
cy.get('[data-cy="payments-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("payments-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@payments-table")
.last()
.find('[data-cy="edit-payment-button"]')
.click();
cy.get('[data-cy="payment-markforreexport"]')
.should("not.be.disabled")
.click();
});
}
);

View File

@@ -0,0 +1,431 @@
import job from "../../fixtures/jobs/job-3.json";
import moment from "moment";
const uuid = () => Cypress._.random(0, 1e6);
describe(
"Billing job parts orders",
{
defaultCommandTimeout: 10000,
},
() => {
const today = moment(new Date()).format("YYYY-MM-DD");
beforeEach(() => {
cy.viewport(1280, 720);
cy.visit("/manage/jobs");
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "SEARCH_VENDOR_AUTOCOMPLETE") {
req.alias = "vendors";
}
});
cy.get('[data-cy="active-jobs-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("active-jobs-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@active-jobs-table")
.contains(job.clm_no)
.first()
.parent()
.find('[data-cy="active-job-link"]')
.click();
});
it("receives a part bill", () => {
// Order a part
// Go to repair data tab
cy.get('[data-cy="tab-repairdata"]').should("be.visible").click();
// Click on filter parts only
cy.get('[data-cy="filter-parts-button"]')
.should("not.be.disabled")
.click();
// Select multiple rows
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find(".ant-checkbox-input")
.click();
// Click Order Parts
cy.get('[data-cy="order-parts-button"]')
.should("not.be.disabled")
.click();
// Modal should be visible
cy.get('[data-cy="parts-order-modal"]').should("be.visible");
// Fill required fields
cy.get(".ant-select-selection-search").find(`#vendorid`).click();
cy.get(`#vendorid_list`)
.next()
.find(".ant-select-item-option-content")
.first()
.click({ force: true });
cy.get("#deliver_by").click();
cy.get(`[title="${today}"]`).should("be.visible").click({ force: true });
cy.get('[data-cy="part-order-comments"]').type("testing from cypress");
cy.get('[data-cy="part-order-select-none"]').check();
cy.get('[data-cy="order-part-submit"]').should("not.be.disabled").click();
cy.get(".ant-notification-notice-message").contains(
"Parts order created successfully."
);
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find(".ant-table-cell")
.eq(15)
.contains("Ordered");
cy.get('[data-cy="tab-partssublet"]').should("be.visible").click();
// Find the first row in the parts order
cy.get('[data-cy="part-orders-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("orders-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@orders-table")
.first()
.should("be.visible")
.find('[data-cy="receive-bill-button"]')
.click();
// fill out form
cy.get('[data-cy="bill-form-invoice"]').type(uuid());
cy.get("#bill-form-date").click();
cy.get(`[title="${today}"]`).should("be.visible").click({ force: true });
cy.get('[data-cy="bill-form-parts-bin"]').find("input").click();
cy.get("#location_list")
.next()
.find(".ant-select-item-option-content")
.first()
.click();
cy.get('[data-cy="bill-line-table"]').each(($row) => {
// get retail amount
cy.wrap($row)
.find('[data-cy="bill-line-actual-price"]')
.click({ force: true, multiple: true });
cy.wrap($row)
.find('[data-cy="bill-line-actual-cost"]')
.click({ multiple: true });
});
cy.get('[data-cy="bill-line-actual-cost"]').then((cells) => {
const totals = cells.toArray().map((el) => Number(el.value));
const sum = Cypress._.sum(totals);
cy.get('[data-cy="bill-form-bill-total"]').type(sum);
});
// Click save
cy.get('[data-cy="bill-form-save-button"]').click();
cy.get(".ant-notification-notice-message").contains(
"Invoice added successfully."
);
cy.get('[data-cy="tab-repairdata"]').should("be.visible").click();
cy.get('[data-cy="filter-parts-button"]')
.should("not.be.disabled")
.click();
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find(".ant-table-cell")
.eq(15)
.contains("Received");
});
it("backorders part from order", () => {
cy.get('[data-cy="tab-partssublet"]').should("be.visible").click();
cy.get('[data-cy="part-orders-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("orders-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@orders-table")
.first()
.should("be.visible")
.find('[data-cy="view-part-order-button"]')
.click();
cy.get('[data-cy="mark-backorder-button"]').click();
cy.get(".backorder-date").click();
cy.get(`[title="${today}"]`).should("be.visible").click({ force: true });
cy.get('[data-cy="mark-for-backorder-button"]').click();
cy.get(".ant-drawer-close").click();
cy.get('[data-cy="tab-repairdata"]').should("be.visible").click();
cy.get('[data-cy="filter-parts-button"]')
.should("not.be.disabled")
.click();
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find(".ant-table-cell")
.eq(15)
.contains("Backordered");
});
it("order parts inhouse", () => {
cy.get('[data-cy="tab-repairdata"]').should("be.visible").click();
// Click on filter parts only
cy.get('[data-cy="filter-parts-button"]')
.should("not.be.disabled")
.click();
// Select multiple rows
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find(".ant-checkbox-input")
.click();
// Click Order Parts
cy.get('[data-cy="order-parts-inhouse-button"]')
.should("not.be.disabled")
.click();
cy.antdSelect("bill-vendor");
cy.antdSelect("bill-cost-center");
cy.get('[data-cy="bill-form-save-button"]').click({ force: true });
cy.get(".ant-notification-notice-message").contains(
"Invoice added successfully."
);
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find(".ant-table-cell")
.eq(15)
.contains("Received");
});
it("check inhouse bill to have extra actions", () => {
cy.get('[data-cy="tab-partssublet"]').should("be.visible").click();
cy.get('[data-cy="bills-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("bills-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@bills-table")
.contains("$0.00")
.parent()
.parent()
.first()
.find('[data-cy="print-wrapper"]')
.should("exist");
});
it("posts bill directly", () => {
cy.get('[data-cy="tab-partssublet"]').should("be.visible").click();
cy.get('[data-cy="bills-post-button"]').should("be.visible").click();
// Add New Line
cy.get('[data-cy="bill-line-add-button"]')
.should("not.be.disabled")
.click();
// Select Vendor
cy.antdSelect("bill-vendor");
// Select Line
cy.antdSelect("bill-line");
cy.get('[data-cy="bill-line-line-desc"]').type("Line Description");
// Fill the Form
cy.get('[data-cy="bill-form-invoice"]').type(uuid());
cy.get("#bill-form-date").click();
cy.get(`[title="${today}"]`).should("be.visible").click({ force: true });
cy.get('[data-cy="bill-form-parts-bin"]').find("input").click();
cy.get("#location_list")
.next()
.find(".ant-select-item-option-content")
.first()
.click();
cy.get('[data-cy="bill-line-table"]').each(($row) => {
// get retail amount
cy.wrap($row)
.find('[data-cy="bill-line-actual-price"]')
.click({ force: true, multiple: true });
cy.wrap($row)
.find('[data-cy="bill-line-actual-cost"]')
.click({ multiple: true });
});
cy.get('[data-cy="bill-line-actual-cost"]').then((cells) => {
const totals = cells.toArray().map((el) => Number(el.value));
const sum = Cypress._.sum(totals);
cy.get('[data-cy="bill-form-bill-total"]').type(sum);
});
cy.antdSelect("bill-cost-center");
// Click save
cy.get('[data-cy="bill-form-save-button"]').click();
cy.get(".ant-notification-notice-message").contains(
"Invoice added successfully."
);
});
it("posts a bill with save and new", () => {
cy.get('[data-cy="tab-partssublet"]').should("be.visible").click();
cy.get('[data-cy="bills-post-button"]').should("be.visible").click();
// Add New Line
cy.get('[data-cy="bill-line-add-button"]')
.should("not.be.disabled")
.click();
// Select Vendor
cy.antdSelect("bill-vendor");
// Select Line
cy.antdSelect("bill-line", "-- Not On Estimate --");
// Fill the Form
cy.get('[data-cy="bill-form-invoice"]').type(uuid());
cy.get("#bill-form-date").click();
cy.get(`[title="${today}"]`).should("be.visible").click({ force: true });
cy.get('[data-cy="bill-form-parts-bin"]').find("input").click();
cy.get("#location_list")
.next()
.find(".ant-select-item-option-content")
.first()
.click();
cy.get('[data-cy="bill-line-table"]').each(($row) => {
// get retail amount
cy.wrap($row)
.find('[data-cy="bill-line-actual-price"]')
.click({ force: true, multiple: true });
cy.wrap($row)
.find('[data-cy="bill-line-actual-cost"]')
.click({ multiple: true });
});
cy.get('[data-cy="bill-line-actual-cost"]').then((cells) => {
const totals = cells.toArray().map((el) => Number(el.value));
const sum = Cypress._.sum(totals);
cy.get('[data-cy="bill-form-bill-total"]').type(sum);
});
cy.antdSelect("bill-cost-center");
// Click save
cy.get('[data-cy="bill-form-savenew-button"]')
.should("not.be.disabled")
.click();
cy.get(".ant-notification-notice-message").contains(
"Invoice added successfully."
);
});
it("uploads a document to a bill", () => {
cy.get('[data-cy="tab-partssublet"]').should("be.visible").click();
cy.get('[data-cy="bills-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("bills-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@bills-table")
.first()
.should("be.visible")
.find('[data-cy="edit-bill-button"]')
.click();
cy.location("search").should("include", "billid");
cy.get('[data-cy="bill-edit-form"]')
.find(".ant-upload #bill-document-upload")
.selectFile("job.json", { force: true });
});
it("marks bill as exported", () => {
cy.get('[data-cy="tab-partssublet"]').should("be.visible").click();
cy.get('[data-cy="bills-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("bills-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@bills-table")
.find('[data-cy="bill-exported-checkbox"]')
.not(":checked")
.first()
.as("export-status")
.parent()
.parent()
.parent()
.parent()
.find('[data-cy="edit-bill-button"]')
.click();
cy.location("search").should("include", "billid");
cy.get('[data-cy="bill-mark-export-button"]')
.as("mark-for-export")
.click();
cy.get("@mark-for-export").should("be.disabled");
});
it("marks bill for re-export", () => {
cy.get('[data-cy="tab-partssublet"]').should("be.visible").click();
cy.get('[data-cy="bills-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("bills-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@bills-table")
.find('[data-cy="bill-exported-checkbox"]')
.filter(":checked")
.first()
.as("export-status")
.parent()
.parent()
.parent()
.parent()
.find('[data-cy="edit-bill-button"]')
.click();
cy.location("search").should("include", "billid");
cy.get('[data-cy="bill-mark-reexport-button"]')
.as("mark-for-reexport")
.click();
cy.get("@mark-for-reexport").should("be.disabled");
});
}
);

View File

@@ -0,0 +1,487 @@
import job2 from "../../fixtures/jobs/job-4.json";
import moment from "moment";
import Dinero from "dinero.js";
const uuid = () => Cypress._.random(0, 1e6);
describe(
"Validating and calculating bills",
{
defaultCommandTimeout: 10000,
},
() => {
const today = moment(new Date()).format("YYYY-MM-DD");
const jobLines = job2.joblines.data.filter(
(line) => line.part_type === "PAS" || line.part_type === "PAE"
);
const linesTotal = jobLines.reduce(
(prev, line) => prev + line.act_price,
0
);
beforeEach(() => {
cy.viewport(1280, 720);
cy.visit("/manage/jobs");
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "SEARCH_VENDOR_AUTOCOMPLETE") {
req.alias = "vendors";
}
});
cy.get('[data-cy="active-jobs-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("active-jobs-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@active-jobs-table")
.contains(job2.clm_no)
.first()
.parent()
.find('[data-cy="active-job-link"]')
.click();
// Go to repair data tab
cy.get('[data-cy="tab-partssublet"]').should("be.visible").click();
});
it("validates auto reconciliation through posting bill", () => {
cy.get('[data-cy="tab-repairdata"]').should("be.visible").click();
// Click on filter parts only
cy.get('[data-cy="filter-parts-button"]')
.should("not.be.disabled")
.click();
// Select multiple rows
cy.get('[data-cy="repair-data-table"]')
.find(".ant-checkbox-input")
.first()
.click();
// Click Order Parts
cy.get('[data-cy="order-parts-button"]')
.should("not.be.disabled")
.click();
// Modal should be visible
cy.get('[data-cy="parts-order-modal"]').should("be.visible");
// Fill required fields
cy.get(".ant-select-selection-search").find(`#vendorid`).click();
cy.get(`#vendorid_list`)
.next()
.find(".ant-select-item-option-content")
.first()
.click({ force: true });
cy.get("#deliver_by").click();
cy.get(`[title="${today}"]`).should("be.visible").click({ force: true });
cy.get('[data-cy="part-order-comments"]').type("testing from cypress");
cy.get('[data-cy="part-order-select-none"]').check();
cy.get('[data-cy="order-part-submit"]').should("not.be.disabled").click();
cy.get(".ant-notification-notice-message").contains(
"Parts order created successfully."
);
cy.get('[data-cy="tab-partssublet"]').should("be.visible").click();
// Find the first row in the parts order
cy.get('[data-cy="part-orders-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("orders-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@orders-table")
.first()
.should("be.visible")
.find('[data-cy="receive-bill-button"]')
.click();
// fill out form
cy.get('[data-cy="bill-form-invoice"]').type(uuid());
cy.get("#bill-form-date").click();
cy.get(`[title="${today}"]`).should("be.visible").click({ force: true });
cy.get('[data-cy="bill-form-parts-bin"]').find("input").click();
cy.get("#location_list")
.next()
.find(".ant-select-item-option-content")
.first()
.click();
cy.get('[data-cy="bill-line-table"]').each(($row) => {
// get retail amount
cy.wrap($row)
.find('[data-cy="bill-line-actual-price"]')
.as("retailPrice")
.click({ force: true, multiple: true });
cy.wrap($row)
.find('[data-cy="bill-line-actual-cost"]')
.click({ multiple: true });
});
cy.get('[data-cy="bill-line-actual-cost"]').then((cells) => {
const totals = cells.toArray().map((el) => Number(el.value));
const sum = Cypress._.sum(totals);
cy.get('[data-cy="bill-form-bill-total"]').type(sum);
// Get taxes add it to the sum
cy.get('[data-cy="bill-form-tax"]').then((taxes) => {
const subtotals = taxes
.toArray()
.map((el) => Number(el.innerText.substring(1)));
const totalTax = Cypress._.sum(subtotals);
const billAmount = sum + totalTax;
cy.get('[data-cy="bill-form-bill-total"]')
.find("input")
.clear()
.type(billAmount);
});
});
cy.get("#bill-form-discrepancy").should("have.text", "$0.00");
// Click save
cy.get('[data-cy="bill-form-save-button"]').click();
cy.get("@retailPrice")
.invoke("val")
.then((val) => {
const discrepancy = linesTotal - Number(val);
cy.get("#retailtotal").should("have.text", `$${val}`);
cy.get(".discrepancy").each(($statistic) => {
cy.wrap($statistic).should(
"have.text",
Dinero({
amount: discrepancy,
precision: 0,
}).toFormat()
);
});
});
});
it("returning item and validating statistics", () => {
cy.get('[data-cy="bills-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("bills-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@bills-table")
.find('[data-cy="credit-memo-checkbox"]')
.filter(":not(:checked)")
.first()
.should("not.be.disabled")
.parent()
.parent()
.parent()
.parent()
.find('[data-cy="return-items-button"]')
.click();
cy.get('[data-cy="billline-checkbox"]').check();
cy.get('[data-cy="billline-return-items-ok-button"]').click();
cy.get('[data-cy="billline-actual-price"]')
.find(".ant-form-item-control-input-content")
.then((prices) => {
const totals = prices
.toArray()
.map((el) => Number(el.innerText.substring(1)));
const sum = Cypress._.sum(totals);
const price = Dinero({
amount: sum * 100,
}).toFormat();
cy.get('[data-cy="order-quantity"]').each((input) => {
cy.wrap(input).type("1");
});
cy.get('[data-cy="part-order-select-none"]').click();
cy.get('[data-cy="order-part-submit"]').click();
cy.get("#totalReturns").should("have.text", price);
cy.get("#calculatedcreditsnotreceived").should("have.text", price);
cy.get("#creditsnotreceived").should("have.text", price);
});
});
it("receives credit memo without return part", () => {
// Find the first row in the parts order
cy.get('[data-cy="part-orders-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("orders-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@orders-table")
.find('[data-cy="part-order-return-checkbox"]')
.filter(":checked")
.first()
.parent()
.parent()
.parent()
.parent()
.find('[data-cy="receive-bill-button"]')
.click();
// fill out form
cy.get('[data-cy="bill-form-invoice"]').type(uuid());
cy.get("#bill-form-date").click();
cy.get(`[title="${today}"]`).should("be.visible").click({ force: true });
cy.get('[data-cy="bill-line-table"]').each(($row) => {
// get retail amount
cy.wrap($row)
.find('[data-cy="bill-line-actual-price"]')
.click({ force: true, multiple: true });
cy.wrap($row)
.find('[data-cy="bill-line-actual-cost"]')
.click({ multiple: true });
});
cy.get('[data-cy="is-credit-memo-switch"]').click();
cy.get('[data-cy="is-credit-memo-switch"]').should(
"have.attr",
"aria-checked",
"false"
);
cy.get('[data-cy="bill-line-actual-cost"]').then((cells) => {
cy.get('[data-cy="bill-line-actual-price"]').then((priceCells) => {
const totals = cells.toArray().map((el) => Number(el.value));
const priceTotals = priceCells
.toArray()
.map((el) => Number(el.value));
const sum = Cypress._.sum(totals);
const priceSum = Cypress._.sum(priceTotals);
cy.get('[data-cy="bill-form-bill-total"]').type(sum);
// Get taxes add it to the sum
cy.get('[data-cy="bill-form-tax"]').then((taxes) => {
const subtotals = taxes
.toArray()
.map((el) => Number(el.innerText.substring(1)));
const totalTax = Cypress._.sum(subtotals);
const billAmount = sum + totalTax;
cy.get('[data-cy="bill-form-bill-total"]')
.find("input")
.clear()
.type(billAmount);
});
cy.get("#bill-form-discrepancy").should("have.text", "$0.00");
cy.get(`.ant-select-bill-cost-center > .ant-select-selector`).each(
(select) => {
cy.wrap(select).click();
cy.wrap(select)
.find("input")
.invoke("attr", "id")
.then((id) => {
cy.get(`#${id}_list`)
.next()
.find(".ant-select-item-option-content")
.first()
.click({ force: true });
});
}
);
cy.get('[data-cy="bill-form-save-button"]').click();
cy.get("#totalReturns")
.invoke("text")
.then((value) => {
const totalReturns =
Number(value.substring(1)) - priceSum < 0
? 0
: Number(value.substring(1)) - priceSum;
cy.get("#calculatedcreditsnotreceived").should(
"have.text",
Dinero({
amount: totalReturns,
precision: 0,
}).toFormat()
);
});
});
});
});
it("receives credit memo with return part", () => {
// Find the first row in the parts order
cy.get('[data-cy="part-orders-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("orders-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@orders-table")
.find('[data-cy="part-order-return-checkbox"]')
.filter(":checked")
.first()
.parent()
.parent()
.parent()
.parent()
.find('[data-cy="receive-bill-button"]')
.click();
// fill out form
cy.get('[data-cy="bill-form-invoice"]').type(uuid());
cy.get("#bill-form-date").click();
cy.get(`[title="${today}"]`).should("be.visible").click({ force: true });
cy.get('[data-cy="bill-line-table"]').each(($row) => {
// get retail amount
cy.wrap($row)
.find('[data-cy="bill-line-actual-price"]')
.click({ force: true, multiple: true });
cy.wrap($row)
.find('[data-cy="bill-line-actual-cost"]')
.click({ multiple: true });
});
cy.get('[data-cy="is-credit-memo-switch"]').should(
"have.attr",
"aria-checked",
"true"
);
cy.get('[data-cy="bill-line-actual-cost"]').then((cells) => {
cy.get('[data-cy="bill-line-actual-price"]').then((priceCells) => {
const totals = cells.toArray().map((el) => Number(el.value));
const priceTotals = priceCells
.toArray()
.map((el) => Number(el.value));
const sum = Cypress._.sum(totals);
const priceSum = Cypress._.sum(priceTotals);
cy.get('[data-cy="bill-form-bill-total"]').type(sum);
// Get taxes add it to the sum
cy.get('[data-cy="bill-form-tax"]').then((taxes) => {
const subtotals = taxes
.toArray()
.map((el) => Number(el.innerText.substring(1)));
const totalTax = Cypress._.sum(subtotals);
const billAmount = sum + totalTax;
cy.get('[data-cy="bill-form-bill-total"]')
.find("input")
.clear()
.type(billAmount);
});
cy.get("#bill-form-discrepancy").should("have.text", "$0.00");
cy.get(`.ant-select-bill-cost-center > .ant-select-selector`).each(
(select) => {
cy.wrap(select).click();
cy.wrap(select)
.find("input")
.invoke("attr", "id")
.then((id) => {
cy.get(`#${id}_list`)
.next()
.find(".ant-select-item-option-content")
.first()
.click({ force: true });
});
}
);
cy.get('[data-cy="mark-as-received-checkbox"]').check({
multiple: true,
});
cy.get('[data-cy="bill-form-save-button"]').click();
cy.get("#totalReturns")
.invoke("text")
.then((value) => {
const totalReturns =
Number(value.substring(1)) - priceSum < 0
? 0
: Number(value.substring(1)) - priceSum;
cy.get("#creditsnotreceived").should(
"have.text",
Dinero({
amount: totalReturns,
precision: 0,
}).toFormat()
);
});
});
});
});
it("views the row expander if it has the order and bill", () => {
cy.get('[data-cy="tab-repairdata"]').should("be.visible").click();
cy.get('[data-cy="filter-parts-button"]')
.should("not.be.disabled")
.click();
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find("td")
.first()
.click();
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find("td")
.eq(13)
.find("div")
.should("exist");
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find("td")
.eq(14)
.find("div")
.should("exist");
cy.get('[data-cy="repair-data-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.first()
.find("td")
.eq(15)
.find("div")
.should("have.text", "Returned");
cy.get('[data-cy="parts-bills-order"]')
.should("be.visible")
.find("li")
.first()
.should("not.have.text", "This part has not yet been ordered.");
});
}
);

View File

@@ -0,0 +1,318 @@
import moment from "moment";
import job2 from "../../fixtures/jobs/job-4.json";
import Dinero from "dinero.js";
const uuid = () => Cypress._.random(0, 1e6);
describe(
"Entering payment for the job",
{
defaultCommandTimeout: 5000,
},
() => {
const today = moment().format("YYYY-MM-DD");
const LABOR_HOURS = 1;
const COST_CENTER = "Body";
beforeEach(() => {
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "QUERY_ACTIVE_EMPLOYEES") {
req.alias = "employees";
}
});
cy.intercept("POST", Cypress.env("graphql_dev_endpoint"), (req) => {
if (req.body.operationName === "QUERY_BODYSHOP") {
req.alias = "bodyshop";
}
});
cy.visit("/manage");
cy.get('[data-cy="active-jobs-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("active-jobs-table")
.should("not.have.class", "ant-table-placeholder");
cy.get("@active-jobs-table")
.contains(job2.clm_no)
.first()
.parent()
.find('[data-cy="active-job-link"]')
.click();
cy.url().should("include", "/manage/jobs");
});
it("checks input validations", () => {
cy.get('[data-cy="job-actions-button"]').click();
cy.get('[data-cy="actions-timetickets"]')
.should("be.visible")
.and("not.be.disabled")
.click();
cy.get('[data-cy="timeticket-save-button"]').first().click();
cy.get('[data-cy="form-timeticket"]')
.find(".ant-form-item-explain-error")
.should("have.length", 4);
cy.get('[data-cy="form-timeticket-date"]').click();
cy.get(`[title="${today}"]`).should("be.visible").click({ force: true });
cy.antdSelect("timeticket-employee");
cy.antdSelect("cost-center");
cy.get('[data-cy="labor-allocations-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("labor-allocations")
.should("not.have.class", "ant-table-placeholder");
cy.get("@labor-allocations")
.eq(0)
.find("td:not(.ant-table-selection-column)")
.eq(4)
.find("strong")
.invoke("text")
.as("bodyDiff")
.then((diff) => {
// TODO dynamically select the employee prior to what is the labor and cost
cy.get('[data-cy="form-timeticket-productivehrs"]').type(
Number(diff) + 1
);
cy.get(".ant-form-item-explain-error").should(
"have.text",
"The number of hours entered is more than what is available for this cost center."
);
});
});
it("adds new time ticket to a job with flat rate", () => {
cy.get('[data-cy="job-actions-button"]').click();
cy.get('[data-cy="actions-timetickets"]')
.should("be.visible")
.and("not.be.disabled")
.click();
cy.get('[data-cy="labor-allocations-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("labor-allocations")
.should("not.have.class", "ant-table-placeholder");
// Get Difference for Body
cy.get('[data-cy="form-timeticket-date"]').click();
cy.get(`[title="${today}"]`).should("be.visible").click();
cy.get("@labor-allocations")
.eq(0)
.find("td:not(.ant-table-selection-column)")
.eq(4)
.find("strong")
.invoke("text")
.as("bodyDiff")
.then((diff) => {
cy.get('[data-cy="form-timeticket-productivehrs"]').type(LABOR_HOURS);
cy.get('[data-cy="form-timeticket-actualhrs"]').type(LABOR_HOURS);
});
cy.wait("@employees").then(({ response }) => {
const employees = response.body.data.employees;
const employee = employees.find((e) => e.flat_rate);
const employee_name = `${employee.first_name} ${employee.last_name}`;
cy.antdSelectValue("timeticket-employee", employee_name);
cy.antdSelectValue("cost-center", COST_CENTER);
});
cy.get('[data-cy="form-timeticket-memo"]').type(uuid());
cy.get('[data-cy="timeticket-save-button"]').first().click();
cy.get(".ant-notification-notice-message").contains(
"Time ticket entered successfully."
);
});
it("adds new time ticket to a job with straight rate", () => {
cy.get('[data-cy="job-actions-button"]').click();
cy.get('[data-cy="actions-timetickets"]')
.should("be.visible")
.and("not.be.disabled")
.click();
cy.get('[data-cy="labor-allocations-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("labor-allocations")
.should("not.have.class", "ant-table-placeholder");
// Get Difference for Body
cy.get('[data-cy="form-timeticket-date"]').click();
cy.get(`[title="${today}"]`).should("be.visible").click();
cy.get("@labor-allocations")
.eq(0)
.find("td:not(.ant-table-selection-column)")
.eq(4)
.find("strong")
.invoke("text")
.as("bodyDiff")
.then((diff) => {
cy.get('[data-cy="form-timeticket-productivehrs"]').type(LABOR_HOURS);
cy.get('[data-cy="form-timeticket-actualhrs"]').type(LABOR_HOURS);
});
cy.wait("@employees").then(({ response }) => {
const employees = response.body.data.employees;
const employee = employees.find((e) => !e.flat_rate);
const employee_name = `${employee.first_name} ${employee.last_name}`;
cy.antdSelectValue("timeticket-employee", employee_name);
cy.antdSelectValue("cost-center", COST_CENTER);
});
cy.get('[data-cy="form-timeticket-memo"]').type(uuid());
cy.get('[data-cy="timeticket-save-button"]').first().click();
cy.get(".ant-notification-notice-message").contains(
"Time ticket entered successfully."
);
});
it("checks hours calculated to the allocations table", () => {
cy.get('[data-cy="tab-labor"]').should("be.visible").click();
cy.get('[data-cy="labor-allocations-table"]')
.find(".ant-table-tbody")
.find("> tr:not(.ant-table-measure-row)")
.as("labor-allocations")
.should("not.have.class", "ant-table-placeholder");
cy.get('[data-cy="labor-total-hrs-claimed"]')
.invoke("text")
.then((hours) => {
cy.wrap(hours).should("not.equal", "0");
});
});
it("checks the job costing calculations", () => {
cy.get('[data-cy="job-actions-button"]').click();
cy.get('[data-cy="actions-jobcosting"]')
.should("be.visible")
.and("not.be.disabled")
.click();
cy.wait("@bodyshop").then(({ request }) => {
const token = request.headers.authorization;
const query = `query QUERY_ACTIVE_EMPLOYEES {
employees(where: { active: { _eq: true } }) {
last_name
id
first_name
employee_number
active
termination_date
hire_date
flat_rate
rates
pin
user_email
}
}`;
cy.request({
url: "http://localhost:4000/test/query",
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: token,
},
body: {
query,
},
}).then((response) => {
const cost_center = COST_CENTER;
const employees = response.body.employees;
const total_cost = employees.reduce((prev, employee) => {
const rate =
employee.rates.find((rate) => rate.cost_center === cost_center)
.rate ?? 0;
return prev + rate * LABOR_HOURS;
}, 0);
cy.get('[data-cy="responsibilitycenter"]')
.contains(cost_center)
.parent()
.parent()
.find('[data-cy="cost"]')
.should(
"have.text",
Dinero({
amount: total_cost,
precision: 0,
}).toFormat()
);
});
});
});
it.only("clocks in and out of the tech page for the timeticket", () => {
// TODO go to tech page for the clock in and out
cy.visit("/tech");
cy.get('[data-cy="tech-employee-id"]').type("a");
cy.get('[data-cy="tech-employee-password"]').type("a{enter}");
cy.contains("Logged in as");
// go to clock in
cy.get('[data-cy="sider-joblock"]').click({ force: true });
// find the job ro
cy.get('[data-cy="clock-ro-select"]').type("273");
cy.get('[data-cy="clock-ro-select"] .ant-select-selection-search input')
.invoke("attr", "id")
.then((selElm) => {
const dropDownSelector = `#${selElm}_list`;
cy.get(dropDownSelector)
.next()
.find(".ant-select-item-option-content")
.first()
.click({ force: true });
});
// select cost center
cy.get('[data-cy="clock-cost-center-select"]').click();
cy.get(
'[data-cy="clock-cost-center-select"] .ant-select-selection-search input'
)
.invoke("attr", "id")
.then((selElm) => {
const dropDownSelector = `#${selElm}_list`;
cy.get(dropDownSelector)
.next()
.find(".ant-select-item-option-content")
.contains("Body")
.first()
.click({ force: true });
});
// clock in and out of the job
});
}
);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,316 @@
{
"parts": {
"parts": {
"list": {
"PAE": {
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"PAN": {
"total": {
"amount": 26661,
"currency": "USD",
"precision": 2
}
}
},
"total": {
"amount": 26661,
"currency": "USD",
"precision": 2
},
"subtotal": {
"amount": 26661,
"currency": "USD",
"precision": 2
},
"prt_dsmk_total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"sublets": {
"total": {
"amount": 5000,
"currency": "USD",
"precision": 2
},
"subtotal": {
"amount": 5000,
"currency": "USD",
"precision": 2
}
}
},
"rates": {
"la1": {
"rate": 92.49,
"hours": 3.5,
"total": {
"amount": 32372,
"currency": "USD",
"precision": 2
}
},
"la2": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"la3": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"la4": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"laa": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"lab": {
"rate": 85.16,
"hours": 29.7,
"total": {
"amount": 252925,
"currency": "USD",
"precision": 2
}
},
"lad": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"lae": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"laf": {
"rate": 97.34,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"lag": {
"rate": 85.16,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"lam": {
"rate": 109.5,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"lar": {
"rate": 85.16,
"hours": 8.500000000000002,
"total": {
"amount": 72386,
"currency": "USD",
"precision": 2
}
},
"las": {
"rate": 85.16,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"lau": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"mapa": {
"rate": 55.38,
"hours": 8.500000000000002,
"total": {
"amount": 47073,
"currency": "USD",
"precision": 2
}
},
"mash": {
"rate": 6.85,
"hours": 33.199999999999996,
"total": {
"amount": 22742,
"currency": "USD",
"precision": 2
}
},
"subtotal": {
"amount": 427498,
"currency": "USD",
"precision": 2
},
"rates_subtotal": {
"amount": 357683,
"currency": "USD",
"precision": 2
}
},
"totals": {
"subtotal": {
"amount": 495355,
"currency": "USD",
"precision": 2
},
"local_tax": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"state_tax": {
"amount": 34675,
"currency": "USD",
"precision": 2
},
"custPayable": {
"total": {
"amount": 30000,
"currency": "USD",
"precision": 2
},
"dep_taxes": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"deductible": {
"amount": 30000,
"currency": "USD",
"precision": 2
},
"federal_tax": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"other_customer_amount": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"federal_tax": {
"amount": 24768,
"currency": "USD",
"precision": 2
},
"net_repairs": {
"amount": 524798,
"currency": "USD",
"precision": 2
},
"statePartsTax": {
"amount": 2216,
"currency": "USD",
"precision": 2
},
"total_repairs": {
"amount": 554798,
"currency": "USD",
"precision": 2
}
},
"additional": {
"pvrt": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"total": {
"amount": 36196,
"currency": "USD",
"precision": 2
},
"towing": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"storage": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"shipping": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"adjustments": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"additionalCosts": {
"amount": 36196,
"currency": "USD",
"precision": 2
},
"additionalCostItems": [
{
"key": "ATS Amount",
"total": {
"amount": 36196,
"currency": "USD",
"precision": 2
}
}
]
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,316 @@
{
"parts": {
"parts": {
"list": {
"PAE": {
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"PAN": {
"total": {
"amount": 36661,
"currency": "USD",
"precision": 2
}
}
},
"total": {
"amount": 36661,
"currency": "USD",
"precision": 2
},
"subtotal": {
"amount": 36661,
"currency": "USD",
"precision": 2
},
"prt_dsmk_total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"sublets": {
"total": {
"amount": 5000,
"currency": "USD",
"precision": 2
},
"subtotal": {
"amount": 5000,
"currency": "USD",
"precision": 2
}
}
},
"rates": {
"la1": {
"rate": 92.49,
"hours": 3.5,
"total": {
"amount": 32372,
"currency": "USD",
"precision": 2
}
},
"la2": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"la3": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"la4": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"laa": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"lab": {
"rate": 85.16,
"hours": 29.7,
"total": {
"amount": 252925,
"currency": "USD",
"precision": 2
}
},
"lad": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"lae": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"laf": {
"rate": 97.34,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"lag": {
"rate": 85.16,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"lam": {
"rate": 109.5,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"lar": {
"rate": 85.16,
"hours": 8.500000000000002,
"total": {
"amount": 72386,
"currency": "USD",
"precision": 2
}
},
"las": {
"rate": 85.16,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"lau": {
"rate": 0,
"hours": 0,
"total": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"mapa": {
"rate": 55.38,
"hours": 8.500000000000002,
"total": {
"amount": 47073,
"currency": "USD",
"precision": 2
}
},
"mash": {
"rate": 6.85,
"hours": 33.199999999999996,
"total": {
"amount": 22742,
"currency": "USD",
"precision": 2
}
},
"subtotal": {
"amount": 427498,
"currency": "USD",
"precision": 2
},
"rates_subtotal": {
"amount": 357683,
"currency": "USD",
"precision": 2
}
},
"totals": {
"subtotal": {
"amount": 505355,
"currency": "USD",
"precision": 2
},
"local_tax": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"state_tax": {
"amount": 35375,
"currency": "USD",
"precision": 2
},
"custPayable": {
"total": {
"amount": 30000,
"currency": "USD",
"precision": 2
},
"dep_taxes": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"deductible": {
"amount": 30000,
"currency": "USD",
"precision": 2
},
"federal_tax": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"other_customer_amount": {
"amount": 0,
"currency": "USD",
"precision": 2
}
},
"federal_tax": {
"amount": 25268,
"currency": "USD",
"precision": 2
},
"net_repairs": {
"amount": 535998,
"currency": "USD",
"precision": 2
},
"statePartsTax": {
"amount": 2916,
"currency": "USD",
"precision": 2
},
"total_repairs": {
"amount": 565998,
"currency": "USD",
"precision": 2
}
},
"additional": {
"pvrt": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"total": {
"amount": 36196,
"currency": "USD",
"precision": 2
},
"towing": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"storage": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"shipping": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"adjustments": {
"amount": 0,
"currency": "USD",
"precision": 2
},
"additionalCosts": {
"amount": 36196,
"currency": "USD",
"precision": 2
},
"additionalCostItems": [
{
"key": "ATS Amount",
"total": {
"amount": 36196,
"currency": "USD",
"precision": 2
}
}
]
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -22,4 +22,106 @@
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
Cypress.Commands.add("goToSignInPage", () => {
cy.visit("/");
cy.contains("Sign In").click();
});
Cypress.Commands.add("login", (username, password) => {
cy.goToSignInPage();
cy.get('[data-cy="username"]').type(username);
cy.get('[data-cy="password"]').type(password);
cy.get('[data-cy="sign-in-button"]', { timeout: 2000 }).click();
});
Cypress.Commands.add("passwordReset", (email) => {
cy.goToSignInPage();
cy.get('[data-cy="reset-password"]').click();
cy.get('[data-cy="reset-password-email-input"]').type(email);
cy.get('[data-cy="reset-password-button"]').click();
});
Cypress.on("uncaught:exception", (err, runnable) => {
// returning false here prevents Cypress from
// failing the test
return false;
});
Cypress.Commands.add("antdSelect", (selector, filter) => {
cy.get(`.ant-select-${selector} > .ant-select-selector`).click();
cy.get(`.ant-select-${selector} .ant-select-selection-search input`)
.invoke("attr", "id")
.then((selElm) => {
const dropDownSelector = `#${selElm}_list`;
if (filter) {
cy.get(dropDownSelector)
.next()
.find(".ant-select-item-option-content")
.not(`:contains("${filter}")`)
.first()
.click({ force: true });
} else {
cy.get(dropDownSelector)
.next()
.find(".ant-select-item-option-content")
.first()
.click({ force: true });
}
});
});
Cypress.Commands.add("antdSelectValue", (selector, filter) => {
cy.get(`.ant-select-${selector} > .ant-select-selector`).click();
cy.get(`.ant-select-${selector} .ant-select-selection-search input`)
.invoke("attr", "id")
.then((selElm) => {
const dropDownSelector = `#${selElm}_list`;
cy.get(dropDownSelector)
.next()
.find(".ant-select-item-option-content")
.contains(filter)
.first()
.click({ force: true });
});
});
Cypress.Commands.add(
"insertAvailableJob",
({ bodyshopid, job, job_est_data, token }) => {
const query = `mutation INSERT_AVAILABLE_JOB($job: available_jobs_insert_input!) {
insert_available_jobs_one(object: $job) {
id
}
}`;
cy.request({
url: "http://localhost:4000/test/query",
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: token,
},
body: {
query,
job: {
est_data: job_est_data,
uploaded_by: Cypress.env("uploaded_by_email"),
cieca_id: job.ciecaid,
bodyshopid,
clm_amt: job.clm_total,
clm_no: job.clm_no,
ins_co_nm: job.ins_co_nm,
ownr_name: `${job.owner.data.ownr_fn} ${job.owner.data.ownr_ln}`,
vehicle_info: `${job.v_model_yr} ${job.v_make_desc} ${job.v_model_desc}`,
},
},
})
.its("body.insert_available_jobs_one")
.should("have.property", "id");
}
);

View File

@@ -14,7 +14,7 @@
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'
import "./commands";
// Alternatively you can use CommonJS syntax:
// require('./commands')
// require('./commands')

5912
client/job.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,71 +4,73 @@
"private": true,
"proxy": "http://localhost:4000",
"dependencies": {
"@apollo/client": "^3.6.9",
"@apollo/client": "^3.7.9",
"@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^6.4.5",
"@craco/craco": "^7.0.0",
"@fingerprintjs/fingerprintjs": "^3.3.3",
"@jsreport/browser-client": "^3.1.0",
"@sentry/react": "^7.7.0",
"@sentry/tracing": "^7.7.0",
"@splitsoftware/splitio-react": "^1.6.0",
"@stripe/react-stripe-js": "^1.9.0",
"@stripe/stripe-js": "^1.32.0",
"@sentry/react": "^7.40.0",
"@sentry/tracing": "^7.40.0",
"@splitsoftware/splitio-react": "^1.8.1",
"@tanem/react-nprogress": "^5.0.8",
"antd": "^4.22.3",
"apollo-link-logger": "^2.0.0",
"axios": "^0.27.2",
"craco-less": "^1.20.0",
"antd": "^4.24.8",
"apollo-link-logger": "^2.0.1",
"axios": "^1.3.4",
"craco-less": "^2.0.0",
"dinero.js": "^1.9.1",
"dotenv": "^16.0.1",
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"firebase": "^9.9.1",
"graphql": "^16.5.0",
"i18next": "^21.8.14",
"i18next-browser-languagedetector": "^6.1.4",
"firebase": "^9.17.1",
"graphql": "^16.6.0",
"i18next": "^22.4.10",
"i18next-browser-languagedetector": "^7.0.1",
"jsoneditor": "^9.9.0",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.10.9",
"libphonenumber-js": "^1.10.21",
"logrocket": "^3.0.1",
"markerjs2": "^2.22.0",
"markerjs2": "^2.28.1",
"moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.34",
"normalize-url": "^7.0.3",
"phone": "^3.1.23",
"moment-timezone": "^0.5.41",
"normalize-url": "^8.0.0",
"phone": "^3.1.35",
"preval.macro": "^5.0.0",
"prop-types": "^15.8.1",
"query-string": "^7.1.1",
"query-string": "^7.1.3",
"rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6",
"react": "^17.0.2",
"react-big-calendar": "^1.5.0",
"react-big-calendar": "^1.6.8",
"react-color": "^2.19.3",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-drag-listview": "^0.2.1",
"react-grid-gallery": "^0.5.5",
"react-grid-gallery": "^1.0.0",
"react-grid-layout": "^1.3.4",
"react-i18next": "^11.18.1",
"react-icons": "^4.4.0",
"react-number-format": "^4.9.3",
"react-redux": "^7.2.8",
"react-i18next": "^12.2.0",
"react-icons": "^4.7.1",
"react-image-lightbox": "^5.1.4",
"react-intersection-observer": "^9.4.3",
"react-number-format": "^5.1.3",
"react-redux": "^8.0.5",
"react-resizable": "^3.0.4",
"react-router-dom": "^5.3.0",
"react-scripts": "^4.0.3",
"react-scripts": "^5.0.1",
"react-sticky": "^6.0.3",
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3",
"recharts": "^2.1.12",
"redux": "^4.2.0",
"recharts": "^2.4.3",
"redux": "^4.2.1",
"redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"redux-saga": "^1.2.2",
"redux-state-sync": "^3.1.4",
"reselect": "^4.1.6",
"sass": "^1.54.0",
"socket.io-client": "^4.5.1",
"styled-components": "^5.3.5",
"reselect": "^4.1.7",
"sass": "^1.58.3",
"socket.io-client": "^4.6.1",
"styled-components": "^5.3.6",
"subscriptions-transport-ws": "^0.11.0",
"uniqid": "^5.4.0",
"web-vitals": "^2.1.4",
"workbox-background-sync": "^6.5.3",
"workbox-broadcast-update": "^6.5.3",
@@ -81,7 +83,8 @@
"workbox-range-requests": "^6.5.3",
"workbox-routing": "^6.5.3",
"workbox-strategies": "^6.5.3",
"workbox-streams": "^6.5.3"
"workbox-streams": "^6.5.3",
"yauzl": "^2.10.0"
},
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
@@ -91,7 +94,8 @@
"build-deploy:test": "yarn run build:test && s3cmd sync build/* s3://imex-online-test && echo '🚀 TESTING Deployed!'",
"buildcra": "REACT_APP_GIT_SHA=`git rev-parse --short HEAD` craco build",
"test": "cypress open",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular ."
},
"eslintConfig": {
"extends": [
@@ -116,8 +120,9 @@
"react-error-overlay": "6.0.9"
},
"devDependencies": {
"@sentry/webpack-plugin": "^1.19.0",
"cypress": "^12.2.0",
"@sentry/webpack-plugin": "^1.20.0",
"@testing-library/cypress": "^8.0.3",
"cypress": "^12.13.0",
"eslint-plugin-cypress": "^2.12.1",
"react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6",

File diff suppressed because one or more lines are too long

View File

@@ -143,8 +143,24 @@
}
}
//Update row highlighting on production board.
//Update row highlighting on production board.
.ant-table-tbody > tr.ant-table-row:hover > td {
background: #eaeaea !important;
}
}
.job-line-manual {
color: tomato;
font-style: italic;
}
td.ant-table-column-sort {
background-color: transparent;
}
.ant-table-tbody > tr.ant-table-row:nth-child(2n) > td {
background-color: #f4f4f4;
}
.rowWithColor > td {
background-color: var(--bgColor) !important;
}

View File

@@ -1,8 +1,4 @@
import {
PaymentRequestButtonElement,
useStripe,
} from "@stripe/react-stripe-js";
import React, { useEffect, useState } from "react";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
@@ -19,49 +15,6 @@ const mapDispatchToProps = (dispatch) => ({
});
function Test({ bodyshop, setEmailOptions }) {
const stripe = useStripe();
const [paymentRequest, setPaymentRequest] = useState(null);
useEffect(() => {
if (stripe) {
const pr = stripe.paymentRequest({
country: "CA",
displayItems: [{ label: "Deductible", amount: 1099 }],
currency: "cad",
total: {
label: "Demo total",
amount: 1099,
},
requestPayerName: true,
requestPayerEmail: true,
});
// Check the availability of the Payment Request API.
pr.canMakePayment().then((result) => {
if (result) {
setPaymentRequest(pr);
} else {
// var details = {
// total: { label: "", amount: { currency: "CAD", value: "0.00" } },
// };
new PaymentRequest(
[{ supportedMethods: ["basic-card"] }],
{}
// details
).show();
}
});
}
}, [stripe]);
if (paymentRequest) {
return (
<div style={{ height: "300px" }}>
<PaymentRequestButtonElement options={{ paymentRequest }} />
</div>
);
}
return (
<div>
<button

View File

@@ -107,11 +107,6 @@ export function AccountingPayablesTableComponent({
dataIndex: "transactionid",
key: "transactionid",
},
{
title: t("payments.fields.stripeid"),
dataIndex: "stripeid",
key: "stripeid",
},
{
title: t("payments.fields.created_at"),
dataIndex: "created_at",

View File

@@ -117,7 +117,7 @@ export default function BillCmdReturnsTableComponent({
name={[field.name, "cm_received"]}
valuePropName="checked"
>
<Checkbox />
<Checkbox data-cy="mark-as-received-checkbox" />
</Form.Item>
</td>
</tr>

View File

@@ -6,7 +6,7 @@ import { useTranslation } from "react-i18next";
import { DELETE_BILL } from "../../graphql/bills.queries";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
export default function BillDeleteButton({ bill }) {
export default function BillDeleteButton({ bill, callback }) {
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const [deleteBill] = useMutation(DELETE_BILL);
@@ -36,6 +36,8 @@ export default function BillDeleteButton({ bill }) {
if (!!!result.errors) {
notification["success"]({ message: t("bills.successes.deleted") });
if (callback && typeof callback === "function") callback(bill.id);
} else {
//Check if it's an fkey violation.
const error = JSON.stringify(result.errors);

View File

@@ -10,7 +10,7 @@ import { createStructuredSelector } from "reselect";
import {
DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES,
UPDATE_BILL_LINE
UPDATE_BILL_LINE,
} from "../../graphql/bill-lines.queries";
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
@@ -194,12 +194,19 @@ export function BillDetailEditcontainer({
{t("general.actions.save")}
</Button>
</Popconfirm>
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
<BillMarkExportedButton bill={data && data.bills_by_pk} />
<BillReeportButtonComponent
data-cy="bill-mark-reexport-button"
bill={data && data.bills_by_pk}
/>
<BillMarkExportedButton
data-cy="bill-mark-export-button"
bill={data && data.bills_by_pk}
/>
</Space>
}
/>
<Form
data-cy="bill-edit-form"
form={form}
onFinish={handleFinish}
initialValues={transformData(data)}

View File

@@ -77,11 +77,14 @@ export function BillDetailEditReturn({
return (
<>
<Modal
visible={visible}
open={visible}
onCancel={() => setVisible(false)}
destroyOnClose
title={t("bills.actions.return")}
onOk={() => form.submit()}
okButtonProps={{
"data-cy": "billline-return-items-ok-button",
}}
>
<Form
initialValues={data && data.bills_by_pk}
@@ -96,6 +99,7 @@ export function BillDetailEditReturn({
<tr>
<td>
<Checkbox
data-cy="billline-checkbox"
onChange={(e) => {
form.setFieldsValue({
billlines: form
@@ -150,6 +154,7 @@ export function BillDetailEditReturn({
// label={t("joblines.fields.actual_price")}
key={`${index}actual_price`}
name={[field.name, "actual_price"]}
data-cy="billline-actual-price"
>
<ReadOnlyFormItemComponent type="currency" />
</Form.Item>
@@ -173,6 +178,7 @@ export function BillDetailEditReturn({
</Form>
</Modal>
<Button
data-cy="return-items-button"
disabled={data.bills_by_pk.is_credit_memo || disabled}
onClick={() => {
setVisible(true);

View File

@@ -362,11 +362,16 @@ function BillEnterModalContainer({
{t("bills.labels.generatepartslabel")}
</Checkbox>
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
<Button loading={loading} onClick={() => form.submit()}>
<Button
data-cy="bill-form-save-button"
loading={loading}
onClick={() => form.submit()}
>
{t("general.actions.save")}
</Button>
{billEnterModal.context && billEnterModal.context.id ? null : (
<Button
data-cy="bill-form-savenew-button"
type="primary"
loading={loading}
onClick={() => {

View File

@@ -177,6 +177,7 @@ export function BillFormComponent({
]}
>
<VendorSearchSelect
className="ant-select-bill-vendor"
disabled={disabled}
options={vendorAutoCompleteOptions}
onSelect={handleVendorSelect}
@@ -249,7 +250,10 @@ export function BillFormComponent({
}),
]}
>
<Input disabled={disabled || disableInvNumber} />
<Input
data-cy="bill-form-invoice"
disabled={disabled || disableInvNumber}
/>
</Form.Item>
<Form.Item
label={t("bills.fields.date")}
@@ -261,7 +265,7 @@ export function BillFormComponent({
},
]}
>
<FormDatePicker disabled={disabled} />
<FormDatePicker id="bill-form-date" disabled={disabled} />
</Form.Item>
<Form.Item
label={t("bills.fields.is_credit_memo")}
@@ -300,9 +304,10 @@ export function BillFormComponent({
}),
]}
>
<Switch />
<Switch data-cy="is-credit-memo-switch" />
</Form.Item>
<Form.Item
data-cy="bill-form-bill-total"
label={t("bills.fields.total")}
name="total"
rules={[
@@ -316,7 +321,12 @@ export function BillFormComponent({
</Form.Item>
{!billEdit && (
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
<Select style={{ width: "10rem" }} disabled={disabled} allowClear>
<Select
data-cy="bill-form-parts-bin"
style={{ width: "10rem" }}
disabled={disabled}
allowClear
>
{bodyshop.md_parts_locations.map((loc, idx) => (
<Select.Option key={idx} value={loc}>
{loc}
@@ -375,17 +385,29 @@ export function BillFormComponent({
/>
<Statistic
title={t("bills.labels.federal_tax")}
value={totals.federalTax.toFormat()}
valueRender={() => (
<span data-cy="bill-form-tax">
{totals.federalTax.toFormat()}
</span>
)}
precision={2}
/>
<Statistic
title={t("bills.labels.state_tax")}
value={totals.stateTax.toFormat()}
valueRender={() => (
<span data-cy="bill-form-tax">
{totals.stateTax.toFormat()}
</span>
)}
precision={2}
/>
<Statistic
title={t("bills.labels.local_tax")}
value={totals.localTax.toFormat()}
valueRender={() => (
<span data-cy="bill-form-tax">
{totals.localTax.toFormat()}
</span>
)}
precision={2}
/>
<Statistic
@@ -406,7 +428,12 @@ export function BillFormComponent({
? "green"
: "red",
}}
value={totals.discrepancy.toFormat()}
// value={totals.discrepancy.toFormat()}
valueRender={() => (
<span id="bill-form-discrepancy">
{totals.discrepancy.toFormat()}
</span>
)}
precision={2}
/>
</Space>
@@ -456,19 +483,20 @@ export function BillFormComponent({
}}
>
<Upload.Dragger
id="bill-image-upload"
multiple={true}
name="logo"
beforeUpload={() => false}
listType="picture"
>
<>
<div>
<p className="ant-upload-drag-icon">
<UploadOutlined />
</p>
<p className="ant-upload-text">
Click or drag files to this area to upload.
</p>
</>
</div>
</Upload.Dragger>
</Form.Item>
</div>

View File

@@ -1,14 +1,15 @@
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Button, Form,
Button,
Form,
Input,
InputNumber,
Select,
Space,
Switch,
Table,
Tooltip
Tooltip,
} from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -79,6 +80,7 @@ export function BillEnterModalLinesComponent({
),
formInput: (record, index) => (
<BillLineSearchSelect
className="ant-select-bill-line"
disabled={disabled}
options={lineData}
style={{ width: "100%", minWidth: "10rem" }}
@@ -132,7 +134,9 @@ export function BillEnterModalLinesComponent({
],
};
},
formInput: (record, index) => <Input disabled={disabled} />,
formInput: (record, index) => (
<Input data-cy="bill-line-line-desc" disabled={disabled} />
),
},
{
title: t("billlines.fields.quantity"),
@@ -194,6 +198,7 @@ export function BillEnterModalLinesComponent({
},
formInput: (record, index) => (
<CurrencyInput
data-cy="bill-line-actual-price"
min={0}
disabled={disabled}
onBlur={(e) => {
@@ -241,6 +246,7 @@ export function BillEnterModalLinesComponent({
},
formInput: (record, index) => (
<CurrencyInput
data-cy="bill-line-actual-cost"
min={0}
disabled={disabled}
controls={false}
@@ -313,7 +319,12 @@ export function BillEnterModalLinesComponent({
};
},
formInput: (record, index) => (
<Select showSearch style={{ minWidth: "3rem" }} disabled={disabled}>
<Select
showSearch
style={{ minWidth: "3rem" }}
disabled={disabled}
className="ant-select-bill-cost-center"
>
{bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber
? CiecaSelect(true, false)
: responsibilityCenters.costs.map((item) => (
@@ -572,6 +583,7 @@ export function BillEnterModalLinesComponent({
return (
<>
<Table
data-cy="bill-line-table"
components={{
body: {
cell: EditableCell,
@@ -587,6 +599,7 @@ export function BillEnterModalLinesComponent({
/>
<Form.Item>
<Button
data-cy="bill-line-add-button"
disabled={disabled}
onClick={() => {
add();

View File

@@ -32,6 +32,7 @@ export function BillMarkExportedButton({
bodyshop,
authLevel,
bill,
...props
}) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
@@ -92,7 +93,12 @@ export function BillMarkExportedButton({
if (hasAccess)
return (
<Button loading={loading} disabled={bill.exported} onClick={handleUpdate}>
<Button
loading={loading}
disabled={bill.exported}
onClick={handleUpdate}
{...props}
>
{t("bills.labels.markexported")}
</Button>
);

View File

@@ -24,7 +24,12 @@ export default connect(
mapDispatchToProps
)(BillMarkForReexportButton);
export function BillMarkForReexportButton({ bodyshop, authLevel, bill }) {
export function BillMarkForReexportButton({
bodyshop,
authLevel,
bill,
...props
}) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
@@ -73,6 +78,7 @@ export function BillMarkForReexportButton({ bodyshop, authLevel, bill }) {
loading={loading}
disabled={!bill.exported}
onClick={handleUpdate}
{...props}
>
{t("bills.labels.markforreexport")}
</Button>

View File

@@ -54,7 +54,10 @@ export function BillsListTableComponent({
const recordActions = (record, showView = false) => (
<Space wrap>
{showView && (
<Button onClick={() => handleOnRowClick(record)}>
<Button
onClick={() => handleOnRowClick(record)}
data-cy="edit-bill-button"
>
<EditFilled />
</Button>
)}
@@ -126,7 +129,12 @@ export function BillsListTableComponent({
sortOrder:
state.sortedInfo.columnKey === "is_credit_memo" &&
state.sortedInfo.order,
render: (text, record) => <Checkbox checked={record.is_credit_memo} />,
render: (text, record) => (
<Checkbox
data-cy="credit-memo-checkbox"
checked={record.is_credit_memo}
/>
),
},
{
title: t("bills.fields.exported"),
@@ -135,7 +143,9 @@ export function BillsListTableComponent({
sorter: (a, b) => a.exported - b.exported,
sortOrder:
state.sortedInfo.columnKey === "exported" && state.sortedInfo.order,
render: (text, record) => <Checkbox checked={record.exported} />,
render: (text, record) => (
<Checkbox data-cy="bill-exported-checkbox" checked={record.exported} />
),
},
{
title: t("general.labels.actions"),
@@ -178,6 +188,7 @@ export function BillsListTableComponent({
{job && job.converted ? (
<>
<Button
data-cy="bills-post-button"
onClick={() => {
setBillEnterContext({
actions: { refetch: billsQuery.refetch },
@@ -217,6 +228,7 @@ export function BillsListTableComponent({
}
>
<Table
data-cy="bills-table"
loading={billsQuery.loading}
scroll={{
x: true, // y: "50rem"

View File

@@ -7,7 +7,9 @@ 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 GlobalSearchOs from "../global-search/global-search-os.component";
import "./breadcrumbs.styles.scss";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
breadcrumbs: selectBreadcrumbs,
@@ -15,6 +17,12 @@ const mapStateToProps = createStructuredSelector({
});
export function BreadCrumbs({ breadcrumbs, bodyshop }) {
const { OpenSearch } = useTreatments(
["OpenSearch"],
{},
bodyshop && bodyshop.imexshopid
);
return (
<Row className="breadcrumb-container">
<Col xs={24} sm={24} md={16}>
@@ -38,7 +46,7 @@ export function BreadCrumbs({ breadcrumbs, bodyshop }) {
</Breadcrumb>
</Col>
<Col xs={24} sm={24} md={8}>
<GlobalSearch />
{OpenSearch.treatment === "on" ? <GlobalSearchOs /> : <GlobalSearch />}
</Col>
</Row>
);

View File

@@ -10,7 +10,10 @@ export default function CABCpvrtCalculator({ disabled, form }) {
const handleFinish = async (values) => {
logImEXEvent("job_ca_bc_pvrt_calculate");
form.setFieldsValue({ ca_bc_pvrt: ((values.rate||0) * (values.days||0)).toFixed(2) });
form.setFieldsValue({
ca_bc_pvrt: ((values.rate || 0) * (values.days || 0)).toFixed(2),
});
form.setFields([{ name: "ca_bc_pvrt", touched: true }]);
setVisibility(false);
};

View File

@@ -1,5 +1,5 @@
import { Badge, List, Tag } from "antd";
import React from "react";
import React, { useEffect } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setSelectedConversation } from "../../redux/messaging/messaging.actions";
@@ -7,6 +7,8 @@ import { selectSelectedConversation } from "../../redux/messaging/messaging.sele
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import { List as VirtualizedList, AutoSizer } from "react-virtualized";
import "./chat-conversation-list.styles.scss";
const mapStateToProps = createStructuredSelector({
@@ -18,59 +20,86 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setSelectedConversation(conversationId)),
});
export function ChatConversationListComponent({
function ChatConversationListComponent({
conversationList,
selectedConversation,
setSelectedConversation,
subscribeToMoreConversations,
loadMoreConversations,
}) {
useEffect(
() => subscribeToMoreConversations(),
// eslint-disable-next-line react-hooks/exhaustive-deps
[]
);
const rowRenderer = ({ index, key, style }) => {
const item = conversationList[index];
return (
<List.Item
key={key}
onClick={() => setSelectedConversation(item.id)}
className={`chat-list-item ${
item.id === selectedConversation
? "chat-list-selected-conversation"
: null
}`}
style={style}
>
<div sryle={{ display: "inline-block" }}>
{item.label && <div className="chat-name">{item.label}</div>}
{item.job_conversations.length > 0 ? (
<div className="chat-name">
{item.job_conversations.map((j, idx) => (
<div key={idx}>
<OwnerNameDisplay ownerObject={j.job} />
</div>
))}
</div>
) : (
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
)}
</div>
<div sryle={{ display: "inline-block" }}>
<div>
{item.job_conversations.length > 0
? item.job_conversations.map((j, idx) => (
<Tag key={idx} className="ro-number-tag">
{j.job.ro_number}
</Tag>
))
: null}
</div>
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
</div>
<Badge count={item.messages_aggregate.aggregate.count || 0} />
</List.Item>
);
};
return (
<div className="chat-list-container">
<List
bordered
dataSource={conversationList}
renderItem={(item) => (
<List.Item
key={item.id}
onClick={() => setSelectedConversation(item.id)}
className={`chat-list-item ${
item.id === selectedConversation
? "chat-list-selected-conversation"
: null
}`}
>
<div sryle={{ display: "inline-block" }}>
{item.label && <div className="chat-name">{item.label}</div>}
{item.job_conversations.length > 0 ? (
<div className="chat-name">
{item.job_conversations.map((j, idx) => (
<div key={idx}>
<OwnerNameDisplay ownerObject={j.job} />
</div>
))}
</div>
) : (
<PhoneFormatter>{item.phone_num}</PhoneFormatter>
)}
</div>
<div sryle={{ display: "inline-block" }}>
<div>
{item.job_conversations.length > 0
? item.job_conversations.map((j, idx) => (
<Tag key={idx} className="ro-number-tag">
{j.job.ro_number}
</Tag>
))
: null}
</div>
<TimeAgoFormatter>{item.updated_at}</TimeAgoFormatter>
</div>
<Badge count={item.messages_aggregate.aggregate.count || 0} />
</List.Item>
<AutoSizer>
{({ height, width }) => (
<VirtualizedList
height={height}
width={width}
rowCount={conversationList.length}
rowHeight={60}
rowRenderer={rowRenderer}
onScroll={({ scrollTop, scrollHeight, clientHeight }) => {
if (scrollTop + clientHeight === scrollHeight) {
loadMoreConversations();
}
}}
/>
)}
/>
</AutoSizer>
</div>
);
}
export default connect(
mapStateToProps,
mapDispatchToProps

View File

@@ -3,8 +3,9 @@
}
.chat-list-container {
flex: 1;
overflow: auto;
overflow: hidden;
height: 100%;
border: 1px solid gainsboro;
}
.chat-list-item {
@@ -21,4 +22,6 @@
.ro-number-tag {
align-self: baseline;
}
padding: 12px 24px;
border-bottom: 1px solid gainsboro;
}

View File

@@ -42,6 +42,7 @@ export function ChatConversationContainer({ bodyshop, selectedConversation }) {
MARK_MESSAGES_AS_READ_BY_CONVERSATION,
{
variables: { conversationId: selectedConversation },
refetchQueries: ["UNREAD_CONVERSATION_COUNT"],
update(cache) {
cache.modify({
id: cache.identify({

View File

@@ -4,13 +4,17 @@ import {
ShrinkOutlined,
SyncOutlined,
} from "@ant-design/icons";
import { useQuery } from "@apollo/client";
import { useLazyQuery, useSubscription } from "@apollo/client";
import { Badge, Card, Col, Row, Space, Tag, Tooltip, Typography } from "antd";
import React, { useEffect, useState } from "react";
import React, { useCallback, useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { CONVERSATION_LIST_QUERY } from "../../graphql/conversations.queries";
import {
CONVERSATION_LIST_QUERY,
CONVERSATION_LIST_SUBSCRIPTION,
UNREAD_CONVERSATION_COUNT_SUBSCRIPTION,
} from "../../graphql/conversations.queries";
import { toggleChatVisible } from "../../redux/messaging/messaging.actions";
import {
selectChatVisible,
@@ -37,10 +41,18 @@ export function ChatPopupComponent({
}) {
const { t } = useTranslation();
const [pollInterval, setpollInterval] = useState(0);
const { loading, data, refetch, called } = useQuery(CONVERSATION_LIST_QUERY, {
const { data: unreadData } = useSubscription(
UNREAD_CONVERSATION_COUNT_SUBSCRIPTION
);
const [
getConversations,
{ loading, data, called, refetch, fetchMore, subscribeToMore },
] = useLazyQuery(CONVERSATION_LIST_QUERY, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
...(pollInterval > 0 ? { pollInterval } : {}),
skip: !chatVisible,
});
const fcmToken = sessionStorage.getItem("fcmtoken");
@@ -54,15 +66,24 @@ export function ChatPopupComponent({
}, [fcmToken]);
useEffect(() => {
if (called && chatVisible) refetch();
}, [chatVisible, called, refetch]);
if (called && chatVisible)
getConversations({
variables: {
offset: 0,
},
});
}, [chatVisible, called, getConversations]);
const unreadCount = data
? data.conversations.reduce(
(acc, val) => val.messages_aggregate.aggregate.count + acc,
0
)
: 0;
const loadMoreConversations = useCallback(() => {
if (data)
fetchMore({
variables: {
offset: data.conversations.length,
},
});
}, [data, fetchMore]);
const unreadCount = unreadData?.messages_aggregate.aggregate.count || 0;
return (
<Badge count={unreadCount}>
@@ -97,6 +118,44 @@ export function ChatPopupComponent({
) : (
<ChatConversationListComponent
conversationList={data ? data.conversations : []}
loadMoreConversations={loadMoreConversations}
subscribeToMoreConversations={() =>
subscribeToMore({
document: CONVERSATION_LIST_SUBSCRIPTION,
variables: { offset: 0 },
updateQuery: (prev, { subscriptionData }) => {
if (
!subscriptionData.data ||
subscriptionData.data.conversations.length === 0
)
return prev;
let conversations = [...prev.conversations];
const newConversations =
subscriptionData.data.conversations;
for (const conversation of newConversations) {
const index = conversations.findIndex(
(prevConversation) =>
prevConversation.id === conversation.id
);
if (index !== -1) {
conversations.splice(index, 1);
conversations.unshift(conversation);
continue;
}
conversations.unshift(conversation);
}
return Object.assign({}, prev, {
conversations: conversations,
});
},
})
}
/>
)}
</Col>

View File

@@ -16,7 +16,7 @@ export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
},
]}
>
<Checkbox disabled={readOnly} />
<Checkbox data-cy="checklist-item-checkbox" disabled={readOnly} />
</Form.Item>
);
}

View File

@@ -3,7 +3,7 @@ import FormTypes from "./config-form-types";
export default function ConfirmFormComponents({ componentList, readOnly }) {
return (
<div>
<div data-cy="config-form-components">
{componentList.map((f, idx) => {
const Comp = FormTypes[f.type];

View File

@@ -4,6 +4,18 @@ import React from "react";
export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
const { name, label, required, min, max } = formItem;
const marks = {
[min]: {
label: <span style={{ height: 0 }}>&nbsp;</span>,
},
[max / 2]: {
label: <span style={{ height: 0 }}>&nbsp;</span>,
},
[max]: {
label: <span style={{ height: 0 }}>&nbsp;</span>,
},
};
return (
<Form.Item
name={name}
@@ -15,7 +27,13 @@ export default function JobIntakeFormCheckboxComponent({ formItem, readOnly }) {
},
]}
>
<Slider disabled={readOnly} min={min || 0} max={max || 10} />
<Slider
disabled={readOnly}
min={min || 0}
max={max || 10}
marks={marks}
style={{ marginBottom: 0, marginTop: 0 }}
/>
</Form.Item>
);
}

View File

@@ -4,7 +4,7 @@ import moment from "moment";
import React from "react";
import { useTranslation } from "react-i18next";
import { DateFormatter } from "../../utils/DateFormatter";
import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component";
//import ContractLicenseDecodeButton from "../contract-license-decode-button/contract-license-decode-button.component";
import ContractStatusSelector from "../contract-status-select/contract-status-select.component";
import ContractsRatesChangeButton from "../contracts-rates-change-button/contracts-rates-change-button.component";
import CourtesyCarFuelSlider from "../courtesy-car-fuel-select/courtesy-car-fuel-select.component";
@@ -165,7 +165,9 @@ export default function ContractFormComponent({
/>
</div>
)}
<ContractLicenseDecodeButton form={form} />
{
//<ContractLicenseDecodeButton form={form} />
}
</Space>
</div>
<LayoutFormRow header={t("contracts.labels.driverinformation")}>

View File

@@ -8,6 +8,8 @@ export default function DataLabel({
vertical,
visible = true,
valueStyle = {},
valueClassName,
onValueClick,
...props
}) {
if (!visible || (hideIfNull && !!!children)) return null;
@@ -28,7 +30,10 @@ export default function DataLabel({
marginLeft: ".3rem",
fontWeight: "bolder",
wordWrap: "break-word",
cursor: onValueClick !== undefined ? "pointer" : "",
}}
className={valueClassName}
onClick={onValueClick}
>
{typeof children === "string" ? (
<Typography.Text style={valueStyle}>{children}</Typography.Text>

View File

@@ -35,6 +35,7 @@ export function DocumentsLocalUploadComponent({
return (
<Upload.Dragger
id="bill-document-upload"
multiple={true}
fileList={fileList}
onChange={(f) => {

View File

@@ -1,28 +1,28 @@
import { UploadOutlined, UserAddOutlined } from "@ant-design/icons";
import {
Button,
Divider,
Dropdown,
Form,
Input,
Menu,
Select,
Space,
Tabs,
Upload,
Space,
Menu,
Dropdown,
Button,
} from "antd";
import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next";
import EmailDocumentsComponent from "../email-documents/email-documents.component";
import _ from "lodash";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectEmailConfig } from "../../redux/email/email.selectors";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { CreateExplorerLinkForJob } from "../../utils/localmedia";
import { selectEmailConfig } from "../../redux/email/email.selectors";
import EmailDocumentsComponent from "../email-documents/email-documents.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -54,6 +54,15 @@ export function EmailOverlayComponent({
]),
});
};
const handle_CC_Click = ({ item, key, keyPath }) => {
const email = item.props.value;
form.setFieldsValue({
cc: _.uniq([
...(form.getFieldValue("cc") || ""),
...(typeof email === "string" ? [email] : email),
]),
});
};
const menu = (
<div>
@@ -74,6 +83,25 @@ export function EmailOverlayComponent({
</div>
);
const menuCC = (
<div>
<Menu onClick={handle_CC_Click}>
{bodyshop.employees
.filter((e) => e.user_email)
.map((e, idx) => (
<Menu.Item value={e.user_email} key={idx}>
{`${e.first_name} ${e.last_name}`}
</Menu.Item>
))}
{bodyshop.md_to_emails.map((e, idx) => (
<Menu.Item value={e.emails} key={idx + "group"}>
{e.label}
</Menu.Item>
))}
</Menu>
</div>
);
return (
<div>
<Form.Item
@@ -122,7 +150,23 @@ export function EmailOverlayComponent({
>
<Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item>
<Form.Item label={t("emails.fields.cc")} name="cc">
<Form.Item
label={
<Space>
{t("emails.fields.cc")}
<Dropdown overlay={menuCC}>
<a
className="ant-dropdown-link"
href=" #"
onClick={(e) => e.preventDefault()}
>
<UserAddOutlined />
</a>
</Dropdown>
</Space>
}
name="cc"
>
<Select mode="tags" tokenSeparators={[",", ";"]} />
</Form.Item>
<Form.Item

View File

@@ -5,13 +5,11 @@ const ReadOnlyFormItem = ({ value, type = "text", onChange }, ref) => {
if (!value) return null;
switch (type) {
case "text":
return <div>{value}</div>;
return <>{value}</>;
case "currency":
return (
<div>{Dinero({ amount: Math.round(value * 100) }).toFormat()}</div>
);
return <>{Dinero({ amount: Math.round(value * 100) }).toFormat()}</>;
default:
return <div>{value}</div>;
return <>{value}</>;
}
};
export default forwardRef(ReadOnlyFormItem);

View File

@@ -0,0 +1,216 @@
import { AutoComplete, Divider, Input, Space } from "antd";
import axios from "axios";
import _ from "lodash";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { Link, useHistory } from "react-router-dom";
import PhoneNumberFormatter from "../../utils/PhoneFormatter";
import OwnerNameDisplay, {
OwnerNameDisplayFunction,
} from "../owner-name-display/owner-name-display.component";
import VehicleVinDisplay from "../vehicle-vin-display/vehicle-vin-display.component";
export default function GlobalSearchOs() {
const { t } = useTranslation();
const history = useHistory();
const [loading, setLoading] = useState(false);
const [data, setData] = useState(false);
const executeSearch = async (v) => {
if (v && v && v !== "" && v.length >= 3) {
try {
setLoading(true);
const searchData = await axios.post("/search", {
search: v,
});
const resultsByType = {
payments: [],
jobs: [],
bills: [],
owners: [],
vehicles: [],
};
searchData.data.hits.hits.forEach((hit) => {
resultsByType[hit._index].push(hit._source);
});
setData([
{
label: renderTitle(t("menus.header.search.jobs")),
options: resultsByType.jobs.map((job) => {
return {
key: job.id,
value: job.ro_number || "N/A",
label: (
<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>
<OwnerNameDisplay ownerObject={job} />
</span>
<span>{`${job.v_model_yr || ""} ${
job.v_make_desc || ""
} ${job.v_model_desc || ""}`}</span>
<span>{`${job.clm_no || ""}`}</span>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.owners")),
options: resultsByType.owners.map((owner) => {
return {
key: owner.id,
value: OwnerNameDisplayFunction(owner),
label: (
<Link to={`/manage/owners/${owner.id}`}>
<Space
size="small"
split={<Divider type="vertical" />}
wrap
>
<span>
<OwnerNameDisplay ownerObject={owner} />
</span>
<PhoneNumberFormatter>
{owner.ownr_ph1}
</PhoneNumberFormatter>
<PhoneNumberFormatter>
{owner.ownr_ph2}
</PhoneNumberFormatter>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.vehicles")),
options: resultsByType.vehicles.map((vehicle) => {
return {
key: vehicle.id,
value: `${vehicle.v_model_yr || ""} ${
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`,
label: (
<Link to={`/manage/vehicles/${vehicle.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>
{`${vehicle.v_model_yr || ""} ${
vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`}
</span>
<span>{vehicle.plate_no || ""}</span>
<span>
<VehicleVinDisplay>
{vehicle.v_vin || ""}
</VehicleVinDisplay>
</span>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.payments")),
options: resultsByType.payments.map((payment) => {
return {
key: payment.id,
value: `${payment.job?.ro_number} ${payment.amount}`,
label: (
<Link to={`/manage/jobs/${payment.job?.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>{payment.paymentnum}</span>
<span>{payment.job?.ro_number}</span>
<span>{payment.memo || ""}</span>
<span>{payment.amount || ""}</span>
<span>{payment.transactionid || ""}</span>
</Space>
</Link>
),
};
}),
},
{
label: renderTitle(t("menus.header.search.bills")),
options: resultsByType.bills.map((bill) => {
return {
key: bill.id,
value: `${bill.invoice_number} - ${bill.vendor.name}`,
label: (
<Link to={`/manage/bills?billid=${bill.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
<span>{bill.invoice_number}</span>
<span>{bill.vendor.name}</span>
<span>{bill.date}</span>
</Space>
</Link>
),
};
}),
},
// {
// label: renderTitle(t("menus.header.search.phonebook")),
// options: resultsByType.search_phonebook.map((pb) => {
// return {
// key: pb.id,
// value: `${pb.firstname || ""} ${pb.lastname || ""} ${
// pb.company || ""
// }`,
// label: (
// <Link to={`/manage/phonebook?phonebookentry=${pb.id}`}>
// <Space size="small" split={<Divider type="vertical" />}>
// <span>{`${pb.firstname || ""} ${pb.lastname || ""} ${
// pb.company || ""
// }`}</span>
// <PhoneNumberFormatter>{pb.phone1}</PhoneNumberFormatter>
// <span>{pb.email}</span>
// </Space>
// </Link>
// ),
// };
// }),
// },
]);
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setLoading(false);
}
}
};
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
const handleSearch = (value) => {
debouncedExecuteSearch(value);
};
const renderTitle = (title) => {
return <span>{title}</span>;
};
return (
<AutoComplete
options={data}
onSearch={handleSearch}
defaultActiveFirstOption
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
onClear={() => setData([])}
>
<Input.Search
size="large"
placeholder={t("general.labels.globalsearch")}
enterButton
allowClear
loading={loading}
/>
</AutoComplete>
);
}

View File

@@ -1,6 +1,5 @@
import { useLazyQuery } from "@apollo/client";
import { LoadingOutlined } from "@ant-design/icons";
import { AutoComplete, Divider, Space } from "antd";
import { AutoComplete, Divider, Input, Space } from "antd";
import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -19,11 +18,18 @@ export default function GlobalSearch() {
useLazyQuery(GLOBAL_SEARCH_QUERY);
const executeSearch = (v) => {
if (v && v.variables.search && v.variables.search !== "") callSearch(v);
if (
v &&
v.variables.search &&
v.variables.search !== "" &&
v.variables.search.length >= 3
)
callSearch(v);
};
const debouncedExecuteSearch = _.debounce(executeSearch, 750);
const handleSearch = (value) => {
console.log("Handle Search");
debouncedExecuteSearch({ variables: { search: value } });
};
@@ -38,7 +44,7 @@ export default function GlobalSearch() {
options: data.search_jobs.map((job) => {
return {
key: job.id,
value: job.ro_number,
value: job.ro_number || "N/A",
label: (
<Link to={`/manage/jobs/${job.id}`}>
<Space size="small" split={<Divider type="vertical" />}>
@@ -178,13 +184,18 @@ export default function GlobalSearch() {
<AutoComplete
options={options}
onSearch={handleSearch}
suffixIcon={loading && <LoadingOutlined spin />}
defaultActiveFirstOption
placeholder={t("general.labels.globalsearch")}
allowClear
onSelect={(val, opt) => {
history.push(opt.label.props.to);
}}
></AutoComplete>
>
<Input.Search
size="large"
placeholder={t("general.labels.globalsearch")}
enterButton
allowClear
loading={loading}
/>
</AutoComplete>
);
}

View File

@@ -106,6 +106,7 @@ function Header({
selectedKeys={[selectedHeader]}
onClick={handleMenuClick}
subMenuCloseDelay={0.3}
data-cy="header-menu"
>
<Menu.Item key="home" icon={<HomeFilled />}>
<Link to="/manage">{t("menus.header.home")}</Link>
@@ -347,8 +348,14 @@ function Header({
currentUser.email ||
t("general.labels.unknown")
}
data-cy="user-sub-menu"
>
<Menu.Item key="signout" danger onClick={() => signOutStart()}>
<Menu.Item
key="signout"
danger
data-cy="sign-out-button"
onClick={() => signOutStart()}
>
{t("user.actions.signout")}
</Menu.Item>
<Menu.Item

View File

@@ -3,9 +3,11 @@ import {
Button,
Divider,
Dropdown,
Form,
Menu,
notification,
Popover,
Select,
Space,
} from "antd";
import parsePhoneNumber from "libphonenumber-js";
@@ -59,7 +61,10 @@ export function ScheduleEventComponent({
const blockContent = (
<div>
<Button onClick={() => handleCancel(event.id)} disabled={event.arrived}>
<Button
onClick={() => handleCancel({ id: event.id })}
disabled={event.arrived}
>
{t("appointments.actions.cancel")}
</Button>
</div>
@@ -203,10 +208,46 @@ export function ScheduleEventComponent({
<Button>{t("appointments.actions.sendreminder")}</Button>
</Dropdown>
) : null}
<Button onClick={() => handleCancel(event.id)} disabled={event.arrived}>
{t("appointments.actions.cancel")}
</Button>
<Popover
trigger="click"
disabled={event.arrived}
content={
<Form
layout="vertical"
onFinish={({ lost_sale_reason }) => {
handleCancel({ id: event.id, lost_sale_reason });
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button htmlType="submit">
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
<Button
// onClick={() => handleCancel(event.id)}
disabled={event.arrived}
>
{t("appointments.actions.cancel")}
</Button>
</Popover>
{event.isintake ? (
<Button
disabled={event.arrived}
@@ -249,7 +290,7 @@ export function ScheduleEventComponent({
const RegularEvent = event.isintake ? (
<Space
wrap
size='small'
size="small"
style={{
backgroundColor:
event.color && event.color.hex ? event.color.hex : event.color,

View File

@@ -11,7 +11,7 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
const { t } = useTranslation();
const [cancelAppointment] = useMutation(CANCEL_APPOINTMENT_BY_ID);
const [updateJob] = useMutation(UPDATE_JOB);
const handleCancel = async (id) => {
const handleCancel = async ({ id, lost_sale_reason }) => {
logImEXEvent("schedule_cancel_appt");
const cancelAppt = await cancelAppointment({
@@ -38,7 +38,8 @@ export default function ScheduleEventContainer({ bodyshop, event, refetch }) {
job: {
date_scheduled: null,
scheduled_in: null,
scheduled_completion:null,
scheduled_completion: null,
lost_sale_reason,
status: bodyshop.md_ro_statuses.default_imported,
},
},

View File

@@ -84,6 +84,8 @@ export default function JobBillsTotalComponent({
})
);
console.log(totals.parts.parts.total);
const totalPartsSublet = Dinero(totals.parts.parts.total)
.add(Dinero(totals.parts.sublets.total))
.add(Dinero(totals.additional.shipping))
@@ -127,7 +129,10 @@ export default function JobBillsTotalComponent({
>
<Statistic
title={t("bills.labels.retailtotal")}
value={billTotals.toFormat()}
// value={billTotals.toFormat()}
valueRender={() => (
<span id="retailtotal">{billTotals.toFormat()}</span>
)}
/>
</Tooltip>
<Typography.Title>=</Typography.Title>
@@ -145,7 +150,12 @@ export default function JobBillsTotalComponent({
valueStyle={{
color: discrepancy.getAmount() === 0 ? "green" : "red",
}}
value={discrepancy.toFormat()}
// value={discrepancy.toFormat()}
valueRender={() => (
<span id="discrepancy" className="discrepancy">
{discrepancy.toFormat()}
</span>
)}
/>
</Tooltip>
<Typography.Title>+</Typography.Title>
@@ -178,7 +188,12 @@ export default function JobBillsTotalComponent({
valueStyle={{
color: discrepWithLbrAdj.getAmount() === 0 ? "green" : "red",
}}
value={discrepWithLbrAdj.toFormat()}
// value={discrepWithLbrAdj.toFormat()}
valueRender={() => (
<span id="discrepWithLbrAdj" className="discrepancy">
{discrepWithLbrAdj.toFormat()}
</span>
)}
/>
</Tooltip>
<Typography.Title>+</Typography.Title>
@@ -193,7 +208,10 @@ export default function JobBillsTotalComponent({
>
<Statistic
title={t("bills.labels.totalreturns")}
value={totalReturns.toFormat()}
// value={totalReturns.toFormat()}
valueRender={() => (
<span id="totalReturns">{totalReturns.toFormat()}</span>
)}
/>
</Tooltip>
<Typography.Title>=</Typography.Title>
@@ -211,7 +229,12 @@ export default function JobBillsTotalComponent({
valueStyle={{
color: discrepWithCms.getAmount() === 0 ? "green" : "red",
}}
value={discrepWithCms.toFormat()}
// value={discrepWithCms.toFormat()}
valueRender={() => (
<span id="discrepWithCms" className="discrepancy">
{discrepWithCms.toFormat()}
</span>
)}
/>
</Tooltip>
</Space>
@@ -231,7 +254,10 @@ export default function JobBillsTotalComponent({
>
<Statistic
title={t("bills.labels.totalreturns")}
value={totalReturns.toFormat()}
// value={totalReturns.toFormat()}
valueRender={() => (
<span id="totalReturns">{totalReturns.toFormat()}</span>
)}
/>
</Tooltip>
<Tooltip
@@ -253,11 +279,18 @@ export default function JobBillsTotalComponent({
? "green"
: "red",
}}
value={
calculatedCreditsNotReceived.getAmount() >= 0
? calculatedCreditsNotReceived.toFormat()
: Dinero().toFormat()
}
// value={
// calculatedCreditsNotReceived.getAmount() >= 0
// ? calculatedCreditsNotReceived.toFormat()
// : Dinero().toFormat()
// }
valueRender={() => (
<span id="calculatedcreditsnotreceived">
{calculatedCreditsNotReceived.getAmount() >= 0
? calculatedCreditsNotReceived.toFormat()
: Dinero().toFormat()}
</span>
)}
/>
</Tooltip>
<Tooltip
@@ -277,11 +310,18 @@ export default function JobBillsTotalComponent({
? "green"
: "red",
}}
value={
totalReturnsMarkedNotReceived.getAmount() >= 0
? totalReturnsMarkedNotReceived.toFormat()
: Dinero().toFormat()
}
// value={
// totalReturnsMarkedNotReceived.getAmount() >= 0
// ? totalReturnsMarkedNotReceived.toFormat()
// : Dinero().toFormat()
// }
valueRender={() => (
<span id="creditsnotreceived">
{totalReturnsMarkedNotReceived.getAmount() >= 0
? totalReturnsMarkedNotReceived.toFormat()
: Dinero().toFormat()}
</span>
)}
/>
</Tooltip>
</Space>

View File

@@ -199,6 +199,7 @@ export function JobChecklistForm({
extra={
!readOnly && (
<Button
data-cy="checklist-submit-button"
loading={loading}
type="primary"
onClick={() => form.submit()}
@@ -209,6 +210,7 @@ export function JobChecklistForm({
}
>
<Form
data-cy="checklist-form"
form={form}
onFinish={handleFinish}
initialValues={{
@@ -256,7 +258,7 @@ export function JobChecklistForm({
label={t("checklist.labels.addtoproduction")}
disabled={readOnly}
>
<Switch disabled={readOnly} />
<Switch data-cy="add-to-production-switch" disabled={readOnly} />
</Form.Item>
<Form.Item
name="allow_text_message"
@@ -292,7 +294,11 @@ export function JobChecklistForm({
disabled={readOnly}
trigger="onChange"
>
<Input.TextArea rows={3} disabled={readOnly} />
<Input.TextArea
data-cy="checklist-production-note"
rows={3}
disabled={readOnly}
/>
</Form.Item>
</div>
)}
@@ -324,7 +330,11 @@ export function JobChecklistForm({
label={t("checklist.labels.removefromproduction")}
disabled={readOnly}
>
<Switch disabled={readOnly} defaultChecked={true} />
<Switch
data-cy="remove-from-production"
disabled={readOnly}
defaultChecked={true}
/>
</Form.Item>
</div>
)}

View File

@@ -23,6 +23,7 @@ export default function JobCostingPartsTable({ data, summaryData }) {
sorter: (a, b) => alphaSort(a.cost_center, b.cost_center),
sortOrder:
state.sortedInfo.columnKey === "cost_center" && state.sortedInfo.order,
render: (record) => <span data-cy="responsibilitycenter">{record}</span>,
},
{
title: t("jobs.labels.sales"),
@@ -42,6 +43,7 @@ export default function JobCostingPartsTable({ data, summaryData }) {
parseFloat(a.costs.substring(1)) - parseFloat(b.costs.substring(1)),
sortOrder:
state.sortedInfo.columnKey === "costs" && state.sortedInfo.order,
render: (record) => <span data-cy="cost">{record}</span>,
},
{

View File

@@ -22,12 +22,12 @@ export default function JobLinesExpander({ jobline, jobid }) {
if (error) return <AlertComponent message={error.message} type="error" />;
return (
<Row>
<Row data-cy="parts-expanded-row">
<Col md={24} lg={12}>
<Typography.Title level={4}>
{t("parts_orders.labels.parts_orders")}
</Typography.Title>
<Timeline>
<Timeline data-cy="parts-bills-order">
{data.parts_order_lines.length > 0 ? (
data.parts_order_lines.map((line) => (
<Timeline.Item key={line.id}>
@@ -43,7 +43,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
</Timeline.Item>
))
) : (
<Timeline.Item>
<Timeline.Item data-cy="parts-empty-order">
{t("parts_orders.labels.notyetordered")}
</Timeline.Item>
)}
@@ -51,7 +51,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
</Col>
<Col md={24} lg={12}>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Timeline>
<Timeline data-cy="parts-bills-order">
{data.billlines.length > 0 ? (
data.billlines.map((line) => (
<Timeline.Item key={line.id}>
@@ -71,7 +71,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
</Col>
<Col span={4}>
<span>
{`${t("billlines.fields.actual_cost")}: `}
{`${t("billlines.fields.actual_cost")}: `}
<CurrencyFormatter>{line.actual_cost}</CurrencyFormatter>
</span>
</Col>
@@ -83,7 +83,7 @@ export default function JobLinesExpander({ jobline, jobid }) {
</Timeline.Item>
))
) : (
<Timeline.Item>
<Timeline.Item data-cy="parts-empty-order">
{t("parts_orders.labels.notyetordered")}
</Timeline.Item>
)}

View File

@@ -103,6 +103,12 @@ export function JobLinesComponent({
fixed: "left",
key: "line_desc",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
onCell: (record) => ({
className: record.manual_line && "job-line-manual",
style: {
...(record.critical ? { boxShadow: " -.5em 0 0 #FFC107" } : {}),
},
}),
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
ellipsis: true,
@@ -342,7 +348,7 @@ export function JobLinesComponent({
onClick={() => {
setJobLineEditContext({
actions: { refetch: refetch, submit: form && form.submit },
context: record,
context: { ...record, jobid: job.id },
});
}}
>
@@ -435,6 +441,7 @@ export function JobLinesComponent({
</Tag>
)}
<Button
data-cy="order-parts-inhouse-button"
disabled={
(job && !job.converted) ||
(selectedLines.length > 0 ? false : true) ||
@@ -491,6 +498,7 @@ export function JobLinesComponent({
{selectedLines.length > 0 && ` (${selectedLines.length})`}
</Button>
<Button
data-cy="order-parts-button"
disabled={
(job && !job.converted) ||
(selectedLines.length > 0 ? false : true) ||
@@ -520,6 +528,7 @@ export function JobLinesComponent({
{selectedLines.length > 0 && ` (${selectedLines.length})`}
</Button>
<Button
data-cy="filter-parts-button"
onClick={() => {
setState((state) => ({
...state,
@@ -569,6 +578,7 @@ export function JobLinesComponent({
}
/>
<Table
data-cy="repair-data-table"
columns={columns}
rowKey="id"
loading={loading}

View File

@@ -22,9 +22,20 @@ export function JoblinePresetButton({ bodyshop, form }) {
};
const menu = (
<Menu>
<Menu
style={{
columnCount: Math.max(
Math.floor(bodyshop.md_jobline_presets.length / 15),
1
),
}}
>
{bodyshop.md_jobline_presets.map((i, idx) => (
<Menu.Item onClick={() => handleSelect(i)} key={idx}>
<Menu.Item
onClick={() => handleSelect(i)}
key={idx}
style={{ breakInside: "avoid" }}
>
{i.label}
</Menu.Item>
))}

View File

@@ -40,6 +40,11 @@ export function JobLinesUpsertModalComponent({
{},
bodyshop.imexshopid
);
const { Autohouse_Detail_line } = useTreatments(
["Autohouse_Detail_line"],
{},
bodyshop.imexshopid
);
return (
<Modal
@@ -155,6 +160,40 @@ export function JobLinesUpsertModalComponent({
>
<InputNumber precision={1} />
</Form.Item>
{Autohouse_Detail_line.treatment === "on" && (
<Form.Item
label={t("joblines.fields.ah_detail_line")}
name="ah_detail_line"
valuePropName="checked"
dependencies={["mod_lbr_ty"]}
initialValue={false}
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (
value === false ||
value === undefined ||
value === null
)
return Promise.resolve();
if (
value === true &&
["LA1", "LA2", "LA3", "LA4", "LAU"].includes(
getFieldValue("mod_lbr_ty")
)
) {
return Promise.resolve();
}
return Promise.reject(
t("joblines.validations.ahdetailonlyonuserdefinedtypes")
);
},
}),
]}
>
<Switch />
</Form.Item>
)}
</LayoutFormRow>
<LayoutFormRow>
<Form.Item label={t("joblines.fields.part_type")} name="part_type">
@@ -218,7 +257,6 @@ export function JobLinesUpsertModalComponent({
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
console.log(value);
if (!value || getFieldValue("part_type") !== "PAE") {
return Promise.resolve();
}
@@ -229,7 +267,6 @@ export function JobLinesUpsertModalComponent({
}),
({ getFieldValue }) => ({
validator(rule, value) {
console.log(value, !!value);
if (
!!getFieldValue("part_type") === (!!value || value === 0)
) {
@@ -252,7 +289,7 @@ export function JobLinesUpsertModalComponent({
name="prt_dsmk_p"
initialValue={0}
>
<InputNumber precision={0} min={0} max={100} />
<InputNumber precision={0} min={-100} max={100} />
</Form.Item>
<Form.Item
label={t("joblines.fields.tax_part")}

View File

@@ -13,8 +13,13 @@ import { selectJobLineEditModal } from "../../redux/modals/modals.selectors";
import UndefinedToNull from "../../utils/undefinedtonull";
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
import Axios from "axios";
import Dinero from "dinero.js";
import CriticalPartsScan from "../../utils/criticalPartsScan";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
jobLineEditModal: selectJobLineEditModal,
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit")),
@@ -23,7 +28,13 @@ const mapDispatchToProps = (dispatch) => ({
function JobLinesUpsertModalContainer({
jobLineEditModal,
toggleModalVisible,
bodyshop,
}) {
const { CriticalPartsScanning } = useTreatments(
["CriticalPartsScanning"],
{},
bodyshop.imexshopid
);
const { t } = useTranslation();
const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE);
const [updateJobLine] = useMutation(UPDATE_JOB_LINE);
@@ -40,7 +51,15 @@ function JobLinesUpsertModalContainer({
manual_line: !(
jobLineEditModal.context && jobLineEditModal.context.id
),
...UndefinedToNull(values),
...UndefinedToNull({
...values,
prt_dsmk_m: Dinero({
amount: Math.round((values.act_price || 0) * 100),
})
.percentage(Math.abs(values.prt_dsmk_p || 0))
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
.toFormat(0.0),
}),
},
],
},
@@ -68,7 +87,15 @@ function JobLinesUpsertModalContainer({
const r = await updateJobLine({
variables: {
lineId: jobLineEditModal.context.id,
line: values,
line: {
...values,
prt_dsmk_m: Dinero({
amount: Math.round(values.act_price * 100),
})
.percentage(Math.abs(values.prt_dsmk_p || 0))
.multiply(values.prt_dsmk_p >= 0 ? 1 : -1)
.toFormat(0.0),
},
},
refetchQueries: ["GET_LINE_TICKET_BY_PK"],
});
@@ -92,6 +119,9 @@ function JobLinesUpsertModalContainer({
}
toggleModalVisible();
}
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(jobLineEditModal.context.jobid);
}
setLoading(false);
};

View File

@@ -25,8 +25,6 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(setModalContext({ context: context, modal: "payment" })),
});
const stripeTestEnv = process.env.REACT_APP_STRIPE_PUBLIC_KEY; //.includes("test");
export function JobPayments({
job,
jobRO,
@@ -94,23 +92,6 @@ export function JobPayments({
state.sortedInfo.columnKey === "transactionid" &&
state.sortedInfo.order,
},
{
title: t("payments.fields.stripeid"),
dataIndex: "stripeid",
key: "stripeid",
render: (text, record) =>
record.stripeid ? (
<a
href={
stripeTestEnv
? `https://dashboard.stripe.com/${bodyshop.stripe_acct_id}/test/payments/${record.stripeid}`
: `https://dashboard.stripe.com/${bodyshop.stripe_acct_id}/payments/${record.stripeid}`
}
>
{record.stripeid}
</a>
) : null,
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
@@ -118,7 +99,8 @@ export function JobPayments({
render: (text, record) => (
<Space wrap>
<Button
disabled={record.exportedat}
// disabled={record.exportedat}
data-cy="edit-payment-button"
onClick={() => {
setPaymentContext({
actions: { refetch: refetch },
@@ -169,6 +151,7 @@ export function JobPayments({
extra={
<Space wrap>
<Button
data-cy="job-payment-button"
disabled={!job.converted}
onClick={() =>
setPaymentContext({
@@ -189,6 +172,7 @@ export function JobPayments({
}
>
<Table
data-cy="payments-table"
columns={columns}
rowKey="id"
pagination={false}

View File

@@ -166,6 +166,16 @@ export default function ScoreboardAddButton({
painthrs: 0,
}
);
//Add Labor Adjustments
v.painthrs = v.painthrs + (job.lbr_adjustments.LAR || 0);
v.bodyhrs =
v.bodyhrs +
Object.keys(job.lbr_adjustments)
.filter((key) => key !== "LAR")
.reduce((acc, val) => {
return acc + job.lbr_adjustments[val];
}, 0);
form.setFieldsValue({
date: new moment(),
bodyhrs: Math.round(v.bodyhrs * 10) / 10,

View File

@@ -108,6 +108,7 @@ export function JobTotalsTableTotals({ bodyshop, job }) {
return (
<Table
data-cy="job-totals-table"
columns={columns}
rowKey="key"
showHeader={false}

View File

@@ -171,12 +171,16 @@ export function JobsAvailableComponent({
{!isClosed && (
<>
<Button
data-cy="add-job-as-new-button"
onClick={() => addJobAsNew(record)}
disabled={record.issupplement}
>
<PlusCircleFilled />
</Button>
<Button onClick={() => addJobAsSupp(record)}>
<Button
data-cy="add-job-as-supplement"
onClick={() => addJobAsSupp(record)}
>
<DownloadOutlined />
</Button>
</>
@@ -214,6 +218,7 @@ export function JobsAvailableComponent({
extra={
<Space wrap>
<Button
data-cy="refetch-available-jobs-button"
onClick={() => {
refetch();
}}
@@ -255,6 +260,7 @@ export function JobsAvailableComponent({
columns={columns}
rowKey="id"
dataSource={availableJobs}
data-cy="available-jobs-table"
onChange={handleTableChange}
/>
</Card>

View File

@@ -5,6 +5,7 @@ import {
useMutation,
useQuery,
} from "@apollo/client";
import { useTreatments } from "@splitsoftware/splitio-react";
import { Col, notification, Row } from "antd";
import Axios from "axios";
import Dinero from "dinero.js";
@@ -31,6 +32,7 @@ import {
} from "../../redux/user/user.selectors";
import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import CriticalPartsScan from "../../utils/criticalPartsScan";
import AlertComponent from "../alert/alert.component";
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
@@ -53,6 +55,11 @@ export function JobsAvailableContainer({
currentUser,
insertAuditTrail,
}) {
const { CriticalPartsScanning } = useTreatments(
["CriticalPartsScanning"],
{},
bodyshop.imexshopid
);
const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
@@ -155,6 +162,9 @@ export function JobsAvailableContainer({
},
})
.then((r) => {
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(r.data.insert_jobs.returning[0].id);
}
notification["success"]({
message: t("jobs.successes.created"),
onClick: () => {
@@ -241,7 +251,9 @@ export function JobsAvailableContainer({
},
},
});
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
}
if (updateResult.errors) {
//error while inserting
notification["error"]({

View File

@@ -9,6 +9,7 @@ import {
Space,
Switch,
} from "antd";
import axios from "axios";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -18,7 +19,6 @@ import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import axios from "axios";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
@@ -43,14 +43,22 @@ export function JobsConvertButton({
const { t } = useTranslation();
const [form] = Form.useForm();
const handleConvert = async (values) => {
const handleConvert = async ({ employee_csr, category, ...values }) => {
if (parentFormIsFieldsTouched()) {
alert(t("jobs.labels.savebeforeconversion"));
return;
}
setLoading(true);
const res = await mutationConvertJob({
variables: { jobId: job.id, ...values },
variables: {
jobId: job.id,
job: {
converted: true,
...(bodyshop.enforce_conversion_csr ? { employee_csr } : {}),
...(bodyshop.enforce_conversion_category ? { category } : {}),
...values,
},
},
});
if (values.ca_gst_registrant) {
@@ -83,7 +91,12 @@ export function JobsConvertButton({
layout="vertical"
form={form}
onFinish={handleConvert}
initialValues={{ driveable: true, towin: false }}
initialValues={{
driveable: true,
towin: false,
employee_csr: job.employee_csr,
category: job.category,
}}
>
<Form.Item
name={["ins_co_nm"]}
@@ -95,9 +108,9 @@ export function JobsConvertButton({
},
]}
>
<Select>
{bodyshop.md_ins_cos.map((s) => (
<Select.Option key={s.name} value={s.name}>
<Select className="ant-select-ins_co_nm">
{bodyshop.md_ins_cos.map((s, i) => (
<Select.Option key={i} value={s.name}>
{s.name}
</Select.Option>
))}
@@ -114,7 +127,7 @@ export function JobsConvertButton({
},
]}
>
<Select>
<Select className="ant-select-class">
{bodyshop.md_classes.map((s) => (
<Select.Option key={s} value={s}>
{s}
@@ -135,7 +148,7 @@ export function JobsConvertButton({
},
]}
>
<Select>
<Select className="ant-select-referral_source">
{bodyshop.md_referral_sources.map((s) => (
<Select.Option key={s} value={s}>
{s}
@@ -144,6 +157,7 @@ export function JobsConvertButton({
</Select>
</Form.Item>
<Form.Item
data-cy="referral_source_extra"
label={t("jobs.fields.referral_source_extra")}
name="referral_source_extra"
>
@@ -151,13 +165,71 @@ export function JobsConvertButton({
</Form.Item>
</>
)}
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch />
</Form.Item>
{bodyshop.enforce_conversion_csr && (
<Form.Item
name={"employee_csr"}
label={t("jobs.fields.employee_csr")}
rules={[
{
required: bodyshop.enforce_conversion_csr,
//message: t("general.validation.required"),
},
]}
>
<Select
className="ant-select-employee_csr"
showSearch
style={{ width: 200 }}
optionFilterProp="children"
filterOption={(input, option) =>
option.props.children
.toLowerCase()
.indexOf(input.toLowerCase()) >= 0
}
>
{bodyshop.employees
.filter((emp) => emp.active)
.map((emp) => (
<Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`}
</Select.Option>
))}
</Select>
</Form.Item>
)}
{bodyshop.enforce_conversion_category && (
<Form.Item
name={"category"}
label={t("jobs.fields.category")}
rules={[
{
required: bodyshop.enforce_conversion_category,
//message: t("general.validation.required"),
},
]}
>
<Select allowClear className="ant-select-category">
{bodyshop.md_categories.map((s) => (
<Select.Option key={s} value={s}>
{s}
</Select.Option>
))}
</Select>
</Form.Item>
)}
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch />
</Form.Item>
)}
<Form.Item
label={t("jobs.fields.driveable")}
name="driveable"
@@ -173,7 +245,12 @@ export function JobsConvertButton({
<Switch />
</Form.Item>
<Space wrap>
<Button type="danger" onClick={() => form.submit()} loading={loading}>
<Button
type="danger"
data-cy="convert-button"
onClick={() => form.submit()}
loading={loading}
>
{t("jobs.actions.convert")}
</Button>
<Button onClick={() => setVisible(false)}>
@@ -194,7 +271,15 @@ export function JobsConvertButton({
// style={{ display: job.converted ? "none" : "" }}
disabled={job.converted || jobRO}
loading={loading}
onClick={() => setVisible(true)}
data-cy="job-convert-button"
onClick={() => {
setVisible(true);
form.setFieldsValue({
driveable: true,
towin: false,
employee_csr: job.employee_csr,
});
}}
>
{t("jobs.actions.convert")}
</Button>

View File

@@ -224,13 +224,15 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch />
</Form.Item>
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch />
</Form.Item>
)}
<Form.Item
label={t("jobs.fields.other_amount_payable")}
name="other_amount_payable"

View File

@@ -9,7 +9,11 @@ const colSpan = {
lg: { span: 12 },
};
export default function JobsCreateVehicleInfoComponent({ loading, vehicles }) {
export default function JobsCreateVehicleInfoComponent({
loading,
vehicles,
form,
}) {
const [state, setState] = useContext(JobCreateContext);
const { t } = useTranslation();
return (
@@ -58,7 +62,7 @@ export default function JobsCreateVehicleInfoComponent({ loading, vehicles }) {
/>
</Col>
<Col {...colSpan}>
<JobsCreateVehicleInfoNewComponent />
<JobsCreateVehicleInfoNewComponent form={form}/>
</Col>
</Row>
</div>

View File

@@ -20,6 +20,7 @@ export default function JobsCreateVehicleInfoContainer({ form }) {
<JobsCreateVehicleInfoComponent
loading={loading}
vehicles={data ? data.search_vehicles : null}
form={form}
/>
);
}

View File

@@ -4,8 +4,9 @@ import { useTranslation } from "react-i18next";
import JobCreateContext from "../../pages/jobs-create/jobs-create.context";
import FormDatePicker from "../form-date-picker/form-date-picker.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import JobsCreateVehicleInfoPredefined from "./jobs-create-vehicle-info.predefined.component";
export default function JobsCreateVehicleInfoNewComponent() {
export default function JobsCreateVehicleInfoNewComponent({ form }) {
const [state] = useContext(JobCreateContext);
const { t } = useTranslation();
@@ -25,7 +26,7 @@ export default function JobsCreateVehicleInfoNewComponent() {
<Input disabled={!state.vehicle.new} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<LayoutFormRow grow noDivider>
<Form.Item
label={t("vehicles.fields.v_color")}
name={["vehicle", "data", "v_color"]}
@@ -52,8 +53,9 @@ export default function JobsCreateVehicleInfoNewComponent() {
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
<LayoutFormRow grow noDivider>
<Form.Item
span={10}
label={t("vehicles.fields.v_make_desc")}
name={["vehicle", "data", "v_make_desc"]}
rules={[
@@ -66,6 +68,7 @@ export default function JobsCreateVehicleInfoNewComponent() {
<Input disabled={!state.vehicle.new} />
</Form.Item>
<Form.Item
span={11}
label={t("vehicles.fields.v_model_desc")}
name={["vehicle", "data", "v_model_desc"]}
rules={[
@@ -77,6 +80,11 @@ export default function JobsCreateVehicleInfoNewComponent() {
>
<Input disabled={!state.vehicle.new} />
</Form.Item>
<JobsCreateVehicleInfoPredefined
disabled={!state.vehicle.new}
form={form}
span={1}
/>
</LayoutFormRow>
<LayoutFormRow header={t("vehicles.forms.registration")} grow>

View File

@@ -0,0 +1,81 @@
import { PlusOutlined, SearchOutlined } from "@ant-design/icons";
import { Button, Input, Popover, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import PredefinedVehicles from "./predefined-vehicles.js";
export default function JobsCreateVehicleInfoPredefined({ disabled, form }) {
const [open, setOpen] = useState(false);
const [search, setSearch] = useState("");
const { t } = useTranslation();
const handleOpenChange = (newOpen) => {
setOpen(newOpen);
setSearch("");
};
const filteredPredefinedVehicles =
search === ""
? PredefinedVehicles
: PredefinedVehicles.filter(
(v) =>
v.make.toLowerCase().includes(search.toLowerCase()) ||
v.model.toLowerCase().includes(search.toLowerCase())
);
const popContent = () => (
<div>
<Table
size="small"
title={() => <Input.Search onSearch={(value) => setSearch(value)} />}
dataSource={filteredPredefinedVehicles}
columns={[
{
dataIndex: "make",
key: "make",
title: t("vehicles.fields.v_make_desc"),
},
{
dataIndex: "model",
key: "model",
title: t("vehicles.fields.v_model_desc"),
},
{
dataIndex: "select",
key: "select",
title: t("general.labels.actions"),
render: (value, record) => (
<Button
disabled={disabled}
onClick={() => {
form.setFieldsValue({
vehicle: {
data: {
v_make_desc: record.make,
v_model_desc: record.model,
},
},
});
setOpen(false);
setSearch("");
}}
>
<PlusOutlined />
</Button>
),
},
]}
/>
</div>
);
return (
<Popover
content={popContent}
trigger="click"
open={open}
placement="left"
onOpenChange={handleOpenChange}
destroyTooltipOnHide
>
<SearchOutlined style={{ cursor: "pointer" }} />
</Popover>
);
}

File diff suppressed because it is too large Load Diff

View File

@@ -256,7 +256,7 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
</FormRow>
<FormRow header={t("jobs.forms.other")}>
<Form.Item label={t("jobs.fields.category")} name="category">
<Select disabled={jobRO}>
<Select disabled={jobRO} allowClear>
{bodyshop.md_categories.map((s) => (
<Select.Option key={s} value={s}>
{s}
@@ -289,6 +289,12 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
>
<Input disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.lost_sale_reason")}
name="lost_sale_reason"
>
<Input disabled={jobRO} allowClear />
</Form.Item>
</FormRow>
</div>
);

View File

@@ -1,6 +1,15 @@
import { DownCircleFilled } from "@ant-design/icons";
import { useApolloClient, useMutation } from "@apollo/client";
import { Button, Dropdown, Menu, notification, Popconfirm } from "antd";
import {
Button,
Dropdown,
Form,
Menu,
notification,
Popconfirm,
Popover,
Select,
} from "antd";
import React, { useMemo } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -20,6 +29,7 @@ import AddToProduction from "./jobs-detail-header-actions.addtoproduction.util";
import JobsDetaiLheaderCsi from "./jobs-detail-header-actions.csi.component";
import DuplicateJob from "./jobs-detail-header-actions.duplicate.util";
import JobsDetailHeaderActionsExportcustdataComponent from "./jobs-detail-header-actions.exportcustdata.component";
import RbacWrapper from "../rbac-wrapper/rbac-wrapper.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -127,37 +137,66 @@ export function JobsDetailHeaderActions({
<Menu.Item
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
>
<Popconfirm
title={t("general.labels.areyousure")}
okText="Yes"
cancelText="No"
onClick={(e) => e.stopPropagation()}
<Popover
trigger="click"
disabled={job.status !== bodyshop.md_ro_statuses.default_scheduled}
onConfirm={async () => {
const jobUpdate = await cancelAllAppointments({
variables: {
jobid: job.id,
job: {
date_scheduled: null,
scheduled_in: null,
scheduled_completion: null,
status: bodyshop.md_ro_statuses.default_imported,
},
},
});
if (!jobUpdate.errors) {
notification["success"]({
message: t("appointments.successes.canceled"),
});
return;
}
}}
getPopupContainer={(trigger) => trigger.parentNode}
content={
<Form
layout="vertical"
onFinish={async ({ lost_sale_reason }) => {
const jobUpdate = await cancelAllAppointments({
variables: {
jobid: job.id,
job: {
date_scheduled: null,
scheduled_in: null,
scheduled_completion: null,
lost_sale_reason,
status: bodyshop.md_ro_statuses.default_imported,
},
},
});
if (!jobUpdate.errors) {
notification["success"]({
message: t("appointments.successes.canceled"),
});
return;
}
}}
>
<Form.Item
name="lost_sale_reason"
label={t("jobs.fields.lost_sale_reason")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Select
options={bodyshop.md_lost_sale_reasons.map((lsr) => ({
label: lsr,
value: lsr,
}))}
/>
</Form.Item>
<Button
htmlType="submit"
disabled={
job.status !== bodyshop.md_ro_statuses.default_scheduled
}
>
{t("appointments.actions.cancel")}
</Button>
</Form>
}
>
{t("menus.jobsactions.cancelallappointments")}
</Popconfirm>
</Popover>
</Menu.Item>
<Menu.Item
data-cy="job-intake-button"
disabled={
!!job.intakechecklist ||
!jobInPreProduction ||
@@ -176,7 +215,7 @@ export function JobsDetailHeaderActions({
</Link>
)}
</Menu.Item>
<Menu.Item disabled={!jobInProduction || jobRO}>
<Menu.Item data-cy="job-deliver" disabled={!jobInProduction || jobRO}>
{!jobInProduction ? (
t("jobs.actions.deliver")
) : (
@@ -185,7 +224,7 @@ export function JobsDetailHeaderActions({
</Link>
)}
</Menu.Item>
<Menu.Item disabled={!job.converted}>
<Menu.Item data-cy="job-checklist" disabled={!job.converted}>
<Link to={`/manage/jobs/${job.id}/checklist`}>
{t("jobs.actions.viewchecklist")}
</Link>
@@ -205,7 +244,9 @@ export function JobsDetailHeaderActions({
});
}}
>
{t("timetickets.actions.enter")}
<span data-cy="actions-timetickets">
{t("timetickets.actions.enter")}
</span>
</Menu.Item>
<Menu.Item
key="enterpayments"
@@ -389,7 +430,7 @@ export function JobsDetailHeaderActions({
});
}}
>
{t("jobs.labels.jobcosting")}
<span data-cy="actions-jobcosting">{t("jobs.labels.jobcosting")}</span>
</Menu.Item>
{job && !job.converted && (
<Menu.Item>
@@ -424,60 +465,62 @@ export function JobsDetailHeaderActions({
)}
<JobsDetailHeaderActionsAddevent jobid={job.id} />
{!jobRO && job.converted && (
<Menu.Item>
<Popconfirm
title={t("jobs.labels.voidjob")}
okText="Yes"
cancelText="No"
onClick={(e) => e.stopPropagation()}
onConfirm={async () => {
//delete the job.
const result = await voidJob({
variables: {
jobId: job.id,
job: {
status: bodyshop.md_ro_statuses.default_void,
voided: true,
scheduled_in: null,
scheduled_completion: null,
inproduction: false,
},
note: [
{
jobid: job.id,
created_by: currentUser.email,
audit: true,
text: t("jobs.labels.voidnote"),
<RbacWrapper action="jobs:void" noauth>
<Menu.Item>
<Popconfirm
title={t("jobs.labels.voidjob")}
okText="Yes"
cancelText="No"
onClick={(e) => e.stopPropagation()}
onConfirm={async () => {
//delete the job.
const result = await voidJob({
variables: {
jobId: job.id,
job: {
status: bodyshop.md_ro_statuses.default_void,
voided: true,
scheduled_in: null,
scheduled_completion: null,
inproduction: false,
},
],
},
});
note: [
{
jobid: job.id,
created_by: currentUser.email,
audit: true,
text: t("jobs.labels.voidnote"),
},
],
},
});
if (!!!result.errors) {
notification["success"]({
message: t("jobs.successes.voided"),
});
//go back to jobs list.
history.push(`/manage/`);
} else {
notification["error"]({
message: t("jobs.errors.voiding", {
error: JSON.stringify(result.errors),
}),
});
}
}}
getPopupContainer={(trigger) => trigger.parentNode}
>
{t("menus.jobsactions.void")}
</Popconfirm>
</Menu.Item>
if (!!!result.errors) {
notification["success"]({
message: t("jobs.successes.voided"),
});
//go back to jobs list.
history.push(`/manage/`);
} else {
notification["error"]({
message: t("jobs.errors.voiding", {
error: JSON.stringify(result.errors),
}),
});
}
}}
getPopupContainer={(trigger) => trigger.parentNode}
>
{t("menus.jobsactions.void")}
</Popconfirm>
</Menu.Item>
</RbacWrapper>
)}
</Menu>
);
return (
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus">
<Button>
<Button data-cy="job-actions-button">
<span>{t("general.labels.actions")}</span>
<DownCircleFilled />

View File

@@ -5,7 +5,7 @@ import {
BranchesOutlined,
} from "@ant-design/icons";
import { Card, Col, Row, Space, Tag, Tooltip } from "antd";
import React from "react";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
@@ -56,7 +56,7 @@ const colSpan = {
export function JobsDetailHeader({ job, bodyshop, disabled }) {
const { t } = useTranslation();
const [notesClamped, setNotesClamped] = useState(true);
const vehicleTitle = `${job.v_model_yr || ""} ${job.v_color || ""}
${job.v_make_desc || ""}
${job.v_model_desc || ""}`.trim();
@@ -229,6 +229,8 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
<DataLabel
label={t("vehicles.fields.notes")}
valueStyle={{ whiteSpace: "pre-wrap" }}
valueClassName={notesClamped ? "clamp" : ""}
onValueClick={() => setNotesClamped(!notesClamped)}
>
{job.vehicle.notes}
</DataLabel>

View File

@@ -6,3 +6,12 @@
display: flex;
flex-wrap: wrap;
}
.clamp {
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
overflow-wrap: break-word;
}

View File

@@ -5,7 +5,6 @@ import BillDetailEditcontainer from "../bill-detail-edit/bill-detail-edit.contai
import BillsListTable from "../bills-list-table/bills-list-table.component";
import JobBillsTotal from "../job-bills-total/job-bills-total.component";
import PartsOrderListTableComponent from "../parts-order-list-table/parts-order-list-table.component";
import PartsOrderModal from "../parts-order-modal/parts-order-modal.container";
export default function JobsDetailPliComponent({
job,
@@ -15,7 +14,6 @@ export default function JobsDetailPliComponent({
}) {
return (
<div>
<PartsOrderModal />
{billsQuery.error ? (
<AlertComponent message={billsQuery.error.message} type="error" />
) : null}

View File

@@ -40,24 +40,26 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
>
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<Tooltip title={t("jobs.labels.ca_gst_all_if_null")}>
<Form.Item
label={t("jobs.fields.ca_customer_gst")}
name="ca_customer_gst"
>
<CurrencyInput
disabled={jobRO}
min={0}
max={
Math.round(
(job.job_totals &&
job.job_totals.totals.federal_tax.amount) ||
0
) / 100
}
/>
</Form.Item>
</Tooltip>
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Tooltip title={t("jobs.labels.ca_gst_all_if_null")}>
<Form.Item
label={t("jobs.fields.ca_customer_gst")}
name="ca_customer_gst"
>
<CurrencyInput
disabled={jobRO}
min={0}
max={
Math.round(
(job.job_totals &&
job.job_totals.totals.federal_tax.amount) ||
0
) / 100
}
/>
</Form.Item>
</Tooltip>
)}
<Form.Item
label={t("jobs.fields.other_amount_payable")}
name="other_amount_payable"
@@ -82,12 +84,14 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
>
<CurrencyInput disabled={jobRO || bodyshop.cdk_dealerid} />
</Form.Item>
<Space align="end">
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<CABCpvrtCalculator form={form} disabled={jobRO} />
</Space>
{bodyshop.region_config === "CA_BC" && (
<Space align="center">
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
<CurrencyInput disabled={jobRO} min={0} />
</Form.Item>
<CABCpvrtCalculator form={form} disabled={jobRO} />
</Space>
)}
<Form.Item
label={t("jobs.fields.auto_add_ats")}
name="auto_add_ats"
@@ -141,13 +145,15 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
>
<InputNumber min={0} max={1} precision={2} disabled={jobRO} />
</Form.Item>
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
{bodyshop.region_config.toLowerCase().startsWith("ca") && (
<Form.Item
label={t("jobs.fields.ca_gst_registrant")}
name="ca_gst_registrant"
valuePropName="checked"
>
<Switch disabled={jobRO} />
</Form.Item>
)}
</FormRow>
<Divider
orientation="left"

View File

@@ -5,7 +5,7 @@ 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 yauzl from "yauzl";
import { useTreatments } from "@splitsoftware/splitio-react";
import { connect } from "react-redux";
@@ -69,44 +69,44 @@ export function JobsDocumentsDownloadButton({
setDownload(null);
if (Direct_Media_Download.treatment === "on") {
try {
const parentDir = await window.showDirectoryPicker({
id: "media",
startIn: "downloads",
});
// const parentDir = await window.showDirectoryPicker({
// id: "media",
// startIn: "downloads",
// });
const directory = await parentDir.getDirectoryHandle(identifier, {
create: true,
});
// 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();
});
});
});
}
);
// 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);

View File

@@ -1,7 +1,7 @@
import { EditFilled, FileExcelFilled, SyncOutlined } from "@ant-design/icons";
import { Button, Card, Col, Row, Space } from "antd";
import React, { useEffect, useState } from "react";
import Gallery from "react-grid-gallery";
import { Gallery } from "react-grid-gallery";
import { useTranslation } from "react-i18next";
import DocumentsUploadComponent from "../documents-upload/documents-upload.component";
import { DetermineFileType } from "../documents-upload/documents-upload.utility";
@@ -11,6 +11,9 @@ import JobsDocumentsGalleryReassign from "./jobs-document-gallery.reassign.compo
import JobsDocumentsDeleteButton from "./jobs-documents-gallery.delete.component";
import JobsDocumentsGallerySelectAllComponent from "./jobs-documents-gallery.selectall.component";
import Lightbox from "react-image-lightbox";
import "react-image-lightbox/style.css";
function JobsDocumentsComponent({
data,
jobId,
@@ -23,11 +26,7 @@ function JobsDocumentsComponent({
}) {
const [galleryImages, setgalleryImages] = useState({ images: [], other: [] });
const { t } = useTranslation();
const [index, setIndex] = useState(0);
const onCurrentImageChange = (index) => {
setIndex(index);
};
const [modalState, setModalState] = useState({ open: false, index: 0 });
useEffect(() => {
let documents = data.reduce(
@@ -35,14 +34,16 @@ function JobsDocumentsComponent({
const fileType = DetermineFileType(value.type);
if (value.type.startsWith("image")) {
acc.images.push({
src: GenerateSrcUrl(value),
thumbnail: GenerateThumbUrl(value),
thumbnailHeight: 225,
thumbnailWidth: 225,
// src: GenerateSrcUrl(value),
src: GenerateThumbUrl(value),
// src: GenerateSrcUrl(value),
// thumbnail: GenerateThumbUrl(value),
fullsize: GenerateSrcUrl(value),
height: 225,
width: 225,
isSelected: false,
key: value.key,
extension: value.extension,
id: value.id,
type: value.type,
size: value.size,
@@ -62,7 +63,7 @@ function JobsDocumentsComponent({
const fileName = value.key.split("/").pop();
acc.other.push({
source: GenerateSrcUrl(value),
src: "",
src: thumb,
thumbnail: thumb,
tags: [
{
@@ -85,10 +86,9 @@ function JobsDocumentsComponent({
]
: []),
],
thumbnailHeight: 225,
thumbnailWidth: 225,
height: 225,
width: 225,
isSelected: false,
extension: value.extension,
key: value.key,
id: value.id,
@@ -148,35 +148,15 @@ function JobsDocumentsComponent({
<Card title={t("jobs.labels.documents-images")}>
<Gallery
images={galleryImages.images}
backdropClosesModal={true}
currentImageWillChange={onCurrentImageChange}
customControls={[
<Button
key="edit-button"
style={{
float: "right",
zIndex: "5",
}}
onClick={() => {
const newWindow = window.open(
`${window.location.protocol}//${window.location.host}/edit?documentId=${galleryImages.images[index].id}`,
"_blank",
"noopener,noreferrer"
);
if (newWindow) newWindow.opener = null;
}}
>
<EditFilled />
</Button>,
]}
onClickImage={(props) => {
window.open(
props.target.src,
"_blank",
"toolbar=0,location=0,menubar=0"
);
onClick={(index, item) => {
setModalState({ open: true, index: index });
// window.open(
// item.fullsize,
// "_blank",
// "toolbar=0,location=0,menubar=0"
// );
}}
onSelectImage={(index, image) => {
onSelect={(index, image) => {
setgalleryImages({
...galleryImages,
images: galleryImages.images.map((g, idx) =>
@@ -191,8 +171,6 @@ function JobsDocumentsComponent({
<Card title={t("jobs.labels.documents-other")}>
<Gallery
images={galleryImages.other}
backdropClosesModal={true}
enableLightbox={false}
thumbnailStyle={() => {
return {
backgroundImage: <FileExcelFilled />,
@@ -201,14 +179,14 @@ function JobsDocumentsComponent({
cursor: "pointer",
};
}}
onClickThumbnail={(index) => {
onClick={(index) => {
window.open(
galleryImages.other[index].source,
"_blank",
"toolbar=0,location=0,menubar=0"
);
}}
onSelectImage={(index) => {
onSelect={(index) => {
setgalleryImages({
...galleryImages,
other: galleryImages.other.map((g, idx) =>
@@ -219,6 +197,53 @@ function JobsDocumentsComponent({
/>
</Card>
</Col>
{modalState.open && (
<Lightbox
toolbarButtons={[
<EditFilled
onClick={() => {
const newWindow = window.open(
`${window.location.protocol}//${
window.location.host
}/edit?documentId=${
galleryImages.images[modalState.index].id
}`,
"_blank",
"noopener,noreferrer"
);
if (newWindow) newWindow.opener = null;
}}
/>,
]}
mainSrc={galleryImages.images[modalState.index].fullsize}
nextSrc={
galleryImages.images[
(modalState.index + 1) % galleryImages.images.length
].fullsize
}
prevSrc={
galleryImages.images[
(modalState.index + galleryImages.images.length - 1) %
galleryImages.images.length
].fullsize
}
onCloseRequest={() => setModalState({ open: false, index: 0 })}
onMovePrevRequest={() =>
setModalState({
...modalState,
index:
(modalState.index + galleryImages.images.length - 1) %
galleryImages.images.length,
})
}
onMoveNextRequest={() =>
setModalState({
...modalState,
index: (modalState.index + 1) % galleryImages.images.length,
})
}
/>
)}
</Row>
</div>
);

View File

@@ -1,7 +1,7 @@
import React, { useEffect } from "react";
import Gallery from "react-grid-gallery";
import { Gallery } from "react-grid-gallery";
import { useTranslation } from "react-i18next";
import { GenerateSrcUrl, GenerateThumbUrl } from "./job-documents.utility";
import { GenerateThumbUrl } from "./job-documents.utility";
function JobsDocumentGalleryExternal({
data,
@@ -15,8 +15,8 @@ function JobsDocumentGalleryExternal({
let documents = data.reduce((acc, value) => {
if (value.type.startsWith("image")) {
acc.push({
src: GenerateSrcUrl(value),
thumbnail: GenerateThumbUrl(value),
//src: GenerateSrcUrl(value),
src: GenerateThumbUrl(value),
thumbnailHeight: 225,
thumbnailWidth: 225,
isSelected: false,
@@ -39,7 +39,7 @@ function JobsDocumentGalleryExternal({
<Gallery
images={galleryImages}
backdropClosesModal={true}
onSelectImage={(index, image) => {
onSelect={(index, image) => {
setgalleryImages(
galleryImages.map((g, idx) =>
index === idx ? { ...g, isSelected: !g.isSelected } : g

View File

@@ -1,7 +1,7 @@
import { SyncOutlined, FileExcelFilled } from "@ant-design/icons";
import { Alert, Button, Card, Space } from "antd";
import React, { useEffect } from "react";
import Gallery from "react-grid-gallery";
import React, { useEffect, useState } from "react";
import { Gallery } from "react-grid-gallery";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -19,6 +19,9 @@ import JobsLocalGalleryDownloadButton from "./jobs-documents-local-gallery.downl
import JobsDocumentsLocalGalleryReassign from "./jobs-documents-local-gallery.reassign.component";
import JobsDocumentsLocalGallerySelectAllComponent from "./jobs-documents-local-gallery.selectall.component";
import Lightbox from "react-image-lightbox";
import "react-image-lightbox/style.css";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
allMedia: selectAllMedia,
@@ -49,6 +52,7 @@ export function JobsDocumentsLocalGallery({
vendorid,
}) {
const { t } = useTranslation();
const [modalState, setModalState] = useState({ open: false, index: 0 });
useEffect(() => {
if (job) {
if (invoice_number) {
@@ -70,12 +74,20 @@ export function JobsDocumentsLocalGallery({
) {
acc.images.push({
...val,
fullsize: val.src,
src: val.thumbnail,
height: val.thumbnailHeight,
width: val.thumbnailWidth,
...(val.optimized && { src: val.optimized, fullsize: val.src }),
});
if (val.optimized) optimized = true;
} else {
acc.other.push({
...val,
fullsize: val.src,
src: val.thumbnail,
height: val.thumbnailHeight,
width: val.thumbnailWidth,
tags: [{ value: val.filename, title: val.filename }],
});
}
@@ -120,8 +132,7 @@ export function JobsDocumentsLocalGallery({
<Card title={t("jobs.labels.documents-images")}>
<Gallery
images={jobMedia.images}
backdropClosesModal={true}
onSelectImage={(index, image) => {
onSelect={(index, image) => {
toggleMediaSelected({ jobid: job.id, filename: image.filename });
}}
{...(optimized && {
@@ -133,24 +144,23 @@ export function JobsDocumentsLocalGallery({
/>,
],
})}
onClickImage={(props) => {
const media = allMedia[job.id].find(
(m) => m.optimized === props.target.src
);
onClick={(index) => {
setModalState({ open: true, index: index });
// const media = allMedia[job.id].find(
// (m) => m.optimized === item.src
// );
window.open(
media ? media.src : props.target.src,
"_blank",
"toolbar=0,location=0,menubar=0"
);
// window.open(
// media ? media.fullsize : item.fullsize,
// "_blank",
// "toolbar=0,location=0,menubar=0"
// );
}}
/>
</Card>
<Card title={t("jobs.labels.documents-other")}>
<Gallery
images={jobMedia.other}
backdropClosesModal={true}
enableLightbox={false}
thumbnailStyle={() => {
return {
backgroundImage: <FileExcelFilled />,
@@ -159,18 +169,48 @@ export function JobsDocumentsLocalGallery({
cursor: "pointer",
};
}}
onClickThumbnail={(index) => {
onClick={(index) => {
window.open(
jobMedia.other[index].src,
jobMedia.other[index].fullsize,
"_blank",
"toolbar=0,location=0,menubar=0"
);
}}
onSelectImage={(index, image) => {
onSelect={(index, image) => {
toggleMediaSelected({ jobid: job.id, filename: image.filename });
}}
/>
</Card>
{modalState.open && (
<Lightbox
mainSrc={jobMedia.images[modalState.index].fullsize}
nextSrc={
jobMedia.images[(modalState.index + 1) % jobMedia.images.length]
.fullsize
}
prevSrc={
jobMedia.images[
(modalState.index + jobMedia.images.length - 1) %
jobMedia.images.length
].fullsize
}
onCloseRequest={() => setModalState({ open: false, index: 0 })}
onMovePrevRequest={() =>
setModalState({
...modalState,
index:
(modalState.index + jobMedia.images.length - 1) %
jobMedia.images.length,
})
}
onMoveNextRequest={() =>
setModalState({
...modalState,
index: (modalState.index + 1) % jobMedia.images.length,
})
}
/>
)}
</div>
);
}

View File

@@ -1,5 +1,5 @@
import React, { useEffect } from "react";
import Gallery from "react-grid-gallery";
import { Gallery } from "react-grid-gallery";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
@@ -38,7 +38,7 @@ function JobDocumentsLocalGalleryExternal({
const { t } = useTranslation();
useEffect(() => {
if ( jobId) {
if (jobId) {
getJobMedia(jobId);
}
}, [jobId, getJobMedia]);
@@ -52,11 +52,15 @@ function JobDocumentsLocalGalleryExternal({
val.type.mime &&
val.type.mime.startsWith("image")
) {
acc.push(val);
acc.push({ ...val, src: val.thumbnail });
}
return acc;
}, [])
: [];
console.log(
"🚀 ~ file: jobs-documents-local-gallery.external.component.jsx:48 ~ useEffect ~ documents:",
documents
);
setgalleryImages(documents);
}, [allMedia, jobId, setgalleryImages, t]);
@@ -65,8 +69,7 @@ function JobDocumentsLocalGalleryExternal({
<div className="clearfix">
<Gallery
images={galleryImages}
backdropClosesModal={true}
onSelectImage={(index, image) => {
onSelect={(index, image) => {
setgalleryImages(
galleryImages.map((g, idx) =>
index === idx ? { ...g, isSelected: !g.isSelected } : g

View File

@@ -182,7 +182,7 @@ export function JobsExportAllButton({
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")}
{t("jobs.actions.exportselected")}
</Button>
);
}

View File

@@ -167,6 +167,7 @@ export default function JobsFindModalComponent({
/>
</div>
)}
data-cy="existing-jobs-table"
pagination={{ position: "bottom" }}
columns={columns}
rowKey="id"
@@ -189,6 +190,7 @@ export default function JobsFindModalComponent({
/>
<Divider />
<Checkbox
data-cy="override-header-checkbox"
defaultChecked={importOptions.overrideHeader}
onChange={(e) =>
setImportOptions({

View File

@@ -77,7 +77,10 @@ export default connect(
title={t("jobs.labels.existing_jobs")}
width={"80%"}
destroyOnClose
okButtonProps={{ disabled: selectedJob ? false : true }}
okButtonProps={{
disabled: selectedJob ? false : true,
"data-cy": "existing-jobs-ok-button",
}}
{...modalProps}
>
{loading ? <LoadingSpinner /> : null}

View File

@@ -1,8 +1,9 @@
import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Input, Space, Table, Typography } from "antd";
import axios from "axios";
import _ from "lodash";
import queryString from "query-string";
import React from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useHistory, useLocation } from "react-router-dom";
@@ -21,6 +22,8 @@ const mapDispatchToProps = (dispatch) => ({
export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
const search = queryString.parse(useLocation().search);
const [openSearchResults, setOpenSearchResults] = useState([]);
const [searchLoading, setSearchLoading] = useState(false);
const { page, sortcolumn, sortorder } = search;
const history = useHistory();
@@ -193,6 +196,28 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
history.push({ search: queryString.stringify(search) });
};
useEffect(() => {
if (search.search && search.search.trim() !== "") {
searchJobs();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
async function searchJobs(value) {
try {
setSearchLoading(true);
const searchData = await axios.post("/search", {
search: value || search.search,
index: "jobs",
});
setOpenSearchResults(searchData.data.hits.hits.map((s) => s._source));
} catch (error) {
console.log("Error while fetching search results", error);
} finally {
setSearchLoading(false);
}
}
return (
<Card
extra={
@@ -205,6 +230,7 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
<Button
onClick={() => {
delete search.search;
delete search.page;
history.push({ search: queryString.stringify(search) });
}}
>
@@ -220,24 +246,32 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
onSearch={(value) => {
search.search = value;
history.push({ search: queryString.stringify(search) });
searchJobs(value);
}}
loading={loading || searchLoading}
enterButton
/>
</Space>
}
>
<Table
loading={loading}
pagination={{
position: "top",
pageSize: 25,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}}
loading={loading || searchLoading}
pagination={
search?.search
? {
pageSize: 25,
showSizeChanger: false,
}
: {
pageSize: 25,
current: parseInt(page || 1),
total: total,
showSizeChanger: false,
}
}
columns={columns}
rowKey="id"
dataSource={jobs}
dataSource={search?.search ? openSearchResults : jobs}
onChange={handleTableChange}
/>
</Card>

View File

@@ -112,7 +112,9 @@ export function JobsList({ bodyshop }) {
title: t("jobs.fields.ro_number"),
dataIndex: "ro_number",
key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sorter: (a, b) =>
parseInt((a.ro_number || "0").replace(/\D/g, "")) -
parseInt((b.ro_number || "0").replace(/\D/g, "")),
sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
@@ -120,6 +122,7 @@ export function JobsList({ bodyshop }) {
<Link
to={"/manage/jobs/" + record.id}
onClick={(e) => e.stopPropagation()}
data-cy="active-job-link"
>
<Space>
{record.ro_number || t("general.labels.na")}
@@ -260,6 +263,19 @@ export function JobsList({ bodyshop }) {
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true,
filters:
(jobs &&
jobs
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s,
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"],
},
{
@@ -349,6 +365,7 @@ export function JobsList({ bodyshop }) {
}
>
<Table
data-cy="active-jobs-table"
loading={loading}
pagination={{ defaultPageSize: 50 }}
columns={columns}

View File

@@ -75,6 +75,27 @@ export function JobNotesComponent({
</span>
),
},
{
title: t("notes.fields.type"),
dataIndex: "type",
key: "type",
width: 120,
filteredValue: filter?.type || null,
filters: [
{ value: "general", text: t("notes.fields.types.general") },
{ value: "customer", text: t("notes.fields.types.customer") },
{ value: "shop", text: t("notes.fields.types.shop") },
{ value: "office", text: t("notes.fields.types.office") },
{ value: "parts", text: t("notes.fields.types.parts") },
{ value: "paint", text: t("notes.fields.types.paint") },
{
value: "supplement",
text: t("notes.fields.types.supplement"),
},
],
onFilter: (value, record) => value.includes(record.type),
render: (text, record) => t(`notes.fields.types.${record.type}`),
},
{
title: t("notes.fields.text"),
dataIndex: "text",
@@ -106,7 +127,7 @@ export function JobNotesComponent({
title: t("notes.actions.actions"),
dataIndex: "actions",
key: "actions",
width: 150,
width: 200,
render: (text, record) => (
<Space wrap>
<Button

View File

@@ -272,6 +272,19 @@ export function JobsReadyList({ bodyshop }) {
dataIndex: "ins_co_nm",
key: "ins_co_nm",
ellipsis: true,
filters:
(jobs &&
jobs
.map((j) => j.ins_co_nm)
.filter(onlyUnique)
.map((s) => {
return {
text: s,
value: [s],
};
})) ||
[],
onFilter: (value, record) => value.includes(record.ins_co_nm),
responsive: ["md"],
},
{

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