Compare commits

...

555 Commits

Author SHA1 Message Date
Patrick Fic
345fc48c17 Cypress package files. 2022-12-23 07:36:10 -08:00
Patrick Fic
b8fe566030 Cypress reconfiguration. 2022-12-22 15:18:29 -08:00
Patrick Fic
bee078fe18 Add user definted rates to labor misc for autohouse. 2022-12-20 12:38:41 -08:00
Patrick Fic
8a0281fb43 Merged in release/2022-12-16 (pull request #643)
Release/2022 12 16
2022-12-16 18:59:41 +00:00
Patrick Fic
1df023fd15 Remove unneeded import to fix CI. 2022-12-15 17:09:05 -08:00
Patrick Fic
54277e4548 IO-2131 Resolve job search select filtering issue. 2022-12-15 16:39:47 -08:00
Patrick Fic
cffa1e2172 IO-2121 Add custom label to conversation. 2022-12-15 10:13:30 -08:00
Patrick Fic
6a25dff32d Merged in release/2022-12-16 (pull request #639)
Resolve scheduling loading issue.
2022-12-14 16:59:04 +00:00
Patrick Fic
ecfc365926 Resolve scheduling loading issue. 2022-12-14 08:30:16 -08:00
Patrick Fic
2f0bb4539e Merged in release/2022-12-09 (pull request #638)
Release/2022 12 09
2022-12-10 01:32:06 +00:00
Patrick Fic
2ac7f9b678 Add ownr_co_nm to search own query. 2022-12-09 14:07:27 -08:00
Patrick Fic
e192a63575 IO-2123 Add cc inventory print. 2022-12-09 11:35:13 -08:00
Patrick Fic
4c42522f3a IO-2122 Add time ticket report. 2022-12-09 08:09:53 -08:00
Patrick Fic
dad3dc9e42 Autohouse based updates. 2022-12-08 14:47:51 -08:00
Patrick Fic
b2b754bee0 IO-2118 Add expected production hours to scheduling. 2022-12-05 15:20:37 -08:00
Patrick Fic
8f9f80e8ee IO-2084 Add actualr epair start date. 2022-12-05 14:38:36 -08:00
Patrick Fic
0cf677abbf Merged in release/2022-12-02 (pull request #635)
IO-2116 Add account number to control types for DMS.
2022-12-03 03:16:53 +00:00
Patrick Fic
bc3238aba2 IO-2116 Add account number to control types for DMS. 2022-11-30 08:47:20 -08:00
Patrick Fic
df8c94cda2 Merged in release/2022-11-25 (pull request #632)
IO-2114 Resolve issue with unfound payer.
2022-11-26 00:01:25 +00:00
Patrick Fic
6a2fefc573 IO-2114 Resolve issue with unfound payer. 2022-11-25 15:59:55 -08:00
Patrick Fic
e1dc257279 Merged in release/2022-11-25 (pull request #631)
Release/2022 11 25
2022-11-25 20:57:01 +00:00
Patrick Fic
94e8dc7456 IO-2106 Add payment Ref to QBO export. 2022-11-25 08:40:40 -08:00
Patrick Fic
d6c6e5245d IO-2114 Resolve missing translation on DMS form when no control tpye set. 2022-11-25 08:27:56 -08:00
Patrick Fic
a64f9d8213 IO-2112 Update size of note preset field. 2022-11-25 08:23:02 -08:00
Patrick Fic
e8c727a393 Resolve bounced email tracking. 2022-11-23 14:31:25 -08:00
Patrick Fic
37cef75fef IO-2098 Fix vin search bug on job import. 2022-11-23 12:27:11 -08:00
Patrick Fic
7301d9da85 IO-2098 Remove page size on all jobs. 2022-11-23 11:05:27 -08:00
Patrick Fic
beb7daec5f Remove packages no longer controled by this repo. 2022-11-23 10:53:39 -08:00
Patrick Fic
311da0c028 Merged in revert-pr-628 (pull request #629)
Revert "Release/2022 11 25 (pull request #628)"
2022-11-22 01:31:33 +00:00
Patrick Fic
f9a5e8485b Revert "Release/2022 11 25 (pull request #628)" 2022-11-22 01:31:09 +00:00
Patrick Fic
0fdb663d50 Merged in release/2022-11-25 (pull request #628)
Release/2022 11 25
2022-11-21 23:52:30 +00:00
Patrick Fic
d528a4b730 Resynced lock files. 2022-11-21 15:50:34 -08:00
Patrick Fic
e4692c2965 Remove uneeded package. 2022-11-21 15:00:48 -08:00
Patrick Fic
a600bd446c Merged in release/2022-11-10 (pull request #625)
Release/2022 11 10
2022-11-10 20:04:14 +00:00
Patrick Fic
9f28b80a5a IO-1532 Status Transition Tracking. 2022-11-10 09:42:03 -08:00
Patrick Fic
77e865c5c4 CI Test 2022-11-09 16:34:30 -08:00
Patrick Fic
d01a1aa0a0 CI Test 2022-11-09 16:29:32 -08:00
Patrick Fic
77bfa90b59 IO-2062 Update handling of negative EFT amounts for ICBC. 2022-11-09 09:28:56 -08:00
Patrick Fic
b8d3c5e36c IO-2097 Update query on job card details to include remove = true filter. 2022-11-09 09:18:20 -08:00
Patrick Fic
abac60a316 IO-2100 Update label for updated date range picker. 2022-11-09 08:59:51 -08:00
Patrick Fic
83626d83de IO-2100 Add last 3 months to date range picker. 2022-11-09 08:16:44 -08:00
Patrick Fic
928673691b Updated header to remove language selector. 2022-11-08 14:22:03 -08:00
Patrick Fic
818e0679d9 Update AP posting form validation. 2022-11-08 13:36:54 -08:00
Patrick Fic
18f56a93d0 Merged in circleci-editor/847/test (pull request #620)
Add .circleci/config.yml
2022-11-08 19:08:56 +00:00
Patrick Fic
d4a6c528d0 Add .circleci/config.yml 2022-11-08 19:06:22 +00:00
Patrick Fic
6410f868db Merged in release/2022-11-10 (pull request #619)
release/2022-11-10

Approved-by: Patrick Fic
2022-11-08 18:51:55 +00:00
Patrick Fic
058cd31784 CI file updates. 2022-11-08 10:51:20 -08:00
Patrick Fic
9a04c63f72 Merged in release/2022-11-10 (pull request #618)
release/2022-11-10

Approved-by: Patrick Fic
2022-11-08 18:35:58 +00:00
Patrick Fic
3fa38822f3 IO-2096 Fix grouping for CT report. 2022-11-08 10:35:26 -08:00
Patrick Fic
8d84ed8e00 Updated CI 2022-11-08 10:13:04 -08:00
Patrick Fic
60fa40f738 Merged in release/2022-11-10 (pull request #617)
Updated git ignore for updated CI.

Approved-by: Patrick Fic
2022-11-08 18:10:27 +00:00
Patrick Fic
22d075c89a Updated git ignore for updated CI. 2022-11-08 10:08:58 -08:00
Patrick Fic
ac1eead695 Merged in release/2022-11-10 (pull request #616)
release/2022-11-10

Approved-by: Patrick Fic
2022-11-08 18:02:10 +00:00
Patrick Fic
5a12738da5 IO-2096 Add Cycle Time Analysis report 2022-11-08 10:01:40 -08:00
Patrick Fic
480cdb9b28 updated GIT and circle CI messages. 2022-11-07 15:58:30 -08:00
Patrick Fic
f389dfdd94 Merged in release/2022-11-10 (pull request #615)
PBS AP: skip posting of bills/wip for AR
2022-11-07 22:23:14 +00:00
Patrick Fic
fb20cceebc Merged in release/2022-11-10 (pull request #614)
PBS AP: skip posting of bills/wip for AR
2022-11-07 22:05:40 +00:00
Patrick Fic
b549f45cde PBS AP: skip posting of bills/wip for AR 2022-11-07 13:24:54 -08:00
Patrick Fic
a4fc26c139 Merged in release/2022-11-04 (pull request #613)
Release/2022 11 04
2022-11-04 17:32:17 +00:00
Patrick Fic
8f9cecd6bf Merged in release/2022-11-04 (pull request #612)
Add title to email overlay to prevent close button overlap.
2022-11-04 17:31:07 +00:00
Patrick Fic
6c5d684218 Add title to email overlay to prevent close button overlap. 2022-11-03 16:37:09 -07:00
Patrick Fic
ee05f59bd3 Merged in release/2022-11-04 (pull request #611)
release/2022-11-04

Approved-by: Patrick Fic
2022-11-03 18:45:28 +00:00
Patrick Fic
a62796b12f ARMS Update 2022-11-03 11:44:37 -07:00
Patrick Fic
3a5508fc95 Merged in release/2022-11-04 (pull request #610)
Release/2022 11 04
2022-11-03 17:15:37 +00:00
Patrick Fic
6a49457382 CI fix. 2022-11-03 10:12:46 -07:00
Patrick Fic
24d71413c1 Revert "IO-2086 Use new grid gallery and lightbox for performance."
This reverts commit 089fc0b0f7.
2022-11-03 10:12:15 -07:00
Patrick Fic
00ae2cda5d Merged in release/2022-11-04 (pull request #609)
Release/2022 11 04
2022-11-03 17:02:46 +00:00
Patrick Fic
3354afe380 Merge branch 'feature/pbs-ap' into release/2022-11-04
* feature/pbs-ap:
  Minor bug fixes.
  Add notification of complete export.
  AP changes based on validation testing.
  WIP PBS AP.
  WIP PBS AP.
2022-11-03 10:01:39 -07:00
Patrick Fic
d0bd2aeaba Minor bug fixes. 2022-11-03 10:01:22 -07:00
Patrick Fic
b3ed9106f0 Add notification of complete export. 2022-11-03 08:25:44 -07:00
Patrick Fic
6354ccee87 AP changes based on validation testing. 2022-11-01 14:26:13 -07:00
Patrick Fic
089fc0b0f7 IO-2086 Use new grid gallery and lightbox for performance. 2022-11-01 09:04:41 -07:00
Patrick Fic
ede1cdb89b WIP PBS AP. 2022-10-31 15:59:00 -07:00
Patrick Fic
cd96de3a96 Merge branch 'release/2022-10-31' into feature/pbs-ap
* release/2022-10-31:
  Improved LR activiation.
  Improved LR tracking.
2022-10-31 11:34:56 -07:00
Patrick Fic
33dea8638c Merged in release/2022-10-31 (pull request #608)
Improved LR tracking.
2022-10-31 18:31:03 +00:00
Patrick Fic
790fd4611f Improved LR activiation. 2022-10-31 11:30:45 -07:00
Patrick Fic
c72fdb81af Improved LR tracking. 2022-10-31 11:27:33 -07:00
Patrick Fic
8d5202f46d WIP PBS AP. 2022-10-31 10:42:42 -07:00
Patrick Fic
7d81898a45 Merged in release/2022-10-28 (pull request #607)
Release/2022 10 28
2022-10-31 14:18:40 +00:00
Patrick Fic
821bc850db Merged in release/2022-10-28 (pull request #606)
Release/2022 10 28
2022-10-27 18:15:08 +00:00
Patrick Fic
107265c2fc IO-223 Remove LA1 from ARMS. 2022-10-27 11:11:29 -07:00
Patrick Fic
d6d75cacd8 Merged in release/2022-10-27 (pull request #605)
Resolve PBS query error.
2022-10-27 15:16:27 +00:00
Patrick Fic
bfefc99434 Resolve PBS query error. 2022-10-27 08:15:41 -07:00
Patrick Fic
c26cc9e908 Resolve PBS query error. 2022-10-27 08:13:57 -07:00
Patrick Fic
3e8af5fec7 Merged in release/2022-10-28 (pull request #604)
release/2022-10-28

Approved-by: Patrick Fic
2022-10-26 16:27:23 +00:00
Patrick Fic
21d05553ff Add ARMS logging. 2022-10-26 09:26:39 -07:00
Patrick Fic
fdcd97d195 Merged in release/2022-10-28 (pull request #603)
release/2022-10-28

Approved-by: Patrick Fic
2022-10-24 21:02:19 +00:00
Patrick Fic
15bf1937d3 Remove logging statement. 2022-10-24 14:01:37 -07:00
Patrick Fic
07dce11341 Merged in release/2022-10-28 (pull request #602)
release/2022-10-28

Approved-by: Patrick Fic
2022-10-24 20:50:20 +00:00
Patrick Fic
41b32a25d7 IO-2086 LMS Optimized imges bugfix. 2022-10-24 13:49:57 -07:00
Patrick Fic
ab242955f1 Merged in release/2022-10-28 (pull request #601)
IO-2086 Improved local media image display.
2022-10-24 19:52:10 +00:00
Patrick Fic
dc0d8526a3 IO-2086 Improved local media image display. 2022-10-24 12:50:52 -07:00
Patrick Fic
84fcca239a Merged in release/2022-10-21 (pull request #600)
Release/2022 10 21
2022-10-19 22:51:50 +00:00
Patrick Fic
7039eff354 Minimum bill quantity set to 1. 2022-10-19 15:20:06 -07:00
Patrick Fic
2c1bd448e7 Merged in release/2022-10-21 (pull request #599)
Release/2022 10 21
2022-10-18 17:46:18 +00:00
Patrick Fic
05bfd217be IO-2070 Credit memos no longer update line status. 2022-10-18 10:39:58 -07:00
Patrick Fic
c83ff03ae8 IO-2038 Resolve duplicate shift clock login. 2022-10-18 09:51:55 -07:00
Patrick Fic
b363b2f7e5 Resolve CI issue. 2022-10-18 09:22:21 -07:00
Patrick Fic
d14a3f77f1 IO-2082 Closing $0 RO. 2022-10-18 09:19:58 -07:00
Patrick Fic
04f4ce97ed IO-2059 Add invoice history to job line expander. 2022-10-18 09:00:21 -07:00
Patrick Fic
dc21c25c95 Merged in release/2022-10-21 (pull request #598)
IO-2072 Update template for parts label.
2022-10-18 00:40:49 +00:00
Patrick Fic
1d39e574cf IO-2072 Update template for parts label. 2022-10-17 17:30:30 -07:00
Patrick Fic
24c62f4905 Merged in release/2022-10-21 (pull request #597)
Release/2022 10 21
2022-10-18 00:07:33 +00:00
Patrick Fic
ac6903edcb IO-2072 add parts label to bill posting screen. 2022-10-17 16:58:03 -07:00
Patrick Fic
0129868bb0 IO-2058 Add RO/Name to resent parts order. 2022-10-17 16:30:45 -07:00
Patrick Fic
1012d4acda IO-2066 Update item used for adjustments. 2022-10-17 16:21:44 -07:00
Patrick Fic
2b5e8f9a81 IO-2013 Add IOU flag to job header in certain areas. 2022-10-17 16:07:16 -07:00
Patrick Fic
dbc83aab00 Merged in release/2022-10-14 (pull request #596)
Release/2022 10 14
2022-10-14 18:49:16 +00:00
Patrick Fic
f23868bbde Merged in release/2022-10-14 (pull request #595)
release/2022-10-14

Approved-by: Patrick Fic
2022-10-14 15:47:18 +00:00
Patrick Fic
ebcf27feea IO-2074 updated labels for reports. 2022-10-14 08:46:51 -07:00
Patrick Fic
7d84dcacee Merged in release/2022-10-14 (pull request #594)
release/2022-10-14

Approved-by: Patrick Fic
2022-10-13 20:22:18 +00:00
Patrick Fic
c391b7d90b IO-2067 IO-2074 Add new reports. 2022-10-13 13:21:53 -07:00
Patrick Fic
41b505748c Merged in release/2022-10-03 (pull request #593)
IO-2054 Resolve issue for multi AR.
2022-10-03 18:01:21 +00:00
Patrick Fic
54feb0b541 IO-2054 Resolve issue for multi AR. 2022-10-03 08:00:29 -10:00
Patrick Fic
2b36bcc56b Merged in release/2022-09-30 (pull request #592)
Release/2022 09 30
2022-09-28 23:26:19 +00:00
Patrick Fic
6aeeac15b6 Merged in release/2022-09-30 (pull request #591)
IO-223 Use NA for owner company name.
2022-09-28 19:45:32 +00:00
Patrick Fic
d699e49369 IO-223 Use NA for owner company name. 2022-09-28 12:44:09 -07:00
Patrick Fic
e69a218392 Merged in release/2022-09-30 (pull request #590)
Release/2022 09 30
2022-09-28 19:07:50 +00:00
Patrick Fic
1105431909 IO-2054 Improved spacing for QB Multi Payer 2022-09-28 12:07:27 -07:00
Patrick Fic
1c222db5a3 IO-2051 Mark appointment as arrived from checklist. 2022-09-28 11:55:46 -07:00
Patrick Fic
f6b72ab428 IO-2065 Resolve parts status filtering. 2022-09-28 11:47:48 -07:00
Patrick Fic
045a3660a3 IO-2054 Updated labels for multi AR. 2022-09-28 11:30:11 -07:00
Patrick Fic
477c7bb006 Merged in release/2022-09-30 (pull request #589)
IO-2054 Bug fix for payments.
2022-09-28 00:08:15 +00:00
Patrick Fic
45fca7206b IO-2054 Bug fix for payments. 2022-09-27 17:07:42 -07:00
Patrick Fic
0f75ca555d Merged in release/2022-09-30 (pull request #588)
Release/2022 09 30
2022-09-27 22:25:54 +00:00
Patrick Fic
e8fc29ea61 Autohouse Updates. 2022-09-27 15:07:26 -07:00
Patrick Fic
918bc402f6 IO-2054 Add Payments Export 2022-09-27 15:07:15 -07:00
Patrick Fic
e9c83e567a Merged in release/2022-09-30 (pull request #587)
Release/2022 09 30
2022-09-27 19:20:01 +00:00
Patrick Fic
c154e7be2e IO-223 ARMS bug fixes. 2022-09-27 08:41:45 -07:00
Patrick Fic
1f8edf764d IO-2054 Update responsibility center page. 2022-09-26 14:40:10 -07:00
Patrick Fic
acd3f545b3 IO-2054 RO Splitting for QBO 2022-09-26 11:33:58 -07:00
Patrick Fic
220e863482 Merged in release/2022-09-23 (pull request #586)
Prevent no reply loop
2022-09-24 18:05:24 +00:00
Patrick Fic
0fee89623c Prevent no reply loop 2022-09-24 11:04:08 -07:00
Patrick Fic
79c7ef327c Merged in release/2022-09-23 (pull request #585)
Updated recipieint for bounced emails since testing confirmed.
2022-09-23 21:10:48 +00:00
Patrick Fic
9249222fab Add missing query field. 2022-09-23 14:10:16 -07:00
Patrick Fic
4b6eea41c2 Updated recipieint for bounced emails since testing confirmed. 2022-09-23 14:08:45 -07:00
Patrick Fic
f7adbd9a20 Merged in release/2022-09-23 (pull request #584)
Updates to bounced email tracker.
2022-09-23 21:03:19 +00:00
Patrick Fic
ac270a608f Updates to bounced email tracker. 2022-09-23 13:59:58 -07:00
Patrick Fic
32110b13c2 Merged in release/2022-09-23 (pull request #583)
Update SNS handling
2022-09-23 20:49:06 +00:00
Patrick Fic
39efb52497 Update SNS handling 2022-09-23 13:48:45 -07:00
Patrick Fic
b4fd674535 Merged in release/2022-09-23 (pull request #582)
Release/2022 09 23
2022-09-23 20:36:42 +00:00
Patrick Fic
7aeac03b2c update formatting of bounced emails. 2022-09-23 13:35:33 -07:00
Patrick Fic
5ed8cef2f5 Merged in release/2022-09-23 (pull request #581)
Release/2022 09 23
2022-09-22 23:22:55 +00:00
Patrick Fic
c2dd122370 Merge branch 'release/2022-09-23' of https://bitbucket.org/snaptsoft/bodyshop into release/2022-09-23 2022-09-22 14:46:12 -07:00
Patrick Fic
7fc8cbcca4 IO-2061 Handled Bounced Emails. 2022-09-22 14:46:08 -07:00
Patrick Fic
ceafab55fa IO-2054 QBO Split AR 2022-09-22 11:21:08 -07:00
Patrick Fic
cccd025a24 Resolve PPG data pump issue. 2022-09-20 15:30:32 -07:00
Patrick Fic
78a04067c5 IO-2053 Confirm before marking as exported. 2022-09-19 19:21:44 -07:00
Patrick Fic
2db2c8edbf IO-2049 Document delete&move on server side 2022-09-19 14:48:06 -07:00
Patrick Fic
ff149c4464 Merged in release/2022-09-23 (pull request #580)
IO-233 Logging & Credentials
2022-09-19 18:00:44 +00:00
Patrick Fic
38efe03889 IO-233 Logging & Credentials 2022-09-19 11:00:04 -07:00
Patrick Fic
195e4a05db Merged in release/2022-09-23 (pull request #579)
IO-233 Update ARMS trigger & error logging.
2022-09-19 17:54:24 +00:00
Patrick Fic
2042ded99b IO-233 Update ARMS trigger & error logging. 2022-09-19 10:53:48 -07:00
Patrick Fic
d75a6328e8 Merged in release/2022-09-16 (pull request #578)
Release/2022 09 16
2022-09-19 15:50:43 +00:00
Patrick Fic
7194d2bba0 Merged in release/2022-09-16 (pull request #577)
Release/2022 09 16
2022-09-14 17:12:08 +00:00
Patrick Fic
3d9a24de4f IO-782 Parts Label Multi. 2022-09-13 15:43:05 -07:00
Patrick Fic
9792773cf0 IO-2043 Add LMS bill upload notification. 2022-09-13 15:26:55 -07:00
Patrick Fic
7c4ba416f7 IO-2028 LMS Deletion. 2022-09-13 15:13:43 -07:00
Patrick Fic
9dbb4b586f IO-233 ARMS event based updates. 2022-09-13 14:00:54 -07:00
Patrick Fic
169fdf6ae8 Merged in release/2022-09-09 (pull request #576)
Release/2022 09 09
2022-09-09 20:13:00 +00:00
Patrick Fic
9b92f83e52 Merged in release/2022-09-09 (pull request #575)
Release/2022 09 09
2022-09-08 17:04:23 +00:00
Patrick Fic
5f3c1fc95e IO-2039 LMS support for non media files. 2022-09-08 10:03:24 -07:00
Patrick Fic
ba9ea17805 Add QBO error for missing insurance company name. 2022-09-06 09:41:02 -07:00
Patrick Fic
d7c68441e8 Merged in releaase/2022-09-02 (pull request #574)
Releaase/2022 09 02
2022-09-02 15:03:09 +00:00
Patrick Fic
88a9ec1d3c Merged in releaase/2022-09-02 (pull request #573)
releaase/2022-09-02

Approved-by: Patrick Fic
2022-09-01 23:49:36 +00:00
Patrick Fic
c0973b6098 Remove unnecessary console log. 2022-09-01 16:48:29 -07:00
Patrick Fic
e10196bb62 IO-1325 Populate 3rd party payer modal correctly. 2022-09-01 16:39:05 -07:00
Patrick Fic
2e2e9ad7a9 Merged in releaase/2022-09-02 (pull request #572)
Releaase/2022 09 02
2022-09-01 15:54:46 +00:00
Patrick Fic
637a33e670 IO-1981 Disable order type for OEC orders. 2022-08-31 14:48:47 -07:00
Patrick Fic
eb52ccbc9d Merged in releaase/2022-09-02 (pull request #571)
Releaase/2022 09 02
2022-08-31 21:10:18 +00:00
Patrick Fic
8bcc903f2b IO-1325 Automatically populate 3rd party modal. 2022-08-31 13:37:01 -07:00
Patrick Fic
5ec5be0852 IO-1981 Add Sublet Order. 2022-08-31 13:04:36 -07:00
Patrick Fic
cb0c4d55df IO-1950 Parts Order Discounts 2022-08-31 10:32:41 -07:00
Patrick Fic
dea4d50821 IO-1949 Add additional buttons to email overlay. 2022-08-30 16:16:07 -07:00
Patrick Fic
df1adc34a2 IO-2033 Mark payment as exported. 2022-08-30 14:11:34 -07:00
Patrick Fic
e44e2bd7dd Merged in release/2022-08-26 (pull request #570)
IO-2032 Add psr by make template.

Approved-by: Patrick Fic
2022-08-29 23:50:53 +00:00
Patrick Fic
8f44b61a16 Merged in release/2022-08-26 (pull request #569)
release/2022-08-26

Approved-by: Patrick Fic
2022-08-29 23:22:05 +00:00
Patrick Fic
1f9abac599 IO-2032 Add psr by make template. 2022-08-29 16:21:46 -07:00
Patrick Fic
b557100fc6 Merged in release/2022-08-26 (pull request #568)
IO-1984 Resolve error on email audit tab.

Approved-by: Patrick Fic
2022-08-29 15:13:57 +00:00
Patrick Fic
7104af82d7 Merged in release/2022-08-26 (pull request #567)
release/2022-08-26

Approved-by: Patrick Fic
2022-08-29 15:13:31 +00:00
Patrick Fic
5adfef6ce0 IO-1984 Resolve error on email audit tab. 2022-08-29 08:13:12 -07:00
Patrick Fic
d9a8831eb3 Merged in release/2022-08-26 (pull request #566)
Minor release fixes.

Approved-by: Patrick Fic
2022-08-26 21:06:43 +00:00
Patrick Fic
511b7693c7 Merged in release/2022-08-26 (pull request #565)
release/2022-08-26

Approved-by: Patrick Fic
2022-08-26 21:06:12 +00:00
Patrick Fic
f5c9a7dfef Minor release fixes. 2022-08-26 13:59:44 -07:00
Patrick Fic
cdacc2befa Merged in release/2022-08-26 (pull request #564)
release/2022-08-26

Approved-by: Patrick Fic
2022-08-25 22:48:17 +00:00
Patrick Fic
79563a5cba IO-2030 Add totals to labor allocations table. 2022-08-25 14:56:59 -07:00
Patrick Fic
5264dfa49f Merge branch 'release/2022-08-26' of bitbucket.org:snaptsoft/bodyshop into release/2022-08-26 2022-08-23 15:16:27 -07:00
Patrick Fic
0810467689 IO-223 ARMS based updates. 2022-08-23 14:03:55 -07:00
Patrick Fic
2e069bf628 Merge branch 'hotfix/2022-08-23' into release/2022-08-26
* hotfix/2022-08-23:
  Query only active QBO tax codes.
2022-08-23 10:10:50 -07:00
Patrick Fic
137370812d Merged in hotfix/2022-08-23 (pull request #563)
Query only active QBO tax codes.
2022-08-23 17:01:03 +00:00
Patrick Fic
4f060ec447 Query only active QBO tax codes. 2022-08-23 09:59:38 -07:00
Patrick Fic
23971e23f2 IO-1998 Filter schedule by event type. 2022-08-23 09:31:04 -07:00
Patrick Fic
44e313d8e3 IO-1882 Add manual event from job detail actions menu. 2022-08-22 15:01:59 -07:00
Patrick Fic
3b9c44b0a8 IO-1984 Email Audit Trail 2022-08-22 13:02:02 -07:00
Patrick Fic
e438348e9b Update ARMS query 2022-08-19 14:10:13 -07:00
Patrick Fic
6122a24b80 Merged in release/2022-08-19 (pull request #562)
Release/2022 08 19
2022-08-19 18:25:50 +00:00
Patrick Fic
93cdbea17c Merged in release/2022-08-19 (pull request #561)
Release/2022 08 19
2022-08-18 21:47:16 +00:00
Patrick Fic
62d5c17de2 IO-2027 LMS Texting & Emails. 2022-08-18 14:46:40 -07:00
Patrick Fic
87c934c886 Add RBAC to tech jobs list. 2022-08-18 10:48:51 -07:00
Patrick Fic
e4b736d4e9 Resolve label typo. 2022-08-17 14:19:28 -07:00
Patrick Fic
d0673bfcba Updated third party notices. 2022-08-17 14:19:21 -07:00
Patrick Fic
18c182e573 Merged in release/2022-08-19 (pull request #560)
Release/2022 08 19
2022-08-17 16:30:52 +00:00
Patrick Fic
fe6e85e993 IO-2023 Resolve hours paid as part for MPI. 2022-08-16 16:18:50 -07:00
Patrick Fic
b744720efe IO-2009 Better handling of MPI discounts. 2022-08-16 14:29:28 -07:00
Patrick Fic
d5c27fc9ae IO-2015 Job Costing fix for lines with no part type but a dollar amount. 2022-08-15 12:39:09 -07:00
Patrick Fic
129c94f066 Check for blank string on owner name 2022-08-15 10:45:19 -07:00
Patrick Fic
503e901295 Merged in release/2022-08-05 (pull request #559)
IO-2008 Split local media into images and other as per cloudinary for better other support.
2022-08-04 16:06:52 +00:00
Patrick Fic
c2058bcec6 Merged in release/2022-08-05 (pull request #558)
Release/2022 08 05
2022-08-04 15:28:08 +00:00
Patrick Fic
85caf828ea IO-2008 Split local media into images and other as per cloudinary for better other support. 2022-08-03 13:27:45 -07:00
Patrick Fic
92b89af1c7 Merged in release/2022-08-05 (pull request #555)
Add null check to owner display.

Approved-by: Patrick Fic
2022-08-02 18:19:26 +00:00
Patrick Fic
b03d729af6 Add null check to owner display. 2022-08-02 11:18:36 -07:00
Patrick Fic
5c22ce188b Merged in release/2022-08-05 (pull request #554)
Updated antd package to fix drawer issues.

Approved-by: Patrick Fic
2022-08-02 18:11:31 +00:00
Patrick Fic
faef05a95b Updated antd package to fix drawer issues. 2022-08-02 11:11:01 -07:00
Patrick Fic
64aa8336b3 Merged in release/2022-07-29 (pull request #553)
Resolve production board loading issue for MAXWR.

Approved-by: Patrick Fic
2022-07-30 21:42:56 +00:00
Patrick Fic
8700e9a9ae Resolve production board loading issue for MAXWR. 2022-07-30 14:42:31 -07:00
Patrick Fic
d92f706d05 Merged in release/2022-07-29 (pull request #552)
release/2022-07-29

Approved-by: Patrick Fic
2022-07-29 23:05:08 +00:00
Patrick Fic
6031255d61 Merged in release/2022-07-29 (pull request #551)
release/2022-07-29

Approved-by: Patrick Fic
2022-07-29 18:08:36 +00:00
Patrick Fic
a62fd5d9b2 IO-1861 Scoreboard entry edit bug fixes. 2022-07-29 10:57:55 -07:00
Patrick Fic
e9c229f307 Merged in release/2022-07-29 (pull request #550)
release/2022-07-29

Approved-by: Patrick Fic
2022-07-29 17:39:51 +00:00
Patrick Fic
9fec6bbe0c IO-2004 Include adjustments made to new labor types. 2022-07-29 10:37:48 -07:00
Patrick Fic
e379ecfba6 IO-2002 Keep manual lines on supplement. 2022-07-27 13:09:33 -07:00
Patrick Fic
4ef68a0e26 IO-2001 Always expose production note. 2022-07-27 10:00:57 -07:00
Patrick Fic
26f6f63782 Merged in release/2022-07-29 (pull request #549)
IO-2000 Add parts_not_recieved_vendor to report center.

Approved-by: Patrick Fic
2022-07-26 21:30:26 +00:00
Patrick Fic
f36926f228 IO-2000 Add parts_not_recieved_vendor to report center. 2022-07-26 14:30:01 -07:00
Patrick Fic
3eaf199322 Merged in release/2022-07-29 (pull request #548)
release/2022-07-29

Approved-by: Patrick Fic
2022-07-26 18:40:50 +00:00
Patrick Fic
22cd7fcef1 IO-1861 All scoreboard entries. 2022-07-26 11:22:15 -07:00
Patrick Fic
2586393b4c Added admin functionality. 2022-07-26 09:33:08 -07:00
Patrick Fic
9ded2fd5ba Merged in release/2022-07-29 (pull request #547)
release/2022-07-29

Approved-by: Patrick Fic
2022-07-25 20:05:51 +00:00
Patrick Fic
0b448e043c IO-1990 Add alt_partno to bill posting screen. 2022-07-25 12:37:56 -07:00
Patrick Fic
f89e39759a Package updates. 2022-07-25 12:24:35 -07:00
Patrick Fic
ce783fdc5a Merged in release/2022-07-29 (pull request #546)
release/2022-07-29

Approved-by: Patrick Fic
2022-07-25 18:15:43 +00:00
Patrick Fic
8aa99415b1 IO-1995 remove syncing of shop status between tabs to reduce unintended consequences. 2022-07-25 11:14:45 -07:00
Patrick Fic
2ede54b901 IO-1994 Resolve parts filtering status and sorting. 2022-07-25 11:04:03 -07:00
Patrick Fic
19329e76b7 Merged in release/2022-07-22 (pull request #545)
Release/2022 07 22
2022-07-22 17:56:34 +00:00
Patrick Fic
a1e35a137e Merged in release/2022-07-22 (pull request #544)
Remove logging statement.
2022-07-22 17:56:10 +00:00
Patrick Fic
c1881671a8 Remove logging statement. 2022-07-22 10:49:45 -07:00
Patrick Fic
fa9731369b Merged in release/2022-07-22 (pull request #543)
Release/2022 07 22
2022-07-22 15:34:30 +00:00
Patrick Fic
e0e88acb9e IO-1979 Clear vacation form on enter. 2022-07-21 15:05:44 -07:00
Patrick Fic
14c40958b4 IO-1957 Adjust scoreboard effiiency for edge case. 2022-07-21 14:53:06 -07:00
Patrick Fic
2438857cb7 IO-1294 Refetch data after admin update. 2022-07-21 14:20:40 -07:00
Patrick Fic
9362372512 IO-1985 Add filtering for est ct on ready and active jobs screens. 2022-07-21 14:05:57 -07:00
Patrick Fic
616f326a88 Merged in release/2022-07-22 (pull request #542)
release/2022-07-22

Approved-by: Patrick Fic
2022-07-20 22:19:46 +00:00
Patrick Fic
b30192c6d5 IO-1978 Updated parts return logic and removed extra button. 2022-07-20 15:18:13 -07:00
Patrick Fic
9ddcb1f27d IO-1985 add Est to actie and ready jobs screens. 2022-07-20 15:07:37 -07:00
Patrick Fic
cd29a0f613 IO-1983 Add filter to job notes table for audit. 2022-07-20 14:39:06 -07:00
Patrick Fic
b189b045f8 IO-1988 Download LMS as zip. 2022-07-20 14:17:30 -07:00
Patrick Fic
17baca40d9 IO-1982 add alt_partno to jobs list and order button. 2022-07-19 15:57:32 -07:00
Patrick Fic
60950be9d5 Merged in release/2022-07-22 (pull request #541)
Release/2022 07 22
2022-07-19 21:53:12 +00:00
Patrick Fic
956134c360 IO-1957 Add efficiencies to timetickets scoreboard 2022-07-19 14:40:53 -07:00
Patrick Fic
d5f55c5873 IO-1979 Resolve various employee vacation bugs. 2022-07-19 13:41:14 -07:00
Patrick Fic
179147dc4e IO-1755 Add tax number to owner. 2022-07-19 13:14:22 -07:00
Patrick Fic
385de3eaaa IO-1414 Job costing GP and decimals fix. 2022-07-19 12:35:06 -07:00
Patrick Fic
22368069a4 IO-1294 Add fields changed alert to admin dates. 2022-07-19 11:21:50 -07:00
Patrick Fic
5eb8c2c03a Code cleanup for CI. 2022-07-18 17:01:52 -07:00
Patrick Fic
a23b3bf02d IO-1978 Selection of lines for parts return 2022-07-18 16:25:33 -07:00
Patrick Fic
4976e6be95 IO-1961 Updated table hover contrast. 2022-07-18 12:17:11 -07:00
Patrick Fic
1b58a29112 IO-1974 Add lease returned CC status. 2022-07-18 11:59:57 -07:00
Patrick Fic
05e236cd9c IO-1018 Warnings for courtesy car fields. 2022-07-18 11:56:05 -07:00
Patrick Fic
90bd70070d IO-1972 Add tracking for conversions to labor. 2022-07-18 11:45:02 -07:00
Patrick Fic
fb6c667a7f Merge remote-tracking branch 'origin/master' into release/2022-07-22 2022-07-18 11:39:32 -07:00
Patrick Fic
c048f21674 IO-1972 Converted to labor display. 2022-07-18 11:39:23 -07:00
Patrick Fic
05de47c833 Update parts filtering on job lines page. 2022-07-18 11:08:11 -07:00
Patrick Fic
c163e2a274 Merged in release/2022-07-15 (pull request #540)
Release/2022 07 15
2022-07-15 22:40:46 +00:00
Patrick Fic
f479aeda54 Merged in release/2022-07-15 (pull request #539)
Release/2022 07 15
2022-07-15 22:40:04 +00:00
Patrick Fic
7cf32775eb Revert changes for IO-1972. 2022-07-15 15:31:16 -07:00
Patrick Fic
98bf700d1c Merged in release/2022-07-15 (pull request #538)
IO-1536 Add VIN Highlighting.
2022-07-15 17:51:21 +00:00
Patrick Fic
27ce30527e IO-1536 Add VIN Highlighting. 2022-07-14 15:18:01 -07:00
Patrick Fic
2a9609b917 Merged in release/2022-07-15 (pull request #537)
release/2022-07-15

Approved-by: Patrick Fic
2022-07-14 16:10:05 +00:00
Patrick Fic
b346c28fe0 IO-1954 schedule display improvements. 2022-07-13 16:17:21 -07:00
Patrick Fic
05a0ee30f4 IO-1972 add table for converted parts to labor lines. 2022-07-13 13:50:13 -07:00
Patrick Fic
7a0d5d712a IO-1906 Remove bin from bill edit. 2022-07-13 11:35:21 -07:00
Patrick Fic
4802c1abe8 IO-1937 Update email sizing error message. 2022-07-12 15:42:00 -07:00
Patrick Fic
7022609e22 Parts Quote create order first. 2022-07-11 15:01:57 -07:00
Patrick Fic
d8447b1197 Resolve phone number parsing issue on messaging. 2022-07-11 14:23:27 -07:00
Patrick Fic
43b140aed4 Merged in release/2022-07-08 (pull request #536)
Autohouse error fix.

Approved-by: Patrick Fic
2022-07-07 16:30:46 +00:00
Patrick Fic
423df9f9aa Merged in release/2022-07-08 (pull request #535)
release/2022-07-08

Approved-by: Patrick Fic
2022-07-07 16:30:17 +00:00
Patrick Fic
d4ee6ca8ba Autohouse error fix. 2022-07-07 09:29:37 -07:00
Patrick Fic
d6673ed278 Merged in release/2022-06-30 (pull request #534)
release/2022-06-30

Approved-by: Patrick Fic
2022-06-30 21:17:23 +00:00
Patrick Fic
184ea58ec2 Merged in release/2022-06-30 (pull request #533)
release/2022-06-30

Approved-by: Patrick Fic
2022-06-30 21:17:08 +00:00
Patrick Fic
e0804099ee IO-1967 Adjust display of convert to labor button. 2022-06-30 14:16:10 -07:00
Patrick Fic
ece0946738 Remove updating of message count on outbound message. 2022-06-30 11:38:49 -07:00
Patrick Fic
f9b6920eba Merged in release/2022-06-30 (pull request #532)
release/2022-06-30

Approved-by: Patrick Fic
2022-06-30 16:59:17 +00:00
Patrick Fic
9bcc44c0cc Updated labor adjustment audit trail. 2022-06-30 09:58:55 -07:00
Patrick Fic
9d8550e040 Merged in release/2022-06-30 (pull request #531)
release/2022-06-30

Approved-by: Patrick Fic
2022-06-30 16:04:37 +00:00
Patrick Fic
b9b6759c54 IO-1967 Update audit trail for labor adjustments. 2022-06-30 09:04:17 -07:00
Patrick Fic
1f1274a54a IO0-1951 Move checkbox location for parts order quote. 2022-06-30 08:39:33 -07:00
Patrick Fic
35d4188469 IO-1947 Resolve unknown filter status for qfp. 2022-06-30 08:34:58 -07:00
Patrick Fic
930321c885 Merged in release/2022-06-30 (pull request #530)
release/2022-06-30

Approved-by: Patrick Fic
2022-06-29 23:20:00 +00:00
Patrick Fic
a8f89c81fc IO-1951 Resolve parts order issue. 2022-06-29 16:19:16 -07:00
Patrick Fic
5ff660d83d Merged in release/2022-06-30 (pull request #529)
release/2022-06-30

Approved-by: Patrick Fic
2022-06-29 22:20:53 +00:00
Patrick Fic
c382b3f2e0 Fix CI errors. 2022-06-29 15:19:47 -07:00
Patrick Fic
4b923acdf3 Merged in release/2022-06-30 (pull request #528)
release/2022-06-30

Approved-by: Patrick Fic
2022-06-29 22:05:24 +00:00
Patrick Fic
39dbf40a49 IO-1967 Convert dollar amount to labor. 2022-06-29 15:05:03 -07:00
Patrick Fic
037ff4c2a1 Resolve email rendering issue with JSR Update. 2022-06-29 10:54:19 -07:00
Patrick Fic
40037216aa Merged in hotfix/2022-06-28 (pull request #527)
Update autohouse error logging.

Approved-by: Patrick Fic
2022-06-29 17:34:54 +00:00
Patrick Fic
5a6a92c260 Merged in hotfix/2022-06-28 (pull request #526)
Update autohouse error logging.

Approved-by: Patrick Fic
2022-06-29 17:05:14 +00:00
Patrick Fic
fb4b12233a Update autohouse error logging. 2022-06-29 09:48:26 -07:00
Patrick Fic
d45f84afbd Change firebase handler rejection logging. 2022-06-28 16:24:49 -07:00
Patrick Fic
1d80153da1 Added placeholder for paint codes on prod board. 2022-06-28 16:14:26 -07:00
Patrick Fic
f704fd5f56 Added mix data logging. 2022-06-28 16:14:13 -07:00
Patrick Fic
82c4320f0c Add paint mix data logging. 2022-06-28 16:04:34 -07:00
Patrick Fic
6337a961e9 Merged in release/2022-06-30 (pull request #525)
Release/2022 06 30
2022-06-28 22:50:16 +00:00
Patrick Fic
bebe99f4e6 Merge branch 'hotfix/2022-06-28' into release/2022-06-30
* hotfix/2022-06-28:
  Update error handling for autohouse.
  Autohouse replace fix.
2022-06-28 13:31:59 -07:00
Patrick Fic
1e24d5d57f Merged in hotfix/2022-06-28 (pull request #524)
hotfix/2022-06-28

Approved-by: Patrick Fic
2022-06-28 15:35:03 +00:00
Patrick Fic
fbcf2b559e Update error handling for autohouse. 2022-06-28 08:34:34 -07:00
Patrick Fic
4582c493ee Autohouse replace fix. 2022-06-28 08:31:24 -07:00
Patrick Fic
ecfd284539 Upgrade to latest JSR package. 2022-06-27 22:03:10 -07:00
Patrick Fic
2a1c046dd6 IO-1943 Enter again bill functionality bugfix. 2022-06-27 16:17:45 -07:00
Patrick Fic
54ebc2e25b IO-1955 include names for incoming/outgoing jobs on schedule. 2022-06-27 14:12:06 -07:00
Patrick Fic
974a0ec1f1 IO-1951 Added quote for OEC orders. 2022-06-27 13:42:48 -07:00
Patrick Fic
acf99584ea IO-1947 Remember parts queue filter status. 2022-06-27 13:06:14 -07:00
Patrick Fic
d0d4ceb270 IO-1958 IO-1884 Vehicle card updates. 2022-06-27 12:46:29 -07:00
Patrick Fic
f2e7808fa0 Merged in release/2022-06-24 (pull request #523)
release/2022-06-24

Approved-by: Patrick Fic
2022-06-24 21:04:03 +00:00
Patrick Fic
fb04742e5b Merged in release/2022-06-24 (pull request #522)
Add job reconciliation & autohouse filtering.

Approved-by: Patrick Fic
2022-06-24 16:48:05 +00:00
Patrick Fic
c07458babf Add job reconciliation & autohouse filtering. 2022-06-24 09:45:11 -07:00
Patrick Fic
0892461631 Merged in release/2022-06-24 (pull request #521)
release/2022-06-24

Approved-by: Patrick Fic
2022-06-21 00:53:28 +00:00
Patrick Fic
50f5be3174 Merged in release/2022-06-24 (pull request #520)
release/2022-06-24

Approved-by: Patrick Fic
2022-06-20 22:19:15 +00:00
Patrick Fic
623d407a6c IO-1942 Resolve marking credit memoes received error. 2022-06-20 15:18:54 -07:00
Patrick Fic
5e3218a145 Merged in release/2022-06-17 (pull request #519)
release/2022-06-17

Approved-by: Patrick Fic
2022-06-20 17:48:49 +00:00
Patrick Fic
63106487b9 Merged in release/2022-06-17 (pull request #518)
release/2022-06-17

Approved-by: Patrick Fic
2022-06-20 17:48:16 +00:00
Patrick Fic
706f300750 IO-1941 change owner search 2022-06-20 10:27:22 -07:00
Patrick Fic
4fad4e41c2 IO-1938 Updated sorting on time tickets. 2022-06-20 08:53:15 -07:00
Patrick Fic
1e88d5ae1b IO-1937 Add 10mb limit for emails. 2022-06-17 15:22:29 -07:00
Patrick Fic
7ba3cc5ffa Added basic creation of shops. 2022-06-15 19:03:31 -07:00
Patrick Fic
4fdd48c279 Update developement db addresses. 2022-06-14 11:32:29 -07:00
Patrick Fic
f5834ae6bc Updated dev end points. 2022-06-14 09:55:46 -07:00
Patrick Fic
db36b27819 Merged in release/2022-06-17 (pull request #517)
IO-1917 Autohouse handling for DMS.

Approved-by: Patrick Fic
2022-06-13 18:49:23 +00:00
Patrick Fic
43fbf32e99 IO-1917 Autohouse handling for DMS. 2022-06-13 11:44:35 -07:00
Patrick Fic
1b2afb9e93 Merged in release/2022-06-10 (pull request #516)
IO-1911 Round TTSB to 1 decimal.

Approved-by: Patrick Fic
2022-06-13 15:59:24 +00:00
Patrick Fic
77cb6a4acc IO-1911 Round TTSB to 1 decimal. 2022-06-13 08:58:33 -07:00
Patrick Fic
6a109d63ce Merged in release/2022-06-10 (pull request #515)
Remove package.lock.

Approved-by: Patrick Fic
2022-06-10 23:38:47 +00:00
Patrick Fic
17c3b24380 Remove package.lock. 2022-06-10 16:16:58 -07:00
Patrick Fic
a6610309e9 Merged in release/2022-06-10 (pull request #514)
Updated package.lock.

Approved-by: Patrick Fic
2022-06-10 23:04:58 +00:00
Patrick Fic
c63f2ed035 Updated package.lock. 2022-06-10 16:04:36 -07:00
Patrick Fic
db02b9c1c2 Merged in release/2022-06-10 (pull request #513)
release/2022-06-10

Approved-by: Patrick Fic
2022-06-10 22:38:15 +00:00
Patrick Fic
a3a48c1b29 Merged in release/2022-06-10 (pull request #512)
release/2022-06-10

Approved-by: Patrick Fic
2022-06-10 22:37:40 +00:00
Patrick Fic
2c760948c5 IO-1911 Add target to tt scoreboard. 2022-06-10 10:57:51 -07:00
Patrick Fic
72d0a990ec Merged in release/2022-06-10 (pull request #511)
release/2022-06-10

Approved-by: Patrick Fic
2022-06-10 17:07:01 +00:00
Patrick Fic
446bd9035f IO-1914 Resolve bill edit error. 2022-06-10 10:06:40 -07:00
Patrick Fic
e8d6871a20 Merged in release/2022-06-10 (pull request #510)
release/2022-06-10

Approved-by: Patrick Fic
2022-06-09 22:55:25 +00:00
Patrick Fic
1f16abf303 IO-1860 Scoreboard always show on bottom of button. 2022-06-09 14:25:59 -07:00
Patrick Fic
f116e89c94 IO-1926 Set export logs for manual items to be array of 1. 2022-06-09 13:57:16 -07:00
Patrick Fic
f0ba00aeb8 IO-1911 Create separate query for time tickets scoreboard. 2022-06-09 13:35:34 -07:00
Patrick Fic
0089e50a29 IO-1862 Only show remove from production when job in production on close screen. 2022-06-09 13:18:49 -07:00
Patrick Fic
5d4b2f308d Merged in release/2022-06-10 (pull request #509)
Resolved issues for job search select & updated packages.

Approved-by: Patrick Fic
2022-06-09 19:01:39 +00:00
Patrick Fic
c01f402f92 Resolved issues for job search select & updated packages. 2022-06-09 11:51:01 -07:00
Patrick Fic
84b274d0a5 Merged in release/2022-06-10 (pull request #508)
release/2022-06-10

Approved-by: Patrick Fic
2022-06-09 17:46:36 +00:00
Patrick Fic
92fb519642 Additional cookie changes. 2022-06-09 10:46:05 -07:00
Patrick Fic
271b2286ae Merged in release/2022-06-10 (pull request #507)
release/2022-06-10

Approved-by: Patrick Fic
2022-06-09 17:24:20 +00:00
Patrick Fic
fa1dbc2611 Add back cookie support for CORS and WS. 2022-06-09 10:24:03 -07:00
Patrick Fic
660f851ee6 Merged in release/2022-06-10 (pull request #506)
Revert "Server Side CORS Updates."

Approved-by: Patrick Fic
2022-06-09 16:56:28 +00:00
Patrick Fic
447298c07d Revert "Server Side CORS Updates."
This reverts commit fde0681a93.
2022-06-09 09:55:48 -07:00
Patrick Fic
5367e96aad Merged in release/2022-06-10 (pull request #505)
release/2022-06-10

Approved-by: Patrick Fic
2022-06-09 00:46:53 +00:00
Patrick Fic
4844c42425 IO-1914 Add manual inventory and edit. 2022-06-08 17:45:58 -07:00
Patrick Fic
82db7a1f14 IO-1914 Add manual and edit lines of inventory. 2022-06-08 17:45:30 -07:00
Patrick Fic
fde0681a93 Server Side CORS Updates. 2022-06-08 13:24:43 -07:00
Patrick Fic
9ac262fec2 Merged in release/2022-06-10 (pull request #504)
Resolve email PDFs not generating header.

Approved-by: Patrick Fic
2022-06-08 17:01:40 +00:00
Patrick Fic
b36b4cb213 Resolve email PDFs not generating header. 2022-06-08 09:37:48 -07:00
Patrick Fic
62e044fbb2 Merged in release/2022-06-10 (pull request #503)
release/2022-06-10

Approved-by: Patrick Fic
2022-06-07 21:36:19 +00:00
Patrick Fic
77cbbef085 IO-1914 Add Quantity Handling for inventory. 2022-06-07 14:16:54 -07:00
Patrick Fic
d71155f350 Merged in release/2022-06-10 (pull request #502)
release/2022-06-10

Approved-by: Patrick Fic
2022-06-07 19:40:36 +00:00
Patrick Fic
a1472cd9ff IO-1926 Add export log to mark as exported. 2022-06-07 12:39:59 -07:00
Patrick Fic
d32fd9e697 IO-1914 Consume from inventory screen. 2022-06-07 12:14:24 -07:00
Patrick Fic
fe5e2a247a IO-1914 Inventory bugfixes. 2022-06-07 11:41:27 -07:00
Patrick Fic
9bf3974ba0 IO-1732 Adjust tech job drawer width. 2022-06-07 08:59:28 -07:00
Patrick Fic
42195fccea IO-1862 Add option to remove from production on invoice close. 2022-06-07 08:55:07 -07:00
Patrick Fic
9491d5f069 IO-1916 Resolve required labels on sign in. 2022-06-07 08:43:24 -07:00
Patrick Fic
e003768969 IO-1925 Change job costing to have negative sale on adjustment. 2022-06-07 08:37:27 -07:00
Patrick Fic
d6c8d97715 Add comments field to parts queue. 2022-06-06 17:30:20 -07:00
Patrick Fic
ba55717683 IO-1663 Add time to production board. 2022-06-06 15:43:35 -07:00
Patrick Fic
c105c56acf Merged in release/2022-06-10 (pull request #501)
release/2022-06-10

Approved-by: Patrick Fic
2022-06-06 21:34:08 +00:00
Patrick Fic
78dd14af85 IO-1921 Add shipping to reconciliation. 2022-06-06 14:32:59 -07:00
Patrick Fic
e109b5102c IO-1920 Add ability to directly post tickets from tech console. 2022-06-06 13:53:09 -07:00
Patrick Fic
644f269629 IO-1919 Add filtering to parts queue. 2022-06-06 12:59:24 -07:00
Patrick Fic
320aa9c177 IO-1923 Add CC warning to CC list page. 2022-06-06 12:32:32 -07:00
Patrick Fic
a0b238c4bb IO-1841 Remove special characters on QB Export. 2022-06-06 12:20:32 -07:00
Patrick Fic
fd8dab911f IO-1911 Add scrolling to time tickets scoreboard. 2022-06-06 12:10:01 -07:00
Patrick Fic
3666b7cd22 IO-1911 Tickets scoreboard updates. 2022-06-06 12:04:47 -07:00
Patrick Fic
e846d7fce4 Merge branch 'master' into release/2022-06-10 2022-06-06 11:09:33 -07:00
Patrick Fic
46b9359dcb Merged in hotfix/2022-06-03 (pull request #499)
Resolve AR Account Reference.
2022-06-06 18:00:04 +00:00
Patrick Fic
bd6553f8e4 Resolve AR Account Reference. 2022-06-06 10:59:17 -07:00
Patrick Fic
674c06665c IO-1911 Timetickets Scoreboard. 2022-06-06 09:54:02 -07:00
Patrick Fic
3feb1a3887 IO-1914 Add split tracking for inventory. 2022-06-06 09:53:42 -07:00
Patrick Fic
7be8322a14 Merged in hotfix/2022-06-03 (pull request #498)
Hotfix/2022 06 03
2022-06-03 23:14:30 +00:00
Patrick Fic
9c7f2a6080 Merged in hotfix/2022-06-03 (pull request #497)
hotfix/2022-06-03

Approved-by: Patrick Fic
2022-06-03 17:15:51 +00:00
Patrick Fic
534f75c9b1 IO-1913 Add Rental Reservation key 2022-06-03 09:04:40 -07:00
Patrick Fic
87f68f1840 Add A/R Account to Receivables Export. 2022-06-03 09:03:48 -07:00
Patrick Fic
1258843f3d IO-1913 Add Rental Reservation key 2022-06-02 10:09:01 -07:00
Patrick Fic
485b5d0866 IO-1914 Update inventory insert queries. 2022-06-01 16:32:24 -07:00
Patrick Fic
62b1da0b64 IO-1914 Add bill refetch after insert. 2022-06-01 13:45:42 -07:00
Patrick Fic
a855853230 IO-1914 WIP Inventory. 2022-06-01 09:52:47 -07:00
Patrick Fic
d28d4d6283 IO-1914 Added inventory page. 2022-05-31 12:38:07 -07:00
Patrick Fic
912756e0f9 Missed in previous commit. 2022-05-30 17:48:29 -07:00
Patrick Fic
908c17aa68 IO-1914 Schema for inventory and basic adding to inventory. 2022-05-30 17:39:43 -07:00
Patrick Fic
96995fdd1b Add A/R Account to Receivables Export. 2022-05-30 16:30:49 -07:00
Patrick Fic
5341d93e29 Merged in release/2022-05-27 (pull request #495)
Remove console log statmenets.

Approved-by: Patrick Fic
2022-05-27 21:46:44 +00:00
Patrick Fic
bcd6cc006f Merged in release/2022-05-27 (pull request #494)
release/2022-05-27

Approved-by: Patrick Fic
2022-05-27 21:44:45 +00:00
Patrick Fic
b8d2dbc2e1 Remove console log statmenets. 2022-05-27 14:38:45 -07:00
Patrick Fic
b2e14ed03b Merged in release/2022-05-27 (pull request #493)
release/2022-05-27

Approved-by: Patrick Fic
2022-05-27 21:34:31 +00:00
Patrick Fic
595ec72edd IO-1912 Resolve date for tech clock in. 2022-05-27 14:34:02 -07:00
Patrick Fic
885a861f1e IO-1910 Include totals of scoreboard components. 2022-05-27 10:39:16 -07:00
Patrick Fic
1d21dc3ee4 Merged in release/2022-05-27 (pull request #492)
release/2022-05-27

Approved-by: Patrick Fic
2022-05-26 22:21:32 +00:00
Patrick Fic
b213e5d54f Fix link on active jobs page. 2022-05-26 15:19:46 -07:00
Patrick Fic
653692b2a5 IO-1868 Prevent returns on invoiced RO. 2022-05-26 15:11:44 -07:00
Patrick Fic
3c355a8227 Merged in release/2022-05-27 (pull request #491)
release/2022-05-27

Approved-by: Patrick Fic
2022-05-26 19:25:41 +00:00
Patrick Fic
e1e5dda710 IO-1891 Add owner Note. 2022-05-26 12:09:36 -07:00
Patrick Fic
7f3b1413d7 Remove files. 2022-05-26 12:04:56 -07:00
Patrick Fic
528c68695f IO-1885 Resolve UTC time for parts orders. 2022-05-26 11:31:09 -07:00
Patrick Fic
65a18acdc1 Added loading to parts receive modal. 2022-05-25 08:37:14 -07:00
Patrick Fic
930b2791f2 IO-1907 Prevent NaN PVRT. 2022-05-24 16:59:25 -07:00
Patrick Fic
1cf5a1fba8 IO-1864 Mark bills as exported in bulk. 2022-05-24 16:44:10 -07:00
Patrick Fic
83137b2d96 Merged in release/2022-05-20 (pull request #490)
Release/2022 05 20
2022-05-20 17:33:07 +00:00
Patrick Fic
546208d249 Merged in release/2022-05-20 (pull request #489)
Release/2022 05 20
2022-05-20 17:32:31 +00:00
Patrick Fic
5832a5f529 Add improved logging to export procedures. 2022-05-20 10:31:31 -07:00
Patrick Fic
012d59e5b7 Merged in release/2022-05-20 (pull request #488)
release/2022-05-20

Approved-by: Patrick Fic
2022-05-20 16:20:12 +00:00
Patrick Fic
f3ee20e89d Bug fixes for this weeks stories. 2022-05-20 09:19:54 -07:00
Patrick Fic
bcd062dffd Remove IO Event Tracking. 2022-05-20 09:01:37 -07:00
Patrick Fic
8da2205a14 Merged in release/2022-05-20 (pull request #487)
release/2022-05-20

Approved-by: Patrick Fic
2022-05-20 15:54:17 +00:00
Patrick Fic
bd8b961bda IO-1894 Add scheduled return to all cc list. 2022-05-20 08:53:37 -07:00
Patrick Fic
ad2bae00de Merged in release/2022-05-20 (pull request #486)
release/2022-05-20

Approved-by: Patrick Fic
2022-05-19 18:26:10 +00:00
Patrick Fic
43b6bdb024 IO-1896 Add remove from partst queue on parts order. 2022-05-19 11:08:12 -07:00
Patrick Fic
3dfc6eede2 Change scoreboard to query instead of subscription. 2022-05-19 11:07:51 -07:00
Patrick Fic
e70c11d4e6 IO-1890 Clear page when searching phonebook. 2022-05-19 10:46:36 -07:00
Patrick Fic
abc7262584 IO-1886 Handle large numbers of of menu items for notes and file handler. 2022-05-19 10:09:45 -07:00
Patrick Fic
e10ca9897c Merged in hotfix/2022-05-18 (pull request #485)
hotfix/2022-05-18

Approved-by: Patrick Fic
2022-05-18 19:00:28 +00:00
Patrick Fic
7e969e32b2 Merged in hotfix/2022-05-18 (pull request #484)
hotfix/2022-05-18

Approved-by: Patrick Fic
2022-05-18 19:00:12 +00:00
Patrick Fic
6f561e4caa Resolve job notes insert for ROs with no vehicle. 2022-05-18 11:59:46 -07:00
Patrick Fic
97beb14209 Merged in release/2022-05-13 (pull request #483)
release/2022-05-13

Approved-by: Patrick Fic
2022-05-17 18:24:42 +00:00
Patrick Fic
5568a434b9 Merged in release/2022-05-13 (pull request #482)
release/2022-05-13

Approved-by: Patrick Fic
2022-05-17 18:24:12 +00:00
Patrick Fic
e92827aeb2 Resolve note saving issue. 2022-05-17 11:23:50 -07:00
Patrick Fic
ac890bd92b IO-1898 Add vehicle notes. 2022-05-17 08:24:04 -07:00
Patrick Fic
0eaf23841a IO-1893 Add scheduled in to parts queue. 2022-05-16 16:23:25 -07:00
Patrick Fic
82c13eae9e update NPM manager. 2022-05-13 17:14:03 -07:00
Patrick Fic
708eb3c73f Merged in release/2022-05-13 (pull request #481)
Updated CI config.

Approved-by: Patrick Fic
2022-05-13 23:41:10 +00:00
Patrick Fic
8a21c24a5b Merged in release/2022-05-13 (pull request #480)
Updated CI config.

Approved-by: Patrick Fic
2022-05-13 23:40:32 +00:00
Patrick Fic
cdb8e48f0d Updated CI config. 2022-05-13 16:39:47 -07:00
Patrick Fic
4004856cd6 Merged in release/2022-05-13 (pull request #479)
Local media server bugfixes.

Approved-by: Patrick Fic
2022-05-13 16:35:47 +00:00
Patrick Fic
90600cdff4 Local media server bugfixes. 2022-05-13 09:34:19 -07:00
Patrick Fic
23ace9f7be Merged in release/2022-05-13 (pull request #478)
IO-1875 Add CNR by Vendor
2022-05-12 23:59:10 +00:00
Patrick Fic
cdcfea988f Merged in hotfix/2022-05-10 (pull request #477)
Hotfix/2022 05 10
2022-05-12 23:58:38 +00:00
Patrick Fic
26f58961a0 IO-1875 Add CNR by Vendor 2022-05-12 16:58:02 -07:00
Patrick Fic
64f188dbdc Merged in release/2022-05-13 (pull request #476)
release/2022-05-13

Approved-by: Patrick Fic
2022-05-12 23:04:31 +00:00
Patrick Fic
8daa0ac154 Update URL to create explorer link. 2022-05-12 16:04:10 -07:00
Patrick Fic
ce15216764 Merged in release/2022-05-13 (pull request #475)
Release/2022 05 13
2022-05-12 19:41:19 +00:00
Patrick Fic
76fee429ea Merge branch 'hotfix/2022-05-10' into release/2022-05-13 2022-05-12 12:40:26 -07:00
Patrick Fic
d1a65530a3 IO-1881 Related RO notes. 2022-05-12 12:35:38 -07:00
Patrick Fic
4613a93d09 IO-1877 Multi line notes presets. 2022-05-12 11:36:11 -07:00
Patrick Fic
faf1d638fb IO-1874 Custom fields for receivables. 2022-05-12 11:34:15 -07:00
Patrick Fic
01253361b5 Merged in release/2022-05-13 (pull request #473)
Added IMS token changes.

Approved-by: Patrick Fic
2022-05-11 23:32:04 +00:00
Patrick Fic
55144bd621 Added IMS token changes. 2022-05-11 16:31:09 -07:00
Patrick Fic
fad667f2a4 Merged in release/2022-05-13 (pull request #471)
release/2022-05-13

Approved-by: Patrick Fic
2022-05-10 21:45:17 +00:00
Patrick Fic
bbf908e5e1 Merge branch 'hotfix/2022-05-10' into release/2022-05-13 2022-05-10 14:29:22 -07:00
Patrick Fic
9da126879e Merged in hotfix/2022-05-10 (pull request #470)
Update QBO export query for payments.

Approved-by: Patrick Fic
2022-05-10 21:25:52 +00:00
Patrick Fic
18fa00785c Update QBO export query for payments. 2022-05-10 14:25:28 -07:00
Patrick Fic
00b982becc Merged in release/2022-05-13 (pull request #465)
Release/2022 05 13
2022-05-10 20:31:43 +00:00
Patrick Fic
3192e918a4 Merged in feature/local-images (pull request #464)
feature/local-images

Approved-by: Patrick Fic
2022-05-10 20:18:39 +00:00
Patrick Fic
70e2fd000c Add local media setup to shop config. 2022-05-10 13:05:06 -07:00
Patrick Fic
f9fdd95491 Remove documents components that do not support local media. 2022-05-09 09:56:46 -07:00
Patrick Fic
45354417d0 Merge master into feature/local-images 2022-05-09 08:26:43 -07:00
Patrick Fic
70749cdef5 Merged in release/2022-05-06 (pull request #463)
Release/2022 05 06
2022-05-06 23:46:20 +00:00
Patrick Fic
c8fb685cb0 Merged in release/2022-05-06 (pull request #462)
release/2022-05-06

Approved-by: Patrick Fic
2022-05-06 22:50:44 +00:00
Patrick Fic
6c3d29ba91 IO-1853 CSV Generation. 2022-05-06 15:50:05 -07:00
Patrick Fic
eca5e8241a QBO Bill parent by default. 2022-05-06 15:35:34 -07:00
Patrick Fic
6a7b616037 Merged in release/2022-05-06 (pull request #461)
release/2022-05-06

Approved-by: Patrick Fic
2022-05-06 21:30:47 +00:00
Patrick Fic
cf23266831 IO-1853 Change attendance recipe type to text. 2022-05-06 14:30:26 -07:00
Patrick Fic
09d54722f0 IO-1863 Delete parts return and order line. 2022-05-06 14:25:02 -07:00
Patrick Fic
6607258777 Merged in release/2022-05-06 (pull request #460)
release/2022-05-06

Approved-by: Patrick Fic
2022-05-06 17:20:13 +00:00
Patrick Fic
d78955a8fd IO-1853 Add Attendance Table excel creation. 2022-05-06 10:19:49 -07:00
Patrick Fic
467841bea2 IO-1865 Add employee external ID 2022-05-06 09:46:26 -07:00
Patrick Fic
e56424c9b3 IO-1858 Email Groupings 2022-05-06 09:30:29 -07:00
Patrick Fic
a1e4f3827d Uploads and viewing from bills. 2022-05-05 15:46:58 -07:00
Patrick Fic
5461aae6f6 Base changes to job upload screen. 2022-05-04 18:13:58 -07:00
Patrick Fic
55fa2a9b8d Merged in release/2022-05-06 (pull request #459)
Release/2022 05 06
2022-05-03 18:47:30 +00:00
Patrick Fic
e348110bdd IO-1857 Resolve time ticket update fix. 2022-05-03 11:33:31 -07:00
Patrick Fic
4ad9ccec47 Merged in release/2022-05-06 (pull request #458)
Add backwards compatibility for log generation.
2022-05-03 18:08:45 +00:00
Patrick Fic
d533423fb6 Add backwards compatibility for log generation. 2022-05-03 11:08:08 -07:00
Patrick Fic
896d82202b Merged in release/2022-05-06 (pull request #457)
Release/2022 05 06
2022-05-03 16:55:47 +00:00
Patrick Fic
9b4e83705b Add department to QBO Payables. 2022-05-03 09:51:54 -07:00
Patrick Fic
4f6d1d27d5 IO-1855 Change QBO changes to server side. 2022-05-02 15:52:05 -07:00
Patrick Fic
2d9de4703b Merged in release/2022-04-29 (pull request #456)
Test
2022-05-02 01:00:23 +00:00
Patrick Fic
2ad4035294 Merged in release/2022-04-29 (pull request #455)
release/2022-04-29

Approved-by: Patrick Fic
2022-04-28 20:41:34 +00:00
Patrick Fic
865f4776d0 IO-233 Mixdata schema updates and API. 2022-04-28 09:54:15 -07:00
Patrick Fic
ad6d1202f2 IO-227 Begin PPG 2 way communication. 2022-04-27 16:03:53 -07:00
Patrick Fic
3db613da7f IO-1847 Allow $0 labor line with type selected. 2022-04-26 16:01:50 -07:00
Patrick Fic
c48b0d7b99 IO-1847 Add blank line if no sales lines for QBD export. 2022-04-26 15:56:03 -07:00
Patrick Fic
15cdcdfbea IO-1846 add prior succesful export indicator. 2022-04-26 15:25:51 -07:00
Patrick Fic
39b7280595 Allow clear of deductible status. 2022-04-26 13:38:16 -07:00
Patrick Fic
273542f93b IO-1532 Add job transition tracking server method. 2022-04-26 13:38:07 -07:00
Patrick Fic
6d01199185 IO-1831 Skip PBS vehicle search with no vin 2022-04-25 14:54:36 -07:00
Patrick Fic
db0ade9791 IO-1680 Order as In House from Job View 2022-04-25 14:40:23 -07:00
Patrick Fic
cbad157ded Merged in release/2022-04-22 (pull request #454)
Release/2022 04 22
2022-04-24 20:29:31 +00:00
Patrick Fic
e65101b4a9 Merged in release/2022-04-22 (pull request #453)
Release/2022 04 22
2022-04-22 22:14:52 +00:00
Patrick Fic
a1f6f2fe4c IO-1477 Update to joblines_status view. 2022-04-22 15:14:02 -07:00
Patrick Fic
f72169d98f Header-Footer template render updates. 2022-04-22 15:12:20 -07:00
Patrick Fic
cd25f079b5 Merged in release/2022-04-22 (pull request #452)
Release/2022 04 22
2022-04-22 19:04:11 +00:00
Patrick Fic
478e03cbe7 Resolve PO and Bills search on PLI screen. 2022-04-22 11:00:35 -07:00
Patrick Fic
c7389cc093 Updated footer minimum. 2022-04-22 10:37:33 -07:00
Patrick Fic
e659204e8f CNR Modifications. 2022-04-22 10:36:00 -07:00
Patrick Fic
427c2b762b Merged in release/2022-04-22 (pull request #451)
release/2022-04-22

Approved-by: Patrick Fic
2022-04-21 19:39:36 +00:00
Patrick Fic
07e6344812 IO-1839 Add filing coversheet landscape. 2022-04-21 12:36:04 -07:00
Patrick Fic
73e50df21b Added missing translations. 2022-04-21 12:13:07 -07:00
Patrick Fic
f50b7ccaee Merged in release/2022-04-22 (pull request #450)
release/2022-04-22

Approved-by: Patrick Fic
2022-04-21 19:12:03 +00:00
Patrick Fic
80f92203ca IO-1834 Add more job info and filtering to scoreboard jobs display. 2022-04-21 12:10:39 -07:00
Patrick Fic
eab9aca3d4 IO-1840 Add to scoreboard from production detail view. 2022-04-21 11:51:47 -07:00
Patrick Fic
49818cc043 Modification to Credits not Received. 2022-04-21 11:45:41 -07:00
Patrick Fic
c67bcd2098 Merged in release/2022-04-22 (pull request #449)
release/2022-04-22

Approved-by: Patrick Fic
2022-04-21 17:05:20 +00:00
Patrick Fic
51843f364b Add footer path. 2022-04-21 10:04:39 -07:00
Patrick Fic
7d8bbe69bd IO-1366 Add additional audit trail items. 2022-04-18 16:18:57 -07:00
Patrick Fic
6998a11a3a Add refresh button to job detail page. 2022-04-18 15:34:12 -07:00
Patrick Fic
65bf81b349 IO-1829 Add additional columns to visual board. 2022-04-18 14:48:56 -07:00
Patrick Fic
65783cde07 Update parts status counter to exclude sublets. 2022-04-18 14:34:36 -07:00
Patrick Fic
74297052d4 IO-1830 Add Parts Status to Production Boards. 2022-04-18 14:28:53 -07:00
Patrick Fic
988c3a9f22 IO-1477 Updated parts queue page. 2022-04-18 13:53:21 -07:00
Patrick Fic
c08713bfbe IO-1650 Added ready jobs screen. 2022-04-18 13:07:52 -07:00
Patrick Fic
a10b5a2ee0 IO-1816 Refactor main page. 2022-04-18 11:37:56 -07:00
Patrick Fic
920ebdde42 Merged in release/2022-04-15 (pull request #448)
Release/2022 04 15
2022-04-14 17:48:45 +00:00
Patrick Fic
6c6cce7da7 Merged in release/2022-04-15 (pull request #447)
Resolve third party payer template issue.
2022-04-14 17:48:23 +00:00
Patrick Fic
e31a7eb65f Resolve third party payer template issue. 2022-04-14 10:23:09 -07:00
Patrick Fic
e273481a25 Merged in release/2022-04-15 (pull request #446)
release/2022-04-15

Approved-by: Patrick Fic
2022-04-14 00:30:22 +00:00
Patrick Fic
4b34cd2a2e Merged in release/2022-04-15 (pull request #445)
release/2022-04-15

Approved-by: Patrick Fic
2022-04-13 22:13:29 +00:00
Patrick Fic
f54883164c Merged in release/2022-04-15 (pull request #444)
Release/2022 04 15
2022-04-12 22:51:22 +00:00
Patrick Fic
82e7e5d910 Merged in release/2022-04-08 (pull request #442)
release/2022-04-08

Approved-by: Patrick Fic
2022-04-08 00:40:14 +00:00
Patrick Fic
6831184e33 Merged in release/2022-04-02 (pull request #438)
release/2022-04-02

Approved-by: Patrick Fic
2022-03-31 21:44:14 +00:00
Patrick Fic
a39a79102d Merged in release/2022-04-02 (pull request #437)
release/2022-04-02

Approved-by: Patrick Fic
2022-03-31 21:26:41 +00:00
Patrick Fic
95fff2acc4 Merged in release/2022-04-02 (pull request #436)
Release/2022 04 02
2022-03-31 19:51:22 +00:00
Patrick Fic
64d1c87dbd Merged in release/2022-04-02 (pull request #435)
release/2022-04-02

Approved-by: Patrick Fic
2022-03-25 21:42:50 +00:00
Patrick Fic
47dc00ff6e Merged in release/2022-03-25 (pull request #432)
release/2022-03-25

Approved-by: Patrick Fic
2022-03-24 21:40:43 +00:00
Patrick Fic
a2fbdd6a32 Merged in release/2022-03-25 (pull request #431)
release/2022-03-25

Approved-by: Patrick Fic
2022-03-24 21:18:15 +00:00
Patrick Fic
e46240f543 Merged in release/2022-03-25 (pull request #430)
release/2022-03-25

Approved-by: Patrick Fic
2022-03-22 19:41:39 +00:00
Patrick Fic
8882481006 Merged in release/2022-03-25 (pull request #429)
release/2022-03-25

Approved-by: Patrick Fic
2022-03-22 17:12:08 +00:00
Patrick Fic
79f66294d9 Merged in release/2022-03-25 (pull request #428)
release/2022-03-25

Approved-by: Patrick Fic
2022-03-21 17:53:26 +00:00
Patrick Fic
f9f1dbaf24 Merged in release/2022-03-18 (pull request #425)
Release/2022 03 18
2022-03-17 22:48:36 +00:00
Patrick Fic
ac8ae34e1d Merged in release/2022-03-18 (pull request #423)
Release/2022 03 18
2022-03-16 21:01:48 +00:00
Patrick Fic
e72925e34e Merged in release/2022-03-18 (pull request #422)
Release/2022 03 18
2022-03-16 20:54:13 +00:00
Patrick Fic
9a6be8a71e Merged in release/2022-03-18 (pull request #421)
Release/2022 03 18
2022-03-14 20:49:42 +00:00
Patrick Fic
11b20c54c2 Merged in release/2022-03-11 (pull request #419)
IO-1776 Mechanical authorization added to print center.

Approved-by: Patrick Fic
2022-03-11 16:53:26 +00:00
Patrick Fic
ac7dcdd6a9 Merged in release/2022-03-11 (pull request #418)
release/2022-03-11

Approved-by: Patrick Fic
2022-03-10 19:02:58 +00:00
Patrick Fic
20abda83f3 Merged in release/2022-03-11 (pull request #414)
release/2022-03-11

Approved-by: Patrick Fic
2022-03-07 22:47:56 +00:00
Patrick Fic
826a57c012 Merged in release/2022-03-04 (pull request #412)
release/2022-03-04

Approved-by: Patrick Fic
2022-03-04 18:13:33 +00:00
Patrick Fic
1a0c5c44ba Merged in release/2022-03-04 (pull request #411)
release/2022-03-04

Approved-by: Patrick Fic
2022-03-03 20:39:40 +00:00
Patrick Fic
9e8eb35792 Merged in release/2022-03-04 (pull request #409)
release/2022-03-04

Approved-by: Patrick Fic
2022-03-03 17:07:03 +00:00
Patrick Fic
49618ac6ef Merged in release/2022-03-04 (pull request #408)
release/2022-03-04

Approved-by: Patrick Fic
2022-03-02 21:00:10 +00:00
Patrick Fic
ad8c13f346 Merged in release/2022-03-04 (pull request #406)
release/2022-03-04

Approved-by: Patrick Fic
2022-03-02 01:27:33 +00:00
Patrick Fic
e2f3d6ef83 Merged in release/2022-02-18 (pull request #404)
Updated task scheduler.

Approved-by: Patrick Fic
2022-02-18 20:59:32 +00:00
Patrick Fic
a859990110 Merged in release/2022-02-18 (pull request #403)
release/2022-02-18

Approved-by: Patrick Fic
2022-02-18 01:14:54 +00:00
Patrick Fic
1286b72f2c Merged in release/2022-02-18 (pull request #402)
release/2022-02-18

Approved-by: Patrick Fic
2022-02-18 01:08:43 +00:00
Patrick Fic
5a42cafe8a Merged in release/2022-02-18 (pull request #401)
release/2022-02-18

Approved-by: Patrick Fic
2022-02-18 00:30:33 +00:00
Patrick Fic
1fd9bfc34c Merged in release/2022-02-18 (pull request #400)
CI Fix.
2022-02-17 01:12:36 +00:00
Patrick Fic
8e470376fd Merged in release/2022-02-18 (pull request #399)
IO-1738 PVRT Update
2022-02-17 00:46:15 +00:00
Patrick Fic
171e11c9f0 Merged in release/2022-02-18 (pull request #398)
Release/2022 02 18
2022-02-16 23:31:51 +00:00
Patrick Fic
e56664bbc6 Merge branch 'release/2022-02-18' into test 2022-02-15 16:03:52 -08:00
Patrick Fic
0f16f616d4 Merged in release/2022-02-18 (pull request #396)
Release/2022 02 18
2022-02-15 21:47:22 +00:00
Patrick Fic
2fc2e0f02e Merged in release/2022-02-18 (pull request #394)
Resolve Date Picker Issues.
2022-02-15 17:31:05 +00:00
Patrick Fic
3cd2445098 Merged in release/2022-02-18 (pull request #391)
Release/2022 02 18
2022-02-15 03:35:19 +00:00
Patrick Fic
1742f66312 Merged in hotfix/2022-02-14 (pull request #387)
Resolve bill update error.
2022-02-14 21:03:44 +00:00
494 changed files with 88145 additions and 149148 deletions

180
.circleci/config.yml Normal file
View File

@@ -0,0 +1,180 @@
version: 2.1
orbs:
#snyk: snyk/snyk@0.0.8
#cypress: cypress-io/cypress@1.23.0
aws-s3: circleci/aws-s3@2.0.0
eb: circleci/aws-elastic-beanstalk@1.0.2
jira: circleci/jira@1.3.1
jobs:
api-deploy:
docker:
- image: "cimg/base:stable"
steps:
- checkout
- eb/setup
- run:
command: |
eb init imex-online-production-api -r ca-central-1 -p "Node.js 16 running on 64bit Amazon Linux 2"
eb status --verbose
eb deploy
eb status
- jira/notify
hasura-migrate:
docker:
- image: cimg/node:16.15.0
parameters:
secret:
type: string
default: $HASURA_PROD_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
npm install hasura-cli -g
hasura migrate apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.imex.online/ --admin-secret << parameters.secret >>
app-build:
docker:
- image: cimg/node:16.15.0
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- run: yarn run build
- aws-s3/sync:
from: build
to: "s3://imex-online-production/"
- jira/notify
test-hasura-migrate:
docker:
- image: cimg/node:16.15.0
parameters:
secret:
type: string
default: $HASURA_TEST_SECRET
working_directory: ~/repo/hasura
steps:
- checkout:
path: ~/repo
- run:
name: Execute migration
command: |
npm install hasura-cli -g
echo ${HASURA_TEST_SECRET}
hasura migrate apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
hasura metadata apply --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
hasura metadata reload --endpoint https://db.test.bodyshop.app/ --admin-secret << parameters.secret >>
test-app-build:
docker:
- image: cimg/node:16.15.0
working_directory: ~/repo/client
steps:
- checkout:
path: ~/repo
- restore_cache:
name: Restore Yarn Package Cache
keys:
- yarn-packages-{{ checksum "yarn.lock" }}
- run:
name: Install Dependencies
command: yarn install --frozen-lockfile --cache-folder ~/.cache/yarn
- save_cache:
name: Save Yarn Package Cache
key: yarn-packages-{{ checksum "yarn.lock" }}
paths:
- ~/.cache/yarn
- run: yarn run build:test
- aws-s3/sync:
from: build
to: "s3://imex-online-test/"
- jira/notify
admin-app-build:
docker:
- image: cimg/node:16.15.0
working_directory: ~/repo/admin
steps:
- checkout:
path: ~/repo
- restore_cache:
keys:
- v1-dependencies-{{ checksum "package.json" }}
# fallback to using the latest cache if no exact match is found
- v1-dependencies-
- run: npm i
- save_cache:
paths:
- node_modules
- ~/.npm
- ~/.cache
key: v1-dependencies-{{ checksum "package.json" }}
- run: npm run build
- aws-s3/sync:
from: build
to: "s3://adm.imex.online/"
workflows:
deploy_and_build:
jobs:
- api-deploy:
filters:
branches:
only: master
- app-build:
filters:
branches:
only: master
- hasura-migrate:
secret: ${HASURA_PROD_SECRET}
filters:
branches:
only: master
- test-app-build:
filters:
branches:
only: test
- test-hasura-migrate:
secret: ${HASURA_TEST_SECRET}
filters:
branches:
only: test
#- admin-app-build:
#filters:
#branches:
#only: master

View File

@@ -16,6 +16,5 @@
"rules": {
"no-console": "off"
},
"settings": {},
"plugins": ["cypress"]
"settings": {}
}

5
.gitignore vendored
View File

@@ -113,3 +113,8 @@ firebase/.env
!.elasticbeanstalk/*.cfg.yml
!.elasticbeanstalk/*.global.yml
logs/oAuthClient-log.log
.node-persist/**
/*.env.*

View File

@@ -19,3 +19,6 @@ npx deadfile ./src/index.js --exclude build templates
hasura migrate create "Init" --from-server --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
hasura migrate apply --version "1620771761757" --skip-execution --endpoint https://db.imex.online/ --admin-secret 'Production-ImEXOnline!@#'
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

View File

@@ -1,68 +0,0 @@
This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
## Available Scripts
In the project directory, you can run:
### `yarn start`
Runs the app in the development mode.<br />
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
The page will reload if you make edits.<br />
You will also see any lint errors in the console.
### `yarn test`
Launches the test runner in the interactive watch mode.<br />
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn build`
Builds the app for production to the `build` folder.<br />
It correctly bundles React in production mode and optimizes the build for the best performance.
The build is minified and the filenames include the hashes.<br />
Your app is ready to be deployed!
See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
### `yarn eject`
**Note: this is a one-way operation. Once you `eject`, you cant go back!**
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Learn More
You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
To learn React, check out the [React documentation](https://reactjs.org/).
### Code Splitting
This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
### Analyzing the Bundle Size
This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
### Making a Progressive Web App
This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
### Advanced Configuration
This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
### Deployment
This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
### `yarn build` fails to minify
This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify

18103
admin/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +0,0 @@
{
"name": "admin",
"version": "0.1.0",
"private": true,
"dependencies": {
"@apollo/client": "^3.3.15",
"@testing-library/jest-dom": "^5.11.10",
"@testing-library/react": "^11.2.6",
"@testing-library/user-event": "^13.1.5",
"@types/prop-types": "^15.7.3",
"apollo-boost": "^0.4.9",
"apollo-link-context": "^1.0.20",
"apollo-link-logger": "^2.0.0",
"dotenv": "^8.2.0",
"firebase": "^8.4.1",
"graphql": "^15.4.0",
"prop-types": "^15.7.2",
"ra-data-hasura-graphql": "^0.1.13",
"react": "^17.0.1",
"react-admin": "^3.14.4",
"react-dom": "^17.0.1",
"react-icons": "^4.2.0",
"react-scripts": "4.0.3",
"sass": "^1.32.10"
},
"scripts": {
"start": "set PORT=3001 && react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

View File

@@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.png" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>ImEX Online - ADMIN</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@@ -1,25 +0,0 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@@ -1,12 +0,0 @@
import React from "react";
import AdminRoot from "../components/admin-root/admin-root.component";
import "./App.css";
function App() {
return (
<div className="App">
<AdminRoot />
</div>
);
}
export default App;

View File

@@ -1,9 +0,0 @@
import React from 'react';
import { render } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
const { getByText } = render(<App />);
const linkElement = getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});

View File

@@ -1,7 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,283 +0,0 @@
import { ApolloClient, ApolloProvider, InMemoryCache } from "@apollo/client";
import { ApolloLink } from "apollo-boost";
import { setContext } from "apollo-link-context";
import { HttpLink } from "apollo-link-http";
import apolloLogger from "apollo-link-logger";
import buildHasuraProvider from "ra-data-hasura-graphql";
import React, { Component } from "react";
import {
Admin,
EditGuesser,
ListGuesser,
Resource,
ShowGuesser,
} from "react-admin";
import { FaFileInvoiceDollar } from "react-icons/fa";
import CircularProgress from "@material-ui/core/CircularProgress";
import { auth } from "../../firebase/admin-firebase-utils";
import authProvider from "../auth-provider/auth-provider";
import JoblinesCreate from "../joblines/joblines.create";
import JoblinesEdit from "../joblines/joblines.edit";
import JoblinesList from "../joblines/joblines.list";
import JoblinesShow from "../joblines/joblines.show";
import JobsCreate from "../jobs/jobs.create";
import JobsEdit from "../jobs/jobs.edit";
import JobsList from "../jobs/jobs.list";
import JobsShow from "../jobs/jobs.show";
const httpLink = new HttpLink({
uri: process.env.REACT_APP_GRAPHQL_ENDPOINT,
headers: {
"x-hasura-admin-secret": `Dev-BodyShopApp!`,
// 'Authorization': `Bearer xxxx`,
},
});
const authLink = setContext((_, { headers }) => {
return (
auth.currentUser &&
auth.currentUser.getIdToken().then((token) => {
if (token) {
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
},
};
} else {
return { headers };
}
})
);
});
const middlewares = [];
if (process.env.NODE_ENV === "development") {
middlewares.push(apolloLogger);
}
middlewares.push(authLink.concat(httpLink));
const client = new ApolloClient({
link: ApolloLink.from(middlewares),
cache: new InMemoryCache(),
});
// const client = new ApolloClient({
// uri: process.env.REACT_APP_GRAPHQL_ENDPOINT,
// cache: new InMemoryCache(),
// headers: {
// "x-hasura-admin-secret": `Dev-BodyShopApp!`,
// // 'Authorization': `Bearer xxxx`,
// },
// });
class AdminRoot extends Component {
constructor() {
super();
this.state = { dataProvider: null };
}
componentDidMount() {
buildHasuraProvider({
client,
}).then((dataProvider) => this.setState({ dataProvider }));
}
render() {
const { dataProvider } = this.state;
if (!dataProvider) {
return (
<div>
<CircularProgress />
</div>
);
}
return (
<ApolloProvider client={client}>
<Admin dataProvider={dataProvider} authProvider={authProvider}>
<Resource
icon={FaFileInvoiceDollar}
name="jobs"
list={JobsList}
edit={JobsEdit}
create={JobsCreate}
show={JobsShow}
/>
<Resource
name="joblines"
list={JoblinesList}
edit={JoblinesEdit}
create={JoblinesCreate}
show={JoblinesShow}
/>
<Resource
name="bodyshops"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="owners"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="vehicles"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="appointments"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="available_jobs"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="cccontracts"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="conversations"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="counters"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="courtesycars"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="csi"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="csiquestions"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="documents"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="employees"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="invoicelines"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="invoices"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="job_conversations"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="masterdata"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="messages"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="notes"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="parts_order_lines"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="parts_orders"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="payments"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="scoreboard"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="templates"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="timetickets"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="users"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
<Resource
name="vendors"
list={ListGuesser}
edit={EditGuesser}
show={ShowGuesser}
/>
</Admin>
</ApolloProvider>
);
}
}
export default AdminRoot;

View File

@@ -1,39 +0,0 @@
import { auth, getCurrentUser } from "../../firebase/admin-firebase-utils";
const authProvider = {
login: async ({ username, password }) => {
console.log(username, password);
try {
const { user } = await auth.signInWithEmailAndPassword(
username,
password
);
const token = await user.getIdToken();
localStorage.setItem("token", token);
return Promise.resolve();
} catch (error) {
console.log("error", error);
return Promise.reject();
}
},
logout: async (params) => {
await auth.signOut();
localStorage.removeItem("token");
return Promise.resolve();
},
checkAuth: async (params) => {
const user = await getCurrentUser();
if (!!user) {
return Promise.resolve();
} else {
return Promise.reject();
}
},
checkError: (error) => {
return Promise.resolve();
},
getPermissions: (params) => {
return Promise.resolve();
},
};
export default authProvider;

View File

@@ -1,26 +0,0 @@
import React from "react";
import {
Create,
NumberInput, SimpleForm,
TextInput
} from "react-admin";
const JoblinesCreate = (props) => (
<Create {...props}>
<SimpleForm>
<TextInput source="line_ref" />
<TextInput source="line_ind" />
<NumberInput source="db_price" />
<NumberInput source="act_price" />
<NumberInput source="part_qty" />
<NumberInput source="mod_lb_hrs" />
<TextInput source="mod_lbr_type" />
<TextInput source="lbr_op" />
</SimpleForm>
</Create>
);
export default JoblinesCreate;

View File

@@ -1,70 +0,0 @@
import React from "react";
import {
BooleanInput,
DateField,
Edit,
NumberInput,
SimpleForm,
TextInput,
} from "react-admin";
const JoblinesEdit = (props) => (
<Edit {...props}>
<SimpleForm>
<TextInput source="id" />
<DateField showTime source="created_at" />
<DateField showTime source="updated_at" />
<TextInput source="jobid" />
<NumberInput source="unq_seq" />
<NumberInput source="line_ind" />
<TextInput source="line_desc" />
<TextInput source="part_type" />
<TextInput source="oem_partno" />
<TextInput source="est_seq" />
<TextInput source="db_ref" />
<TextInput source="line_ref" />
<BooleanInput source="tax_part" />
<NumberInput source="db_price" />
<NumberInput source="act_price" />
<NumberInput source="part_qty" />
<TextInput source="alt_partno" />
<TextInput source="mod_lbr_ty" />
<NumberInput source="db_hrs" />
<NumberInput source="mod_lb_hrs" />
<TextInput source="lbr_op" />
<NumberInput source="lbr_amt" />
<BooleanInput source="glass_flag" />
<TextInput source="price_inc" />
<TextInput source="alt_part_i" />
<TextInput source="price_j" />
<TextInput source="cert_part" />
<TextInput source="alt_co_id" />
<TextInput source="alt_overrd" />
<TextInput source="alt_partm" />
<TextInput source="prt_dsmk_p" />
<TextInput source="prt_dsmk_m" />
<TextInput source="lbr_inc" />
<TextInput source="lbr_hrs_j" />
<TextInput source="lbr_typ_j" />
<TextInput source="lbr_op_j" />
<TextInput source="paint_stg" />
<TextInput source="paint_tone" />
<TextInput source="lbr_tax" />
<NumberInput source="misc_amt" />
<TextInput source="misc_sublt" />
<TextInput source="misc_tax" />
<TextInput source="bett_type" />
<NumberInput source="bett_pctg" />
<NumberInput source="bett_amt" />
<TextInput source="bett_tax" />
<TextInput source="op_code_desc" />
<TextInput source="status" />
<TextInput source="removed" />
<NumberInput source="line_no" />
<TextInput source="notes" />
<TextInput source='"location"' />
</SimpleForm>
</Edit>
);
export default JoblinesEdit;

View File

@@ -1,29 +0,0 @@
import React from "react";
import {
Datagrid, List,
NumberField,
ReferenceField, TextField
} from "react-admin";
const JoblinesList = (props) => (
<List {...props}>
<Datagrid rowClick="edit">
<ReferenceField source="jobid" reference="jobs">
<TextField source="ro_number" />
</ReferenceField>
<TextField source="line_ref" />
<TextField source="line_ind" />
<NumberField source="db_price" />
<NumberField source="act_price" />
<NumberField source="part_qty" />
<NumberField source="mod_lb_hrs" />
<TextField source="mod_lbr_type" />
<TextField source="lbr_op" />
</Datagrid>
</List>
);
export default JoblinesList;

View File

@@ -1,24 +0,0 @@
import React from "react";
import {
NumberInput, Show,
SimpleShowLayout,
TextInput
} from "react-admin";
const JoblinesShow = (props) => (
<Show {...props}>
<SimpleShowLayout>
<TextInput source="line_ref" />
<TextInput source="line_ind" />
<NumberInput source="db_price" />
<NumberInput source="act_price" />
<NumberInput source="part_qty" />
<NumberInput source="mod_lb_hrs" />
<TextInput source="mod_lbr_type" />
<TextInput source="lbr_op" />
</SimpleShowLayout>
</Show>
);
export default JoblinesShow;

View File

@@ -1,17 +0,0 @@
import React from "react";
import { Create, EmailField, SimpleForm, TextInput } from "react-admin";
const JobsCreate = (props) => (
<Create {...props}>
<SimpleForm>
<TextInput source="ro_number" />
<TextInput source="ownr_fn" />
<TextInput source="ownr_ln" />
<TextInput source="converted" />
<EmailField source="ownr_ea" />
</SimpleForm>
</Create>
);
export default JobsCreate;

View File

@@ -1,316 +0,0 @@
import React from "react";
//@ts-ignore
import {
AutocompleteInput,
BooleanInput,
Edit,
FormTab,
NumberInput,
ReferenceInput,
SelectInput,
SimpleForm,
TabbedForm,
TextInput,
} from "react-admin";
const JobsEdit = (props) => (
<Edit {...props}>
<TabbedForm>
<FormTab label="Job Info">
<SimpleForm>
<ReferenceInput label="Shopid" source="shopid" reference="bodyshops">
<SelectInput disabled optionText="shopname" />
</ReferenceInput>
<TextInput source="ro_number" />
<ReferenceInput label="Owner ID" source="ownerid" reference="owners">
<AutocompleteInput
matchSuggestion={(filter, choice) =>
choice.ownr_fn &&
choice.ownr_fn.toLowerCase().includes(filter.toLowerCase())
}
optionText={(record) =>
`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
} (${record.ownr_ph1 || ""})`
}
/>
</ReferenceInput>
<ReferenceInput
label="Vehicle Id"
source="vehicleid"
reference="vehicles"
>
<SelectInput optionText="v_vin" />
</ReferenceInput>
<ReferenceInput label="Shopid" source="shopid" reference="bodyshops">
<SelectInput disabled optionText="shopname" />
</ReferenceInput>
<TextInput source="ro_number" />
<ReferenceInput label="Owner ID" source="ownerid" reference="owners">
<AutocompleteInput
matchSuggestion={(filter, choice) =>
choice.ownr_fn &&
choice.ownr_fn.toLowerCase().includes(filter.toLowerCase())
}
optionText={(record) =>
`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || ""
} (${record.ownr_ph1 || ""})`
}
/>
</ReferenceInput>
<ReferenceInput
label="Vehicle Id"
source="vehicleid"
reference="vehicles"
>
<SelectInput optionText="v_vin" />
</ReferenceInput>
<BooleanInput source="inproduction" />
<BooleanInput source="converted" />
<TextInput disabled source="id" />
<TextInput disabled source="created_at" />
<TextInput disabled source="updated_at" />
</SimpleForm>
</FormTab>
<FormTab label="Labor Rates">
<NumberInput source="labor_rate_id" />
<NumberInput source="labor_rate_desc" />
<NumberInput source="rate_lab" />
<NumberInput source="rate_lad" />
<NumberInput source="rate_lae" />
<NumberInput source="rate_lar" />
<NumberInput source="rate_las" />
<NumberInput source="rate_laf" />
<NumberInput source="rate_lam" />
<NumberInput source="rate_lag" />
<NumberInput source="rate_atp" />
<NumberInput source="rate_lau" />
<NumberInput source="rate_la1" />
<NumberInput source="rate_la2" />
<NumberInput source="rate_la3" />
<NumberInput source="rate_la4" />
<NumberInput source="rate_mapa" />
<NumberInput source="rate_mash" />
<NumberInput source="rate_mahw" />
<NumberInput source="rate_ma2s" />
<NumberInput source="rate_ma3s" />
<NumberInput source="rate_ma2t" />
<NumberInput source="rate_mabl" />
<NumberInput source="rate_macs" />
<NumberInput source="rate_matd" />
<NumberInput source="federal_tax_rate" />
<NumberInput source="state_tax_rate" />
<NumberInput source="local_tax_rate" />
</FormTab>
<FormTab label="Dates">
<TextInput source="scheduled_in" />
<TextInput source="actual_in" />
<TextInput source="scheduled_completion" />
<TextInput source="actual_completion" />
<TextInput source="scheduled_delivery" />
<TextInput source="actual_delivery" />
<TextInput source="invoice_date" />
<TextInput source="date_estimated" />
<TextInput source="date_open" />
<TextInput source="date_scheduled" />
<TextInput source="date_invoiced" />
<TextInput source="date_exported" />
</FormTab>
<FormTab label="Insurance info">
<TextInput source="est_co_nm" />
<TextInput source="est_addr1" />
<TextInput source="est_addr2" />
<TextInput source="est_city" />
<TextInput source="est_st" />
<TextInput source="est_zip" />
<TextInput source="est_ctry" />
<TextInput source="est_ph1" />
<TextInput source="est_ea" />
<TextInput source="est_ct_ln" />
<TextInput source="est_ct_fn" />
<TextInput source="regie_number" />
<TextInput source="statusid" />
<TextInput source="ins_co_id" />
<TextInput source="ins_co_nm" />
<TextInput source="ins_addr1" />
<TextInput source="ins_addr2" />
<TextInput source="ins_city" />
<TextInput source="ins_st" />
<TextInput source="ins_zip" />
<TextInput source="ins_ctry" />
<TextInput source="ins_ph1" />
<TextInput source="ins_ph1x" />
<TextInput source="ins_ph2" />
<TextInput source="ins_ph2x" />
<TextInput source="ins_fax" />
<TextInput source="ins_faxx" />
<TextInput source="ins_ct_ln" />
<TextInput source="ins_ct_fn" />
<TextInput source="ins_title" />
<TextInput source="ins_ct_ph" />
<TextInput source="ins_ct_phx" />
<TextInput source="ins_ea" />
<TextInput source="ins_memo" />
<TextInput source="policy_no" />
<TextInput source="ded_amt" />
<TextInput source="ded_status" />
<TextInput source="asgn_no" />
<TextInput source="asgn_date" />
<TextInput source="asgn_type" />
<TextInput source="clm_no" />
<TextInput source="clm_ofc_id" />
<TextInput source="agt_co_id" />
<TextInput source="agt_co_nm" />
<TextInput source="agt_addr1" />
<TextInput source="agt_addr2" />
<TextInput source="agt_city" />
<TextInput source="agt_st" />
<TextInput source="agt_zip" />
<TextInput source="agt_ctry" />
<TextInput source="agt_ph1" />
<TextInput source="agt_ph1x" />
<TextInput source="agt_ph2" />
<TextInput source="agt_ph2x" />
<TextInput source="agt_fax" />
<TextInput source="agt_faxx" />
<TextInput source="agt_ct_ln" />
<TextInput source="agt_ct_fn" />
<TextInput source="agt_ct_ph" />
<TextInput source="agt_ct_phx" />
<TextInput source="agt_ea" />
<TextInput source="agt_lic_no" />
<TextInput source="loss_type" />
<TextInput source="loss_desc" />
<TextInput source="theft_ind" />
<TextInput source="cat_no" />
<TextInput source="tlos_ind" />
<TextInput source="ciecaid" />
<TextInput source="loss_date" />
<TextInput source="clm_ofc_nm" />
<TextInput source="clm_addr1" />
<TextInput source="clm_addr2" />
<TextInput source="clm_city" />
<TextInput source="clm_st" />
<TextInput source="clm_zip" />
<TextInput source="clm_ctry" />
<TextInput source="clm_ph1" />
<TextInput source="clm_ph1x" />
<TextInput source="clm_ph2" />
<TextInput source="clm_ph2x" />
<TextInput source="clm_fax" />
<TextInput source="clm_faxx" />
<TextInput source="clm_ct_ln" />
<TextInput source="clm_ct_fn" />
<TextInput source="clm_title" />
<TextInput source="clm_ct_ph" />
<TextInput source="clm_ct_phx" />
<TextInput source="clm_ea" />
<TextInput source="payee_nms" />
<TextInput source="pay_type" />
<TextInput source="pay_date" />
<TextInput source="pay_chknm" />
<TextInput source="pay_amt" />
</FormTab>
<FormTab label="Owner Data on Job">
<TextInput source="cust_pr" />
<TextInput source="insd_ln" />
<TextInput source="insd_fn" />
<TextInput source="insd_title" />
<TextInput source="insd_co_nm" />
<TextInput source="insd_addr1" />
<TextInput source="insd_addr2" />
<TextInput source="insd_city" />
<TextInput source="insd_st" />
<TextInput source="insd_zip" />
<TextInput source="insd_ctry" />
<TextInput source="insd_ph1" />
<TextInput source="insd_ph1x" />
<TextInput source="insd_ph2" />
<TextInput source="insd_ph2x" />
<TextInput source="insd_fax" />
<TextInput source="insd_faxx" />
<TextInput source="insd_ea" />
<TextInput source="ownr_ln" />
<TextInput source="ownr_fn" />
<TextInput source="ownr_title" />
<TextInput source="ownr_co_nm" />
<TextInput source="ownr_addr1" />
<TextInput source="ownr_addr2" />
<TextInput source="ownr_city" />
<TextInput source="ownr_st" />
<TextInput source="ownr_zip" />
<TextInput source="ownr_ctry" />
<TextInput source="ownr_ph1" />
<TextInput source="ownr_ph1x" />
<TextInput source="ownr_ph2" />
<TextInput source="ownr_ph2x" />
<TextInput source="ownr_fax" />
<TextInput source="ownr_faxx" />
<TextInput source="ownr_ea" />
</FormTab>
<FormTab label="Financial">
<TextInput source="clm_total" />
<TextInput source="owner_owing" />
</FormTab>
<FormTab label="Other">
<TextInput source="area_of_damage" />
<TextInput source="loss_cat" />
<TextInput source="special_coverage_policy" />
<TextInput source="csr" />
<TextInput source="po_number" />
<TextInput source="unit_number" />
<TextInput source="kmin" />
<TextInput source="kmout" />
<TextInput source="referral_source" />
<TextInput source="selling_dealer" />
<TextInput source="servicing_dealer" />
<TextInput source="servicing_dealer_contact" />
<TextInput source="selling_dealer_contact" />
<TextInput source="depreciation_taxes" />
<TextInput source="federal_tax_payable" />
<TextInput source="other_amount_payable" />
<TextInput source="towing_payable" />
<TextInput source="storage_payable" />
<TextInput source="adjustment_bottom_line" />
<TextInput source="tax_pstthr" />
<TextInput source="tax_tow_rt" />
<TextInput source="tax_sub_rt" />
<TextInput source="tax_paint_mat_rt" />
<TextInput source="tax_levies_rt" />
<TextInput source="tax_prethr" />
<TextInput source="tax_thramt" />
<TextInput source="tax_str_rt" />
<TextInput source="tax_lbr_rt" />
<TextInput source="adj_g_disc" />
<TextInput source="adj_towdis" />
<TextInput source="adj_strdis" />
<TextInput source="tax_predis" />
<TextInput source="rate_laa" />
<TextInput source="status" />
<TextInput source="cieca_stl" />
<TextInput source="g_bett_amt" />
<TextInput source="cieca_ttl" />
<TextInput source="plate_no" />
<TextInput source="plate_st" />
<TextInput source="v_vin" />
<TextInput source="v_model_yr" />
<TextInput source="v_model_desc" />
<TextInput source="v_make_desc" />
<TextInput source="v_color" />
<TextInput source="parts_tax_rates" />
<TextInput source="job_totals" />
<TextInput source="production_vars" />
<TextInput source="intakechecklist" />
<TextInput source="invoice_allocation" />
<TextInput source="kanbanparent" />
<TextInput source="employee_body" />
<TextInput source="employee_refinish" />
<TextInput source="employee_prep" />
</FormTab>
</TabbedForm>
</Edit>
);
export default JobsEdit;

View File

@@ -1,69 +0,0 @@
import { useQuery } from "@apollo/client";
import CircularProgress from "@material-ui/core/CircularProgress";
import React from "react";
import {
Datagrid,
Filter,
List,
ReferenceField,
SelectInput,
TextField,
TextInput,
} from "react-admin";
import { QUERY_ALL_SHOPS } from "../../graphql/admin.shop.queries";
const JobsList = (props) => (
<List filters={<JobsFilter />} {...props}>
<Datagrid rowClick="edit">
<TextField source="id" label="Job ID" />
<ReferenceField source="shopid" reference="bodyshops" label="Shop Name">
<TextField source="shopname" />
</ReferenceField>
<TextField source="ro_number" label="RO Number" />
<TextField source="ownr_fn" label="Owner FN" />
<TextField source="ownr_ln" label="Owner LN" />
<TextField source="ownr_co_nm" label="Owner CO" />
<ReferenceField source="ownerid" reference="owners" label="Owner Record">
<TextField source="id" />
</ReferenceField>
<TextField source="v_model_yr" label="Year" />
<TextField source="v_make_desc" label="Make" />
<TextField source="v_model_desc" label="Model" />
<ReferenceField
source="vehicleid"
reference="vehicles"
label="Vehicle ID"
>
<TextField source="id" />
</ReferenceField>
</Datagrid>
</List>
);
const JobsFilter = (props) => {
const { loading, error, data } = useQuery(QUERY_ALL_SHOPS, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
if (loading) return <CircularProgress />;
if (error) return JSON.stringify(error);
return (
<Filter {...props}>
<TextInput label="RO Number" source="ro_number" />
<TextInput label="Job ID" source="id" />
<SelectInput
source="shopid"
label="Bodyshop"
choices={data.bodyshops.map((b) => {
return { id: b.id, name: b.shopname };
})}
/>
</Filter>
);
};
export default JobsList;

View File

@@ -1,280 +0,0 @@
import React from "react";
import {
Datagrid,
EditButton,
NumberField,
ReferenceManyField,
Show,
Tab,
TabbedShowLayout,
TextField,
} from "react-admin";
const JobsShow = (props) => (
<Show {...props}>
<TabbedShowLayout>
<Tab label="summary">
<TextField source="id" />
<TextField source="created_at" />
<TextField source="updated_at" />
<TextField source="shopid" />
<TextField source="ro_number" />
<TextField source="ownerid" />
<TextField source="vehicleid" />
</Tab>
<Tab label="Job Lines">
<ReferenceManyField
reference="joblines"
target="jobid"
label="Job Lines"
>
<Datagrid>
<TextField source="id" />
<TextField source="line_ref" />
<TextField source="line_desc" />
<TextField source="line_ind" />
<NumberField source="db_price" />
<NumberField source="act_price" />
<NumberField source="part_qty" />
<NumberField source="mod_lb_hrs" />
<TextField source="mod_lbr_type" />
<TextField source="lbr_op" />
<EditButton />
</Datagrid>
</ReferenceManyField>
</Tab>
<Tab label="other">
<TextField source="labor_rate_id" />
<TextField source="labor_rate_desc" />
<TextField source="rate_lab" />
<TextField source="rate_lad" />
<TextField source="rate_lae" />
<TextField source="rate_lar" />
<TextField source="rate_las" />
<TextField source="rate_laf" />
<TextField source="rate_lam" />
<TextField source="rate_lag" />
<TextField source="rate_atp" />
<TextField source="rate_lau" />
<TextField source="rate_la1" />
<TextField source="rate_la2" />
<TextField source="rate_la3" />
<TextField source="rate_la4" />
<TextField source="rate_mapa" />
<TextField source="rate_mash" />
<TextField source="rate_mahw" />
<TextField source="rate_ma2s" />
<TextField source="rate_ma3s" />
<TextField source="rate_ma2t" />
<TextField source="rate_mabl" />
<TextField source="rate_macs" />
<TextField source="rate_matd" />
<TextField source="federal_tax_rate" />
<TextField source="state_tax_rate" />
<TextField source="local_tax_rate" />
<TextField source="est_co_nm" />
<TextField source="est_addr1" />
<TextField source="est_addr2" />
<TextField source="est_city" />
<TextField source="est_st" />
<TextField source="est_zip" />
<TextField source="est_ctry" />
<TextField source="est_ph1" />
<TextField source="est_ea" />
<TextField source="est_ct_ln" />
<TextField source="est_ct_fn" />
<TextField source="scheduled_in" />
<TextField source="actual_in" />
<TextField source="scheduled_completion" />
<TextField source="actual_completion" />
<TextField source="scheduled_delivery" />
<TextField source="actual_delivery" />
<TextField source="regie_number" />
<TextField source="invoice_date" />
<TextField source="inproduction" />
<TextField source="statusid" />
<TextField source="ins_co_id" />
<TextField source="ins_co_nm" />
<TextField source="ins_addr1" />
<TextField source="ins_addr2" />
<TextField source="ins_city" />
<TextField source="ins_st" />
<TextField source="ins_zip" />
<TextField source="ins_ctry" />
<TextField source="ins_ph1" />
<TextField source="ins_ph1x" />
<TextField source="ins_ph2" />
<TextField source="ins_ph2x" />
<TextField source="ins_fax" />
<TextField source="ins_faxx" />
<TextField source="ins_ct_ln" />
<TextField source="ins_ct_fn" />
<TextField source="ins_title" />
<TextField source="ins_ct_ph" />
<TextField source="ins_ct_phx" />
<TextField source="ins_ea" />
<TextField source="ins_memo" />
<TextField source="policy_no" />
<TextField source="ded_amt" />
<TextField source="ded_status" />
<TextField source="asgn_no" />
<TextField source="asgn_date" />
<TextField source="asgn_type" />
<TextField source="clm_no" />
<TextField source="clm_ofc_id" />
<TextField source="date_estimated" />
<TextField source="date_open" />
<TextField source="date_scheduled" />
<TextField source="date_invoiced" />
<TextField source="date_exported" />
<TextField source="clm_total" />
<TextField source="owner_owing" />
<TextField source="converted" />
<TextField source="ciecaid" />
<TextField source="loss_date" />
<TextField source="clm_ofc_nm" />
<TextField source="clm_addr1" />
<TextField source="clm_addr2" />
<TextField source="clm_city" />
<TextField source="clm_st" />
<TextField source="clm_zip" />
<TextField source="clm_ctry" />
<TextField source="clm_ph1" />
<TextField source="clm_ph1x" />
<TextField source="clm_ph2" />
<TextField source="clm_ph2x" />
<TextField source="clm_fax" />
<TextField source="clm_faxx" />
<TextField source="clm_ct_ln" />
<TextField source="clm_ct_fn" />
<TextField source="clm_title" />
<TextField source="clm_ct_ph" />
<TextField source="clm_ct_phx" />
<TextField source="clm_ea" />
<TextField source="payee_nms" />
<TextField source="pay_type" />
<TextField source="pay_date" />
<TextField source="pay_chknm" />
<TextField source="pay_amt" />
<TextField source="agt_co_id" />
<TextField source="agt_co_nm" />
<TextField source="agt_addr1" />
<TextField source="agt_addr2" />
<TextField source="agt_city" />
<TextField source="agt_st" />
<TextField source="agt_zip" />
<TextField source="agt_ctry" />
<TextField source="agt_ph1" />
<TextField source="agt_ph1x" />
<TextField source="agt_ph2" />
<TextField source="agt_ph2x" />
<TextField source="agt_fax" />
<TextField source="agt_faxx" />
<TextField source="agt_ct_ln" />
<TextField source="agt_ct_fn" />
<TextField source="agt_ct_ph" />
<TextField source="agt_ct_phx" />
<TextField source="agt_ea" />
<TextField source="agt_lic_no" />
<TextField source="loss_type" />
<TextField source="loss_desc" />
<TextField source="theft_ind" />
<TextField source="cat_no" />
<TextField source="tlos_ind" />
<TextField source="cust_pr" />
<TextField source="insd_ln" />
<TextField source="insd_fn" />
<TextField source="insd_title" />
<TextField source="insd_co_nm" />
<TextField source="insd_addr1" />
<TextField source="insd_addr2" />
<TextField source="insd_city" />
<TextField source="insd_st" />
<TextField source="insd_zip" />
<TextField source="insd_ctry" />
<TextField source="insd_ph1" />
<TextField source="insd_ph1x" />
<TextField source="insd_ph2" />
<TextField source="insd_ph2x" />
<TextField source="insd_fax" />
<TextField source="insd_faxx" />
<TextField source="insd_ea" />
<TextField source="ownr_ln" />
<TextField source="ownr_fn" />
<TextField source="ownr_title" />
<TextField source="ownr_co_nm" />
<TextField source="ownr_addr1" />
<TextField source="ownr_addr2" />
<TextField source="ownr_city" />
<TextField source="ownr_st" />
<TextField source="ownr_zip" />
<TextField source="ownr_ctry" />
<TextField source="ownr_ph1" />
<TextField source="ownr_ph1x" />
<TextField source="ownr_ph2" />
<TextField source="ownr_ph2x" />
<TextField source="ownr_fax" />
<TextField source="ownr_faxx" />
<TextField source="ownr_ea" />
<TextField source="area_of_damage" />
<TextField source="loss_cat" />
<TextField source="special_coverage_policy" />
<TextField source="csr" />
<TextField source="po_number" />
<TextField source="unit_number" />
<TextField source="kmin" />
<TextField source="kmout" />
<TextField source="referral_source" />
<TextField source="selling_dealer" />
<TextField source="servicing_dealer" />
<TextField source="servicing_dealer_contact" />
<TextField source="selling_dealer_contact" />
<TextField source="depreciation_taxes" />
<TextField source="federal_tax_payable" />
<TextField source="other_amount_payable" />
<TextField source="towing_payable" />
<TextField source="storage_payable" />
<TextField source="adjustment_bottom_line" />
<TextField source="tax_pstthr" />
<TextField source="tax_tow_rt" />
<TextField source="tax_sub_rt" />
<TextField source="tax_paint_mat_rt" />
<TextField source="tax_levies_rt" />
<TextField source="tax_prethr" />
<TextField source="tax_thramt" />
<TextField source="tax_str_rt" />
<TextField source="tax_lbr_rt" />
<TextField source="adj_g_disc" />
<TextField source="adj_towdis" />
<TextField source="adj_strdis" />
<TextField source="tax_predis" />
<TextField source="rate_laa" />
<TextField source="status" />
<TextField source="cieca_stl" />
<TextField source="g_bett_amt" />
<TextField source="cieca_ttl" />
<TextField source="plate_no" />
<TextField source="plate_st" />
<TextField source="v_vin" />
<TextField source="v_model_yr" />
<TextField source="v_model_desc" />
<TextField source="v_make_desc" />
<TextField source="v_color" />
<TextField source="parts_tax_rates" />
<TextField source="job_totals" />
<TextField source="production_vars" />
<TextField source="intakechecklist" />
<TextField source="invoice_allocation" />
<TextField source="kanbanparent" />
<TextField source="employee_body" />
<TextField source="employee_refinish" />
<TextField source="employee_prep" />
</Tab>
</TabbedShowLayout>
</Show>
);
export default JobsShow;

View File

@@ -1,31 +0,0 @@
import firebase from "firebase/app";
import "firebase/firestore";
import "firebase/auth";
const config = JSON.parse(process.env.REACT_APP_FIREBASE_CONFIG);
firebase.initializeApp(config);
export const auth = firebase.auth();
export const firestore = firebase.firestore();
export default firebase;
export const getCurrentUser = () => {
return new Promise((resolve, reject) => {
const unsubscribe = auth.onAuthStateChanged((userAuth) => {
unsubscribe();
resolve(userAuth);
}, reject);
});
};
export const updateCurrentUser = (userDetails) => {
return new Promise((resolve, reject) => {
const unsubscribe = auth.onAuthStateChanged((userAuth) => {
userAuth.updateProfile(userDetails).then((r) => {
unsubscribe();
resolve(userAuth);
});
}, reject);
});
};

View File

@@ -1,10 +0,0 @@
import { gql } from "@apollo/client";
export const QUERY_ALL_SHOPS = gql`
query QUERY_ALL_SHOPS {
bodyshops {
id
shopname
}
}
`;

View File

@@ -1,13 +0,0 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@@ -1,17 +0,0 @@
import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App/App";
import * as serviceWorker from "./serviceWorker";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

View File

@@ -1,141 +0,0 @@
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === 'localhost' ||
// [::1] is the IPv6 localhost address.
window.location.hostname === '[::1]' ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
export function register(config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl, config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://bit.ly/CRA-PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch(error => {
console.error('Error during service worker registration:', error);
});
}
function checkValidServiceWorker(swUrl, config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' },
})
.then(response => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
response.status === 404 ||
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
'No internet connection found. App is running in offline mode.'
);
});
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready
.then(registration => {
registration.unregister();
})
.catch(error => {
console.error(error.message);
});
}
}

View File

@@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom/extend-expect';

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

13
client/.env.development Normal file
View File

@@ -0,0 +1,13 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.dev.bodyshop.app/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.dev.bodyshop.app/v1/graphql
REACT_APP_GA_CODE=231099835
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDPLT8GiDHDR1R4nI66Qi0BY1aYviDPioc","authDomain":"imex-dev.firebaseapp.com","databaseURL":"https://imex-dev.firebaseio.com","projectId":"imex-dev","storageBucket":"imex-dev.appspot.com","messagingSenderId":"759548147434","appId":"1:759548147434:web:e8239868a48ceb36700993","measurementId":"G-K5XRBVVB4S"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/io-test
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/io-test
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_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc

13
client/.env.production Normal file
View File

@@ -0,0 +1,13 @@
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
REACT_APP_FIREBASE_CONFIG={"apiKey":"AIzaSyDSezy-jGJreo7ulgpLdlpOwAOrgcaEkhU","authDomain":"imex-prod.firebaseapp.com","databaseURL":"https://imex-prod.firebaseio.com","projectId":"imex-prod","storageBucket":"imex-prod.appspot.com","messagingSenderId":"253497221485","appId":"1:253497221485:web:3c81c483b94db84b227a64","measurementId":"G-NTWBKG2L0M"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BMgZT1NZztW2DsJl8Mg2L04hgY9FzAg6b8fbzgNAfww2VDzH3VE63Ot9EaP_U7KWS2JT-7HPHaw0T_Tw_5vkZc8'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports.imex.online
REACT_APP_SPLIT_API=et9pjkik6bn67he5evpmpr1agoo7gactphgk

14
client/.env.test Normal file
View File

@@ -0,0 +1,14 @@
REACT_APP_GRAPHQL_ENDPOINT=https://db.test.bodyshop.app/v1/graphql
REACT_APP_GRAPHQL_ENDPOINT_WS=wss://db.test.bodyshop.app/v1/graphql
REACT_APP_GA_CODE=231099835
REACT_APP_FIREBASE_CONFIG={ "apiKey":"AIzaSyBw7_GTy7GtQyfkIRPVrWHEGKfcqeyXw0c", "authDomain":"imex-test.firebaseapp.com", "projectId":"imex-test", "storageBucket":"imex-test.appspot.com", "messagingSenderId":"991923618608", "appId":"1:991923618608:web:633437569cdad78299bef5", "measurementId":"G-TW0XLZEH18"}
REACT_APP_CLOUDINARY_ENDPOINT_API=https://api.cloudinary.com/v1_1/bodyshop
REACT_APP_CLOUDINARY_ENDPOINT=https://res.cloudinary.com/bodyshop
REACT_APP_CLOUDINARY_API_KEY=473322739956866
REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS=c_fill,h_250,w_250
REACT_APP_FIREBASE_PUBLIC_VAPID_KEY='BN2GcDPjipR5MTEosO5dT4CfQ3cmrdBIsI4juoOQrRijn_5aRiHlwj1mlq0W145mOusx6xynEKl_tvYJhpCc9lo'
REACT_APP_STRIPE_PUBLIC_KEY=pk_test_51GqB4TJl3nQjrZ0wCQWAxAhlNF8jKe0tipIa6ExBaxwJGitwvFsIZUEua4dUzaMIAuXp4qwYHXx7lgjyQSwP0Pe900vzm38C7g
REACT_APP_AXIOS_BASE_API_URL=https://api.test.imex.online/
REACT_APP_REPORTS_SERVER_URL=https://reports3.test.imex.online
REACT_APP_IS_TEST=true
REACT_APP_SPLIT_API=ts615lqgnmk84thn72uk18uu5pgce6e0l4rc

9
client/cypress.config.js Normal file
View File

@@ -0,0 +1,9 @@
const { defineConfig } = require("cypress");
module.exports = defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
},
});

View File

@@ -1,8 +0,0 @@
{
"baseUrl": "http://localhost:3000",
"experimentalStudio": true,
"env": {
"FIREBASE_USERNAME": "cypress@imex.test",
"FIREBASE_PASSWORD": "cypress"
}
}

View File

@@ -0,0 +1,18 @@
/// <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

@@ -1,5 +0,0 @@
{
"id": 8739,
"name": "Jane",
"email": "jane@example.com"
}

View File

@@ -1 +0,0 @@
[]

View File

@@ -1,23 +0,0 @@
/// <reference types="Cypress" />
const { FIREBASE_USERNAME, FIREBASE_PASSWORcD } = Cypress.env();
describe("Renders the General Page", () => {
beforeEach(() => {
cy.visit("/");
});
it("Renders Correctly", () => {});
it("Has the Slogan", () => {
cy.findByText("A whole x22new kind of shop management system.").should(
"exist"
);
/* ==== Generated with Cypress Studio ==== */
cy.get(
".ant-menu-item-active > .ant-menu-title-content > .header0-item-block"
).click();
cy.get("#email").clear();
cy.get("#email").type("patrick@imex.dev");
cy.get("#password").clear();
cy.get("#password").type("patrick123{enter}");
cy.get(".ant-form > .ant-btn").click();
/* ==== End Cypress Studio ==== */
});
});

View File

@@ -1,143 +0,0 @@
/// <reference types="cypress" />
// Welcome to Cypress!
//
// This spec file contains a variety of sample tests
// for a todo list app that are designed to demonstrate
// the power of writing tests in Cypress.
//
// To learn more about how Cypress works and
// what makes it such an awesome testing tool,
// please read our getting started guide:
// https://on.cypress.io/introduction-to-cypress
describe('example to-do app', () => {
beforeEach(() => {
// Cypress starts out with a blank slate for each test
// so we must tell it to visit our website with the `cy.visit()` command.
// Since we want to visit the same URL at the start of all our tests,
// we include it in our beforeEach function so that it runs before each test
cy.visit('https://example.cypress.io/todo')
})
it('displays two todo items by default', () => {
// We use the `cy.get()` command to get all elements that match the selector.
// Then, we use `should` to assert that there are two matched items,
// which are the two default items.
cy.get('.todo-list li').should('have.length', 2)
// We can go even further and check that the default todos each contain
// the correct text. We use the `first` and `last` functions
// to get just the first and last matched elements individually,
// and then perform an assertion with `should`.
cy.get('.todo-list li').first().should('have.text', 'Pay electric bill')
cy.get('.todo-list li').last().should('have.text', 'Walk the dog')
})
it('can add new todo items', () => {
// We'll store our item text in a variable so we can reuse it
const newItem = 'Feed the cat'
// Let's get the input element and use the `type` command to
// input our new list item. After typing the content of our item,
// we need to type the enter key as well in order to submit the input.
// This input has a data-test attribute so we'll use that to select the
// element in accordance with best practices:
// https://on.cypress.io/selecting-elements
cy.get('[data-test=new-todo]').type(`${newItem}{enter}`)
// Now that we've typed our new item, let's check that it actually was added to the list.
// Since it's the newest item, it should exist as the last element in the list.
// In addition, with the two default items, we should have a total of 3 elements in the list.
// Since assertions yield the element that was asserted on,
// we can chain both of these assertions together into a single statement.
cy.get('.todo-list li')
.should('have.length', 3)
.last()
.should('have.text', newItem)
})
it('can check off an item as completed', () => {
// In addition to using the `get` command to get an element by selector,
// we can also use the `contains` command to get an element by its contents.
// However, this will yield the <label>, which is lowest-level element that contains the text.
// In order to check the item, we'll find the <input> element for this <label>
// by traversing up the dom to the parent element. From there, we can `find`
// the child checkbox <input> element and use the `check` command to check it.
cy.contains('Pay electric bill')
.parent()
.find('input[type=checkbox]')
.check()
// Now that we've checked the button, we can go ahead and make sure
// that the list element is now marked as completed.
// Again we'll use `contains` to find the <label> element and then use the `parents` command
// to traverse multiple levels up the dom until we find the corresponding <li> element.
// Once we get that element, we can assert that it has the completed class.
cy.contains('Pay electric bill')
.parents('li')
.should('have.class', 'completed')
})
context('with a checked task', () => {
beforeEach(() => {
// We'll take the command we used above to check off an element
// Since we want to perform multiple tests that start with checking
// one element, we put it in the beforeEach hook
// so that it runs at the start of every test.
cy.contains('Pay electric bill')
.parent()
.find('input[type=checkbox]')
.check()
})
it('can filter for uncompleted tasks', () => {
// We'll click on the "active" button in order to
// display only incomplete items
cy.contains('Active').click()
// After filtering, we can assert that there is only the one
// incomplete item in the list.
cy.get('.todo-list li')
.should('have.length', 1)
.first()
.should('have.text', 'Walk the dog')
// For good measure, let's also assert that the task we checked off
// does not exist on the page.
cy.contains('Pay electric bill').should('not.exist')
})
it('can filter for completed tasks', () => {
// We can perform similar steps as the test above to ensure
// that only completed tasks are shown
cy.contains('Completed').click()
cy.get('.todo-list li')
.should('have.length', 1)
.first()
.should('have.text', 'Pay electric bill')
cy.contains('Walk the dog').should('not.exist')
})
it('can delete all completed tasks', () => {
// First, let's click the "Clear completed" button
// `contains` is actually serving two purposes here.
// First, it's ensuring that the button exists within the dom.
// This button only appears when at least one task is checked
// so this command is implicitly verifying that it does exist.
// Second, it selects the button so we can click it.
cy.contains('Clear completed').click()
// Then we can make sure that there is only one element
// in the list and our element does not exist
cy.get('.todo-list li')
.should('have.length', 1)
.should('not.have.text', 'Pay electric bill')
// Finally, make sure that the clear button no longer exists.
cy.contains('Clear completed').should('not.exist')
})
})
})

View File

@@ -1,299 +0,0 @@
/// <reference types="cypress" />
context('Actions', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/actions')
})
// https://on.cypress.io/interacting-with-elements
it('.type() - type into a DOM element', () => {
// https://on.cypress.io/type
cy.get('.action-email')
.type('fake@email.com').should('have.value', 'fake@email.com')
// .type() with special character sequences
.type('{leftarrow}{rightarrow}{uparrow}{downarrow}')
.type('{del}{selectall}{backspace}')
// .type() with key modifiers
.type('{alt}{option}') //these are equivalent
.type('{ctrl}{control}') //these are equivalent
.type('{meta}{command}{cmd}') //these are equivalent
.type('{shift}')
// Delay each keypress by 0.1 sec
.type('slow.typing@email.com', { delay: 100 })
.should('have.value', 'slow.typing@email.com')
cy.get('.action-disabled')
// Ignore error checking prior to type
// like whether the input is visible or disabled
.type('disabled error checking', { force: true })
.should('have.value', 'disabled error checking')
})
it('.focus() - focus on a DOM element', () => {
// https://on.cypress.io/focus
cy.get('.action-focus').focus()
.should('have.class', 'focus')
.prev().should('have.attr', 'style', 'color: orange;')
})
it('.blur() - blur off a DOM element', () => {
// https://on.cypress.io/blur
cy.get('.action-blur').type('About to blur').blur()
.should('have.class', 'error')
.prev().should('have.attr', 'style', 'color: red;')
})
it('.clear() - clears an input or textarea element', () => {
// https://on.cypress.io/clear
cy.get('.action-clear').type('Clear this text')
.should('have.value', 'Clear this text')
.clear()
.should('have.value', '')
})
it('.submit() - submit a form', () => {
// https://on.cypress.io/submit
cy.get('.action-form')
.find('[type="text"]').type('HALFOFF')
cy.get('.action-form').submit()
.next().should('contain', 'Your form has been submitted!')
})
it('.click() - click on a DOM element', () => {
// https://on.cypress.io/click
cy.get('.action-btn').click()
// You can click on 9 specific positions of an element:
// -----------------------------------
// | topLeft top topRight |
// | |
// | |
// | |
// | left center right |
// | |
// | |
// | |
// | bottomLeft bottom bottomRight |
// -----------------------------------
// clicking in the center of the element is the default
cy.get('#action-canvas').click()
cy.get('#action-canvas').click('topLeft')
cy.get('#action-canvas').click('top')
cy.get('#action-canvas').click('topRight')
cy.get('#action-canvas').click('left')
cy.get('#action-canvas').click('right')
cy.get('#action-canvas').click('bottomLeft')
cy.get('#action-canvas').click('bottom')
cy.get('#action-canvas').click('bottomRight')
// .click() accepts an x and y coordinate
// that controls where the click occurs :)
cy.get('#action-canvas')
.click(80, 75) // click 80px on x coord and 75px on y coord
.click(170, 75)
.click(80, 165)
.click(100, 185)
.click(125, 190)
.click(150, 185)
.click(170, 165)
// click multiple elements by passing multiple: true
cy.get('.action-labels>.label').click({ multiple: true })
// Ignore error checking prior to clicking
cy.get('.action-opacity>.btn').click({ force: true })
})
it('.dblclick() - double click on a DOM element', () => {
// https://on.cypress.io/dblclick
// Our app has a listener on 'dblclick' event in our 'scripts.js'
// that hides the div and shows an input on double click
cy.get('.action-div').dblclick().should('not.be.visible')
cy.get('.action-input-hidden').should('be.visible')
})
it('.rightclick() - right click on a DOM element', () => {
// https://on.cypress.io/rightclick
// Our app has a listener on 'contextmenu' event in our 'scripts.js'
// that hides the div and shows an input on right click
cy.get('.rightclick-action-div').rightclick().should('not.be.visible')
cy.get('.rightclick-action-input-hidden').should('be.visible')
})
it('.check() - check a checkbox or radio element', () => {
// https://on.cypress.io/check
// By default, .check() will check all
// matching checkbox or radio elements in succession, one after another
cy.get('.action-checkboxes [type="checkbox"]').not('[disabled]')
.check().should('be.checked')
cy.get('.action-radios [type="radio"]').not('[disabled]')
.check().should('be.checked')
// .check() accepts a value argument
cy.get('.action-radios [type="radio"]')
.check('radio1').should('be.checked')
// .check() accepts an array of values
cy.get('.action-multiple-checkboxes [type="checkbox"]')
.check(['checkbox1', 'checkbox2']).should('be.checked')
// Ignore error checking prior to checking
cy.get('.action-checkboxes [disabled]')
.check({ force: true }).should('be.checked')
cy.get('.action-radios [type="radio"]')
.check('radio3', { force: true }).should('be.checked')
})
it('.uncheck() - uncheck a checkbox element', () => {
// https://on.cypress.io/uncheck
// By default, .uncheck() will uncheck all matching
// checkbox elements in succession, one after another
cy.get('.action-check [type="checkbox"]')
.not('[disabled]')
.uncheck().should('not.be.checked')
// .uncheck() accepts a value argument
cy.get('.action-check [type="checkbox"]')
.check('checkbox1')
.uncheck('checkbox1').should('not.be.checked')
// .uncheck() accepts an array of values
cy.get('.action-check [type="checkbox"]')
.check(['checkbox1', 'checkbox3'])
.uncheck(['checkbox1', 'checkbox3']).should('not.be.checked')
// Ignore error checking prior to unchecking
cy.get('.action-check [disabled]')
.uncheck({ force: true }).should('not.be.checked')
})
it('.select() - select an option in a <select> element', () => {
// https://on.cypress.io/select
// at first, no option should be selected
cy.get('.action-select')
.should('have.value', '--Select a fruit--')
// Select option(s) with matching text content
cy.get('.action-select').select('apples')
// confirm the apples were selected
// note that each value starts with "fr-" in our HTML
cy.get('.action-select').should('have.value', 'fr-apples')
cy.get('.action-select-multiple')
.select(['apples', 'oranges', 'bananas'])
// when getting multiple values, invoke "val" method first
.invoke('val')
.should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
// Select option(s) with matching value
cy.get('.action-select').select('fr-bananas')
// can attach an assertion right away to the element
.should('have.value', 'fr-bananas')
cy.get('.action-select-multiple')
.select(['fr-apples', 'fr-oranges', 'fr-bananas'])
.invoke('val')
.should('deep.equal', ['fr-apples', 'fr-oranges', 'fr-bananas'])
// assert the selected values include oranges
cy.get('.action-select-multiple')
.invoke('val').should('include', 'fr-oranges')
})
it('.scrollIntoView() - scroll an element into view', () => {
// https://on.cypress.io/scrollintoview
// normally all of these buttons are hidden,
// because they're not within
// the viewable area of their parent
// (we need to scroll to see them)
cy.get('#scroll-horizontal button')
.should('not.be.visible')
// scroll the button into view, as if the user had scrolled
cy.get('#scroll-horizontal button').scrollIntoView()
.should('be.visible')
cy.get('#scroll-vertical button')
.should('not.be.visible')
// Cypress handles the scroll direction needed
cy.get('#scroll-vertical button').scrollIntoView()
.should('be.visible')
cy.get('#scroll-both button')
.should('not.be.visible')
// Cypress knows to scroll to the right and down
cy.get('#scroll-both button').scrollIntoView()
.should('be.visible')
})
it('.trigger() - trigger an event on a DOM element', () => {
// https://on.cypress.io/trigger
// To interact with a range input (slider)
// we need to set its value & trigger the
// event to signal it changed
// Here, we invoke jQuery's val() method to set
// the value and trigger the 'change' event
cy.get('.trigger-input-range')
.invoke('val', 25)
.trigger('change')
.get('input[type=range]').siblings('p')
.should('have.text', '25')
})
it('cy.scrollTo() - scroll the window or element to a position', () => {
// https://on.cypress.io/scrollto
// You can scroll to 9 specific positions of an element:
// -----------------------------------
// | topLeft top topRight |
// | |
// | |
// | |
// | left center right |
// | |
// | |
// | |
// | bottomLeft bottom bottomRight |
// -----------------------------------
// if you chain .scrollTo() off of cy, we will
// scroll the entire window
cy.scrollTo('bottom')
cy.get('#scrollable-horizontal').scrollTo('right')
// or you can scroll to a specific coordinate:
// (x axis, y axis) in pixels
cy.get('#scrollable-vertical').scrollTo(250, 250)
// or you can scroll to a specific percentage
// of the (width, height) of the element
cy.get('#scrollable-both').scrollTo('75%', '25%')
// control the easing of the scroll (default is 'swing')
cy.get('#scrollable-vertical').scrollTo('center', { easing: 'linear' })
// control the duration of the scroll (in ms)
cy.get('#scrollable-both').scrollTo('center', { duration: 2000 })
})
})

View File

@@ -1,39 +0,0 @@
/// <reference types="cypress" />
context('Aliasing', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/aliasing')
})
it('.as() - alias a DOM element for later use', () => {
// https://on.cypress.io/as
// Alias a DOM element for use later
// We don't have to traverse to the element
// later in our code, we reference it with @
cy.get('.as-table').find('tbody>tr')
.first().find('td').first()
.find('button').as('firstBtn')
// when we reference the alias, we place an
// @ in front of its name
cy.get('@firstBtn').click()
cy.get('@firstBtn')
.should('have.class', 'btn-success')
.and('contain', 'Changed')
})
it('.as() - alias a route for later use', () => {
// Alias the route to wait for its response
cy.intercept('GET', '**/comments/*').as('getComment')
// we have code that gets a comment when
// the button is clicked in scripts.js
cy.get('.network-btn').click()
// https://on.cypress.io/wait
cy.wait('@getComment').its('response.statusCode').should('eq', 200)
})
})

View File

@@ -1,177 +0,0 @@
/// <reference types="cypress" />
context('Assertions', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/assertions')
})
describe('Implicit Assertions', () => {
it('.should() - make an assertion about the current subject', () => {
// https://on.cypress.io/should
cy.get('.assertion-table')
.find('tbody tr:last')
.should('have.class', 'success')
.find('td')
.first()
// checking the text of the <td> element in various ways
.should('have.text', 'Column content')
.should('contain', 'Column content')
.should('have.html', 'Column content')
// chai-jquery uses "is()" to check if element matches selector
.should('match', 'td')
// to match text content against a regular expression
// first need to invoke jQuery method text()
// and then match using regular expression
.invoke('text')
.should('match', /column content/i)
// a better way to check element's text content against a regular expression
// is to use "cy.contains"
// https://on.cypress.io/contains
cy.get('.assertion-table')
.find('tbody tr:last')
// finds first <td> element with text content matching regular expression
.contains('td', /column content/i)
.should('be.visible')
// for more information about asserting element's text
// see https://on.cypress.io/using-cypress-faq#How-do-I-get-an-elements-text-contents
})
it('.and() - chain multiple assertions together', () => {
// https://on.cypress.io/and
cy.get('.assertions-link')
.should('have.class', 'active')
.and('have.attr', 'href')
.and('include', 'cypress.io')
})
})
describe('Explicit Assertions', () => {
// https://on.cypress.io/assertions
it('expect - make an assertion about a specified subject', () => {
// We can use Chai's BDD style assertions
expect(true).to.be.true
const o = { foo: 'bar' }
expect(o).to.equal(o)
expect(o).to.deep.equal({ foo: 'bar' })
// matching text using regular expression
expect('FooBar').to.match(/bar$/i)
})
it('pass your own callback function to should()', () => {
// Pass a function to should that can have any number
// of explicit assertions within it.
// The ".should(cb)" function will be retried
// automatically until it passes all your explicit assertions or times out.
cy.get('.assertions-p')
.find('p')
.should(($p) => {
// https://on.cypress.io/$
// return an array of texts from all of the p's
// @ts-ignore TS6133 unused variable
const texts = $p.map((i, el) => Cypress.$(el).text())
// jquery map returns jquery object
// and .get() convert this to simple array
const paragraphs = texts.get()
// array should have length of 3
expect(paragraphs, 'has 3 paragraphs').to.have.length(3)
// use second argument to expect(...) to provide clear
// message with each assertion
expect(paragraphs, 'has expected text in each paragraph').to.deep.eq([
'Some text from first p',
'More text from second p',
'And even more text from third p',
])
})
})
it('finds element by class name regex', () => {
cy.get('.docs-header')
.find('div')
// .should(cb) callback function will be retried
.should(($div) => {
expect($div).to.have.length(1)
const className = $div[0].className
expect(className).to.match(/heading-/)
})
// .then(cb) callback is not retried,
// it either passes or fails
.then(($div) => {
expect($div, 'text content').to.have.text('Introduction')
})
})
it('can throw any error', () => {
cy.get('.docs-header')
.find('div')
.should(($div) => {
if ($div.length !== 1) {
// you can throw your own errors
throw new Error('Did not find 1 element')
}
const className = $div[0].className
if (!className.match(/heading-/)) {
throw new Error(`Could not find class "heading-" in ${className}`)
}
})
})
it('matches unknown text between two elements', () => {
/**
* Text from the first element.
* @type {string}
*/
let text
/**
* Normalizes passed text,
* useful before comparing text with spaces and different capitalization.
* @param {string} s Text to normalize
*/
const normalizeText = (s) => s.replace(/\s/g, '').toLowerCase()
cy.get('.two-elements')
.find('.first')
.then(($first) => {
// save text from the first element
text = normalizeText($first.text())
})
cy.get('.two-elements')
.find('.second')
.should(($div) => {
// we can massage text before comparing
const secondText = normalizeText($div.text())
expect(secondText, 'second text').to.equal(text)
})
})
it('assert - assert shape of an object', () => {
const person = {
name: 'Joe',
age: 20,
}
assert.isObject(person, 'value is object')
})
it('retries the should callback until assertions pass', () => {
cy.get('#random-number')
.should(($div) => {
const n = parseFloat($div.text())
expect(n).to.be.gte(1).and.be.lte(10)
})
})
})
})

View File

@@ -1,97 +0,0 @@
/// <reference types="cypress" />
context('Connectors', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/connectors')
})
it('.each() - iterate over an array of elements', () => {
// https://on.cypress.io/each
cy.get('.connectors-each-ul>li')
.each(($el, index, $list) => {
console.log($el, index, $list)
})
})
it('.its() - get properties on the current subject', () => {
// https://on.cypress.io/its
cy.get('.connectors-its-ul>li')
// calls the 'length' property yielding that value
.its('length')
.should('be.gt', 2)
})
it('.invoke() - invoke a function on the current subject', () => {
// our div is hidden in our script.js
// $('.connectors-div').hide()
// https://on.cypress.io/invoke
cy.get('.connectors-div').should('be.hidden')
// call the jquery method 'show' on the 'div.container'
.invoke('show')
.should('be.visible')
})
it('.spread() - spread an array as individual args to callback function', () => {
// https://on.cypress.io/spread
const arr = ['foo', 'bar', 'baz']
cy.wrap(arr).spread((foo, bar, baz) => {
expect(foo).to.eq('foo')
expect(bar).to.eq('bar')
expect(baz).to.eq('baz')
})
})
describe('.then()', () => {
it('invokes a callback function with the current subject', () => {
// https://on.cypress.io/then
cy.get('.connectors-list > li')
.then(($lis) => {
expect($lis, '3 items').to.have.length(3)
expect($lis.eq(0), 'first item').to.contain('Walk the dog')
expect($lis.eq(1), 'second item').to.contain('Feed the cat')
expect($lis.eq(2), 'third item').to.contain('Write JavaScript')
})
})
it('yields the returned value to the next command', () => {
cy.wrap(1)
.then((num) => {
expect(num).to.equal(1)
return 2
})
.then((num) => {
expect(num).to.equal(2)
})
})
it('yields the original subject without return', () => {
cy.wrap(1)
.then((num) => {
expect(num).to.equal(1)
// note that nothing is returned from this callback
})
.then((num) => {
// this callback receives the original unchanged value 1
expect(num).to.equal(1)
})
})
it('yields the value yielded by the last Cypress command inside', () => {
cy.wrap(1)
.then((num) => {
expect(num).to.equal(1)
// note how we run a Cypress command
// the result yielded by this Cypress command
// will be passed to the second ".then"
cy.wrap(2)
})
.then((num) => {
// this callback receives the value yielded by "cy.wrap(2)"
expect(num).to.equal(2)
})
})
})
})

View File

@@ -1,77 +0,0 @@
/// <reference types="cypress" />
context('Cookies', () => {
beforeEach(() => {
Cypress.Cookies.debug(true)
cy.visit('https://example.cypress.io/commands/cookies')
// clear cookies again after visiting to remove
// any 3rd party cookies picked up such as cloudflare
cy.clearCookies()
})
it('cy.getCookie() - get a browser cookie', () => {
// https://on.cypress.io/getcookie
cy.get('#getCookie .set-a-cookie').click()
// cy.getCookie() yields a cookie object
cy.getCookie('token').should('have.property', 'value', '123ABC')
})
it('cy.getCookies() - get browser cookies', () => {
// https://on.cypress.io/getcookies
cy.getCookies().should('be.empty')
cy.get('#getCookies .set-a-cookie').click()
// cy.getCookies() yields an array of cookies
cy.getCookies().should('have.length', 1).should((cookies) => {
// each cookie has these properties
expect(cookies[0]).to.have.property('name', 'token')
expect(cookies[0]).to.have.property('value', '123ABC')
expect(cookies[0]).to.have.property('httpOnly', false)
expect(cookies[0]).to.have.property('secure', false)
expect(cookies[0]).to.have.property('domain')
expect(cookies[0]).to.have.property('path')
})
})
it('cy.setCookie() - set a browser cookie', () => {
// https://on.cypress.io/setcookie
cy.getCookies().should('be.empty')
cy.setCookie('foo', 'bar')
// cy.getCookie() yields a cookie object
cy.getCookie('foo').should('have.property', 'value', 'bar')
})
it('cy.clearCookie() - clear a browser cookie', () => {
// https://on.cypress.io/clearcookie
cy.getCookie('token').should('be.null')
cy.get('#clearCookie .set-a-cookie').click()
cy.getCookie('token').should('have.property', 'value', '123ABC')
// cy.clearCookies() yields null
cy.clearCookie('token').should('be.null')
cy.getCookie('token').should('be.null')
})
it('cy.clearCookies() - clear browser cookies', () => {
// https://on.cypress.io/clearcookies
cy.getCookies().should('be.empty')
cy.get('#clearCookies .set-a-cookie').click()
cy.getCookies().should('have.length', 1)
// cy.clearCookies() yields null
cy.clearCookies()
cy.getCookies().should('be.empty')
})
})

View File

@@ -1,202 +0,0 @@
/// <reference types="cypress" />
context('Cypress.Commands', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/cypress-api')
})
// https://on.cypress.io/custom-commands
it('.add() - create a custom command', () => {
Cypress.Commands.add('console', {
prevSubject: true,
}, (subject, method) => {
// the previous subject is automatically received
// and the commands arguments are shifted
// allow us to change the console method used
method = method || 'log'
// log the subject to the console
// @ts-ignore TS7017
console[method]('The subject is', subject)
// whatever we return becomes the new subject
// we don't want to change the subject so
// we return whatever was passed in
return subject
})
// @ts-ignore TS2339
cy.get('button').console('info').then(($button) => {
// subject is still $button
})
})
})
context('Cypress.Cookies', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/cypress-api')
})
// https://on.cypress.io/cookies
it('.debug() - enable or disable debugging', () => {
Cypress.Cookies.debug(true)
// Cypress will now log in the console when
// cookies are set or cleared
cy.setCookie('fakeCookie', '123ABC')
cy.clearCookie('fakeCookie')
cy.setCookie('fakeCookie', '123ABC')
cy.clearCookie('fakeCookie')
cy.setCookie('fakeCookie', '123ABC')
})
it('.preserveOnce() - preserve cookies by key', () => {
// normally cookies are reset after each test
cy.getCookie('fakeCookie').should('not.be.ok')
// preserving a cookie will not clear it when
// the next test starts
cy.setCookie('lastCookie', '789XYZ')
Cypress.Cookies.preserveOnce('lastCookie')
})
it('.defaults() - set defaults for all cookies', () => {
// now any cookie with the name 'session_id' will
// not be cleared before each new test runs
Cypress.Cookies.defaults({
preserve: 'session_id',
})
})
})
context('Cypress.arch', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/cypress-api')
})
it('Get CPU architecture name of underlying OS', () => {
// https://on.cypress.io/arch
expect(Cypress.arch).to.exist
})
})
context('Cypress.config()', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/cypress-api')
})
it('Get and set configuration options', () => {
// https://on.cypress.io/config
let myConfig = Cypress.config()
expect(myConfig).to.have.property('animationDistanceThreshold', 5)
expect(myConfig).to.have.property('baseUrl', null)
expect(myConfig).to.have.property('defaultCommandTimeout', 4000)
expect(myConfig).to.have.property('requestTimeout', 5000)
expect(myConfig).to.have.property('responseTimeout', 30000)
expect(myConfig).to.have.property('viewportHeight', 660)
expect(myConfig).to.have.property('viewportWidth', 1000)
expect(myConfig).to.have.property('pageLoadTimeout', 60000)
expect(myConfig).to.have.property('waitForAnimations', true)
expect(Cypress.config('pageLoadTimeout')).to.eq(60000)
// this will change the config for the rest of your tests!
Cypress.config('pageLoadTimeout', 20000)
expect(Cypress.config('pageLoadTimeout')).to.eq(20000)
Cypress.config('pageLoadTimeout', 60000)
})
})
context('Cypress.dom', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/cypress-api')
})
// https://on.cypress.io/dom
it('.isHidden() - determine if a DOM element is hidden', () => {
let hiddenP = Cypress.$('.dom-p p.hidden').get(0)
let visibleP = Cypress.$('.dom-p p.visible').get(0)
// our first paragraph has css class 'hidden'
expect(Cypress.dom.isHidden(hiddenP)).to.be.true
expect(Cypress.dom.isHidden(visibleP)).to.be.false
})
})
context('Cypress.env()', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/cypress-api')
})
// We can set environment variables for highly dynamic values
// https://on.cypress.io/environment-variables
it('Get environment variables', () => {
// https://on.cypress.io/env
// set multiple environment variables
Cypress.env({
host: 'veronica.dev.local',
api_server: 'http://localhost:8888/v1/',
})
// get environment variable
expect(Cypress.env('host')).to.eq('veronica.dev.local')
// set environment variable
Cypress.env('api_server', 'http://localhost:8888/v2/')
expect(Cypress.env('api_server')).to.eq('http://localhost:8888/v2/')
// get all environment variable
expect(Cypress.env()).to.have.property('host', 'veronica.dev.local')
expect(Cypress.env()).to.have.property('api_server', 'http://localhost:8888/v2/')
})
})
context('Cypress.log', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/cypress-api')
})
it('Control what is printed to the Command Log', () => {
// https://on.cypress.io/cypress-log
})
})
context('Cypress.platform', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/cypress-api')
})
it('Get underlying OS name', () => {
// https://on.cypress.io/platform
expect(Cypress.platform).to.be.exist
})
})
context('Cypress.version', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/cypress-api')
})
it('Get current version of Cypress being run', () => {
// https://on.cypress.io/version
expect(Cypress.version).to.be.exist
})
})
context('Cypress.spec', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/cypress-api')
})
it('Get current spec information', () => {
// https://on.cypress.io/spec
// wrap the object so we can inspect it easily by clicking in the command log
cy.wrap(Cypress.spec).should('include.keys', ['name', 'relative', 'absolute'])
})
})

View File

@@ -1,88 +0,0 @@
/// <reference types="cypress" />
/// JSON fixture file can be loaded directly using
// the built-in JavaScript bundler
// @ts-ignore
const requiredExample = require('../../fixtures/example')
context('Files', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/files')
})
beforeEach(() => {
// load example.json fixture file and store
// in the test context object
cy.fixture('example.json').as('example')
})
it('cy.fixture() - load a fixture', () => {
// https://on.cypress.io/fixture
// Instead of writing a response inline you can
// use a fixture file's content.
// when application makes an Ajax request matching "GET **/comments/*"
// Cypress will intercept it and reply with the object in `example.json` fixture
cy.intercept('GET', '**/comments/*', { fixture: 'example.json' }).as('getComment')
// we have code that gets a comment when
// the button is clicked in scripts.js
cy.get('.fixture-btn').click()
cy.wait('@getComment').its('response.body')
.should('have.property', 'name')
.and('include', 'Using fixtures to represent data')
})
it('cy.fixture() or require - load a fixture', function () {
// we are inside the "function () { ... }"
// callback and can use test context object "this"
// "this.example" was loaded in "beforeEach" function callback
expect(this.example, 'fixture in the test context')
.to.deep.equal(requiredExample)
// or use "cy.wrap" and "should('deep.equal', ...)" assertion
cy.wrap(this.example)
.should('deep.equal', requiredExample)
})
it('cy.readFile() - read file contents', () => {
// https://on.cypress.io/readfile
// You can read a file and yield its contents
// The filePath is relative to your project's root.
cy.readFile('cypress.json').then((json) => {
expect(json).to.be.an('object')
})
})
it('cy.writeFile() - write to a file', () => {
// https://on.cypress.io/writefile
// You can write to a file
// Use a response from a request to automatically
// generate a fixture file for use later
cy.request('https://jsonplaceholder.cypress.io/users')
.then((response) => {
cy.writeFile('cypress/fixtures/users.json', response.body)
})
cy.fixture('users').should((users) => {
expect(users[0].name).to.exist
})
// JavaScript arrays and objects are stringified
// and formatted into text.
cy.writeFile('cypress/fixtures/profile.json', {
id: 8739,
name: 'Jane',
email: 'jane@example.com',
})
cy.fixture('profile').should((profile) => {
expect(profile.name).to.eq('Jane')
})
})
})

View File

@@ -1,52 +0,0 @@
/// <reference types="cypress" />
context('Local Storage', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/local-storage')
})
// Although local storage is automatically cleared
// in between tests to maintain a clean state
// sometimes we need to clear the local storage manually
it('cy.clearLocalStorage() - clear all data in local storage', () => {
// https://on.cypress.io/clearlocalstorage
cy.get('.ls-btn').click().should(() => {
expect(localStorage.getItem('prop1')).to.eq('red')
expect(localStorage.getItem('prop2')).to.eq('blue')
expect(localStorage.getItem('prop3')).to.eq('magenta')
})
// clearLocalStorage() yields the localStorage object
cy.clearLocalStorage().should((ls) => {
expect(ls.getItem('prop1')).to.be.null
expect(ls.getItem('prop2')).to.be.null
expect(ls.getItem('prop3')).to.be.null
})
cy.get('.ls-btn').click().should(() => {
expect(localStorage.getItem('prop1')).to.eq('red')
expect(localStorage.getItem('prop2')).to.eq('blue')
expect(localStorage.getItem('prop3')).to.eq('magenta')
})
// Clear key matching string in Local Storage
cy.clearLocalStorage('prop1').should((ls) => {
expect(ls.getItem('prop1')).to.be.null
expect(ls.getItem('prop2')).to.eq('blue')
expect(ls.getItem('prop3')).to.eq('magenta')
})
cy.get('.ls-btn').click().should(() => {
expect(localStorage.getItem('prop1')).to.eq('red')
expect(localStorage.getItem('prop2')).to.eq('blue')
expect(localStorage.getItem('prop3')).to.eq('magenta')
})
// Clear keys matching regex in Local Storage
cy.clearLocalStorage(/prop1|2/).should((ls) => {
expect(ls.getItem('prop1')).to.be.null
expect(ls.getItem('prop2')).to.be.null
expect(ls.getItem('prop3')).to.eq('magenta')
})
})
})

View File

@@ -1,32 +0,0 @@
/// <reference types="cypress" />
context('Location', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/location')
})
it('cy.hash() - get the current URL hash', () => {
// https://on.cypress.io/hash
cy.hash().should('be.empty')
})
it('cy.location() - get window.location', () => {
// https://on.cypress.io/location
cy.location().should((location) => {
expect(location.hash).to.be.empty
expect(location.href).to.eq('https://example.cypress.io/commands/location')
expect(location.host).to.eq('example.cypress.io')
expect(location.hostname).to.eq('example.cypress.io')
expect(location.origin).to.eq('https://example.cypress.io')
expect(location.pathname).to.eq('/commands/location')
expect(location.port).to.eq('')
expect(location.protocol).to.eq('https:')
expect(location.search).to.be.empty
})
})
it('cy.url() - get the current URL', () => {
// https://on.cypress.io/url
cy.url().should('eq', 'https://example.cypress.io/commands/location')
})
})

View File

@@ -1,104 +0,0 @@
/// <reference types="cypress" />
context('Misc', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/misc')
})
it('.end() - end the command chain', () => {
// https://on.cypress.io/end
// cy.end is useful when you want to end a chain of commands
// and force Cypress to re-query from the root element
cy.get('.misc-table').within(() => {
// ends the current chain and yields null
cy.contains('Cheryl').click().end()
// queries the entire table again
cy.contains('Charles').click()
})
})
it('cy.exec() - execute a system command', () => {
// execute a system command.
// so you can take actions necessary for
// your test outside the scope of Cypress.
// https://on.cypress.io/exec
// we can use Cypress.platform string to
// select appropriate command
// https://on.cypress/io/platform
cy.log(`Platform ${Cypress.platform} architecture ${Cypress.arch}`)
// on CircleCI Windows build machines we have a failure to run bash shell
// https://github.com/cypress-io/cypress/issues/5169
// so skip some of the tests by passing flag "--env circle=true"
const isCircleOnWindows = Cypress.platform === 'win32' && Cypress.env('circle')
if (isCircleOnWindows) {
cy.log('Skipping test on CircleCI')
return
}
// cy.exec problem on Shippable CI
// https://github.com/cypress-io/cypress/issues/6718
const isShippable = Cypress.platform === 'linux' && Cypress.env('shippable')
if (isShippable) {
cy.log('Skipping test on ShippableCI')
return
}
cy.exec('echo Jane Lane')
.its('stdout').should('contain', 'Jane Lane')
if (Cypress.platform === 'win32') {
cy.exec('print cypress.json')
.its('stderr').should('be.empty')
} else {
cy.exec('cat cypress.json')
.its('stderr').should('be.empty')
cy.exec('pwd')
.its('code').should('eq', 0)
}
})
it('cy.focused() - get the DOM element that has focus', () => {
// https://on.cypress.io/focused
cy.get('.misc-form').find('#name').click()
cy.focused().should('have.id', 'name')
cy.get('.misc-form').find('#description').click()
cy.focused().should('have.id', 'description')
})
context('Cypress.Screenshot', function () {
it('cy.screenshot() - take a screenshot', () => {
// https://on.cypress.io/screenshot
cy.screenshot('my-image')
})
it('Cypress.Screenshot.defaults() - change default config of screenshots', function () {
Cypress.Screenshot.defaults({
blackout: ['.foo'],
capture: 'viewport',
clip: { x: 0, y: 0, width: 200, height: 200 },
scale: false,
disableTimersAndAnimations: true,
screenshotOnRunFailure: true,
onBeforeScreenshot () { },
onAfterScreenshot () { },
})
})
})
it('cy.wrap() - wrap an object', () => {
// https://on.cypress.io/wrap
cy.wrap({ foo: 'bar' })
.should('have.property', 'foo')
.and('include', 'bar')
})
})

View File

@@ -1,56 +0,0 @@
/// <reference types="cypress" />
context('Navigation', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io')
cy.get('.navbar-nav').contains('Commands').click()
cy.get('.dropdown-menu').contains('Navigation').click()
})
it('cy.go() - go back or forward in the browser\'s history', () => {
// https://on.cypress.io/go
cy.location('pathname').should('include', 'navigation')
cy.go('back')
cy.location('pathname').should('not.include', 'navigation')
cy.go('forward')
cy.location('pathname').should('include', 'navigation')
// clicking back
cy.go(-1)
cy.location('pathname').should('not.include', 'navigation')
// clicking forward
cy.go(1)
cy.location('pathname').should('include', 'navigation')
})
it('cy.reload() - reload the page', () => {
// https://on.cypress.io/reload
cy.reload()
// reload the page without using the cache
cy.reload(true)
})
it('cy.visit() - visit a remote url', () => {
// https://on.cypress.io/visit
// Visit any sub-domain of your current domain
// Pass options to the visit
cy.visit('https://example.cypress.io/commands/navigation', {
timeout: 50000, // increase total time for the visit to resolve
onBeforeLoad (contentWindow) {
// contentWindow is the remote page's window object
expect(typeof contentWindow === 'object').to.be.true
},
onLoad (contentWindow) {
// contentWindow is the remote page's window object
expect(typeof contentWindow === 'object').to.be.true
},
})
})
})

View File

@@ -1,163 +0,0 @@
/// <reference types="cypress" />
context('Network Requests', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/network-requests')
})
// Manage HTTP requests in your app
it('cy.request() - make an XHR request', () => {
// https://on.cypress.io/request
cy.request('https://jsonplaceholder.cypress.io/comments')
.should((response) => {
expect(response.status).to.eq(200)
// the server sometimes gets an extra comment posted from another machine
// which gets returned as 1 extra object
expect(response.body).to.have.property('length').and.be.oneOf([500, 501])
expect(response).to.have.property('headers')
expect(response).to.have.property('duration')
})
})
it('cy.request() - verify response using BDD syntax', () => {
cy.request('https://jsonplaceholder.cypress.io/comments')
.then((response) => {
// https://on.cypress.io/assertions
expect(response).property('status').to.equal(200)
expect(response).property('body').to.have.property('length').and.be.oneOf([500, 501])
expect(response).to.include.keys('headers', 'duration')
})
})
it('cy.request() with query parameters', () => {
// will execute request
// https://jsonplaceholder.cypress.io/comments?postId=1&id=3
cy.request({
url: 'https://jsonplaceholder.cypress.io/comments',
qs: {
postId: 1,
id: 3,
},
})
.its('body')
.should('be.an', 'array')
.and('have.length', 1)
.its('0') // yields first element of the array
.should('contain', {
postId: 1,
id: 3,
})
})
it('cy.request() - pass result to the second request', () => {
// first, let's find out the userId of the first user we have
cy.request('https://jsonplaceholder.cypress.io/users?_limit=1')
.its('body') // yields the response object
.its('0') // yields the first element of the returned list
// the above two commands its('body').its('0')
// can be written as its('body.0')
// if you do not care about TypeScript checks
.then((user) => {
expect(user).property('id').to.be.a('number')
// make a new post on behalf of the user
cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', {
userId: user.id,
title: 'Cypress Test Runner',
body: 'Fast, easy and reliable testing for anything that runs in a browser.',
})
})
// note that the value here is the returned value of the 2nd request
// which is the new post object
.then((response) => {
expect(response).property('status').to.equal(201) // new entity created
expect(response).property('body').to.contain({
title: 'Cypress Test Runner',
})
// we don't know the exact post id - only that it will be > 100
// since JSONPlaceholder has built-in 100 posts
expect(response.body).property('id').to.be.a('number')
.and.to.be.gt(100)
// we don't know the user id here - since it was in above closure
// so in this test just confirm that the property is there
expect(response.body).property('userId').to.be.a('number')
})
})
it('cy.request() - save response in the shared test context', () => {
// https://on.cypress.io/variables-and-aliases
cy.request('https://jsonplaceholder.cypress.io/users?_limit=1')
.its('body').its('0') // yields the first element of the returned list
.as('user') // saves the object in the test context
.then(function () {
// NOTE 👀
// By the time this callback runs the "as('user')" command
// has saved the user object in the test context.
// To access the test context we need to use
// the "function () { ... }" callback form,
// otherwise "this" points at a wrong or undefined object!
cy.request('POST', 'https://jsonplaceholder.cypress.io/posts', {
userId: this.user.id,
title: 'Cypress Test Runner',
body: 'Fast, easy and reliable testing for anything that runs in a browser.',
})
.its('body').as('post') // save the new post from the response
})
.then(function () {
// When this callback runs, both "cy.request" API commands have finished
// and the test context has "user" and "post" objects set.
// Let's verify them.
expect(this.post, 'post has the right user id').property('userId').to.equal(this.user.id)
})
})
it('cy.intercept() - route responses to matching requests', () => {
// https://on.cypress.io/intercept
let message = 'whoa, this comment does not exist'
// Listen to GET to comments/1
cy.intercept('GET', '**/comments/*').as('getComment')
// we have code that gets a comment when
// the button is clicked in scripts.js
cy.get('.network-btn').click()
// https://on.cypress.io/wait
cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304])
// Listen to POST to comments
cy.intercept('POST', '**/comments').as('postComment')
// we have code that posts a comment when
// the button is clicked in scripts.js
cy.get('.network-post').click()
cy.wait('@postComment').should(({ request, response }) => {
expect(request.body).to.include('email')
expect(request.headers).to.have.property('content-type')
expect(response && response.body).to.have.property('name', 'Using POST in cy.intercept()')
})
// Stub a response to PUT comments/ ****
cy.intercept({
method: 'PUT',
url: '**/comments/*',
}, {
statusCode: 404,
body: { error: message },
headers: { 'access-control-allow-origin': '*' },
delayMs: 500,
}).as('putComment')
// we have code that puts a comment when
// the button is clicked in scripts.js
cy.get('.network-put').click()
cy.wait('@putComment')
// our 404 statusCode logic in scripts.js executed
cy.get('.network-put-comment').should('contain', message)
})
})

View File

@@ -1,114 +0,0 @@
/// <reference types="cypress" />
context('Querying', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/querying')
})
// The most commonly used query is 'cy.get()', you can
// think of this like the '$' in jQuery
it('cy.get() - query DOM elements', () => {
// https://on.cypress.io/get
cy.get('#query-btn').should('contain', 'Button')
cy.get('.query-btn').should('contain', 'Button')
cy.get('#querying .well>button:first').should('contain', 'Button')
// ↲
// Use CSS selectors just like jQuery
cy.get('[data-test-id="test-example"]').should('have.class', 'example')
// 'cy.get()' yields jQuery object, you can get its attribute
// by invoking `.attr()` method
cy.get('[data-test-id="test-example"]')
.invoke('attr', 'data-test-id')
.should('equal', 'test-example')
// or you can get element's CSS property
cy.get('[data-test-id="test-example"]')
.invoke('css', 'position')
.should('equal', 'static')
// or use assertions directly during 'cy.get()'
// https://on.cypress.io/assertions
cy.get('[data-test-id="test-example"]')
.should('have.attr', 'data-test-id', 'test-example')
.and('have.css', 'position', 'static')
})
it('cy.contains() - query DOM elements with matching content', () => {
// https://on.cypress.io/contains
cy.get('.query-list')
.contains('bananas')
.should('have.class', 'third')
// we can pass a regexp to `.contains()`
cy.get('.query-list')
.contains(/^b\w+/)
.should('have.class', 'third')
cy.get('.query-list')
.contains('apples')
.should('have.class', 'first')
// passing a selector to contains will
// yield the selector containing the text
cy.get('#querying')
.contains('ul', 'oranges')
.should('have.class', 'query-list')
cy.get('.query-button')
.contains('Save Form')
.should('have.class', 'btn')
})
it('.within() - query DOM elements within a specific element', () => {
// https://on.cypress.io/within
cy.get('.query-form').within(() => {
cy.get('input:first').should('have.attr', 'placeholder', 'Email')
cy.get('input:last').should('have.attr', 'placeholder', 'Password')
})
})
it('cy.root() - query the root DOM element', () => {
// https://on.cypress.io/root
// By default, root is the document
cy.root().should('match', 'html')
cy.get('.query-ul').within(() => {
// In this within, the root is now the ul DOM element
cy.root().should('have.class', 'query-ul')
})
})
it('best practices - selecting elements', () => {
// https://on.cypress.io/best-practices#Selecting-Elements
cy.get('[data-cy=best-practices-selecting-elements]').within(() => {
// Worst - too generic, no context
cy.get('button').click()
// Bad. Coupled to styling. Highly subject to change.
cy.get('.btn.btn-large').click()
// Average. Coupled to the `name` attribute which has HTML semantics.
cy.get('[name=submission]').click()
// Better. But still coupled to styling or JS event listeners.
cy.get('#main').click()
// Slightly better. Uses an ID but also ensures the element
// has an ARIA role attribute
cy.get('#main[role=button]').click()
// Much better. But still coupled to text content that may change.
cy.contains('Submit').click()
// Best. Insulated from all changes.
cy.get('[data-cy=submit]').click()
})
})
})

View File

@@ -1,205 +0,0 @@
/// <reference types="cypress" />
// remove no check once Cypress.sinon is typed
// https://github.com/cypress-io/cypress/issues/6720
context('Spies, Stubs, and Clock', () => {
it('cy.spy() - wrap a method in a spy', () => {
// https://on.cypress.io/spy
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
const obj = {
foo () {},
}
const spy = cy.spy(obj, 'foo').as('anyArgs')
obj.foo()
expect(spy).to.be.called
})
it('cy.spy() retries until assertions pass', () => {
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
const obj = {
/**
* Prints the argument passed
* @param x {any}
*/
foo (x) {
console.log('obj.foo called with', x)
},
}
cy.spy(obj, 'foo').as('foo')
setTimeout(() => {
obj.foo('first')
}, 500)
setTimeout(() => {
obj.foo('second')
}, 2500)
cy.get('@foo').should('have.been.calledTwice')
})
it('cy.stub() - create a stub and/or replace a function with stub', () => {
// https://on.cypress.io/stub
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
const obj = {
/**
* prints both arguments to the console
* @param a {string}
* @param b {string}
*/
foo (a, b) {
console.log('a', a, 'b', b)
},
}
const stub = cy.stub(obj, 'foo').as('foo')
obj.foo('foo', 'bar')
expect(stub).to.be.called
})
it('cy.clock() - control time in the browser', () => {
// https://on.cypress.io/clock
// create the date in UTC so its always the same
// no matter what local timezone the browser is running in
const now = new Date(Date.UTC(2017, 2, 14)).getTime()
cy.clock(now)
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
cy.get('#clock-div').click()
.should('have.text', '1489449600')
})
it('cy.tick() - move time in the browser', () => {
// https://on.cypress.io/tick
// create the date in UTC so its always the same
// no matter what local timezone the browser is running in
const now = new Date(Date.UTC(2017, 2, 14)).getTime()
cy.clock(now)
cy.visit('https://example.cypress.io/commands/spies-stubs-clocks')
cy.get('#tick-div').click()
.should('have.text', '1489449600')
cy.tick(10000) // 10 seconds passed
cy.get('#tick-div').click()
.should('have.text', '1489449610')
})
it('cy.stub() matches depending on arguments', () => {
// see all possible matchers at
// https://sinonjs.org/releases/latest/matchers/
const greeter = {
/**
* Greets a person
* @param {string} name
*/
greet (name) {
return `Hello, ${name}!`
},
}
cy.stub(greeter, 'greet')
.callThrough() // if you want non-matched calls to call the real method
.withArgs(Cypress.sinon.match.string).returns('Hi')
.withArgs(Cypress.sinon.match.number).throws(new Error('Invalid name'))
expect(greeter.greet('World')).to.equal('Hi')
// @ts-ignore
expect(() => greeter.greet(42)).to.throw('Invalid name')
expect(greeter.greet).to.have.been.calledTwice
// non-matched calls goes the actual method
// @ts-ignore
expect(greeter.greet()).to.equal('Hello, undefined!')
})
it('matches call arguments using Sinon matchers', () => {
// see all possible matchers at
// https://sinonjs.org/releases/latest/matchers/
const calculator = {
/**
* returns the sum of two arguments
* @param a {number}
* @param b {number}
*/
add (a, b) {
return a + b
},
}
const spy = cy.spy(calculator, 'add').as('add')
expect(calculator.add(2, 3)).to.equal(5)
// if we want to assert the exact values used during the call
expect(spy).to.be.calledWith(2, 3)
// let's confirm "add" method was called with two numbers
expect(spy).to.be.calledWith(Cypress.sinon.match.number, Cypress.sinon.match.number)
// alternatively, provide the value to match
expect(spy).to.be.calledWith(Cypress.sinon.match(2), Cypress.sinon.match(3))
// match any value
expect(spy).to.be.calledWith(Cypress.sinon.match.any, 3)
// match any value from a list
expect(spy).to.be.calledWith(Cypress.sinon.match.in([1, 2, 3]), 3)
/**
* Returns true if the given number is event
* @param {number} x
*/
const isEven = (x) => x % 2 === 0
// expect the value to pass a custom predicate function
// the second argument to "sinon.match(predicate, message)" is
// shown if the predicate does not pass and assertion fails
expect(spy).to.be.calledWith(Cypress.sinon.match(isEven, 'isEven'), 3)
/**
* Returns a function that checks if a given number is larger than the limit
* @param {number} limit
* @returns {(x: number) => boolean}
*/
const isGreaterThan = (limit) => (x) => x > limit
/**
* Returns a function that checks if a given number is less than the limit
* @param {number} limit
* @returns {(x: number) => boolean}
*/
const isLessThan = (limit) => (x) => x < limit
// you can combine several matchers using "and", "or"
expect(spy).to.be.calledWith(
Cypress.sinon.match.number,
Cypress.sinon.match(isGreaterThan(2), '> 2').and(Cypress.sinon.match(isLessThan(4), '< 4')),
)
expect(spy).to.be.calledWith(
Cypress.sinon.match.number,
Cypress.sinon.match(isGreaterThan(200), '> 200').or(Cypress.sinon.match(3)),
)
// matchers can be used from BDD assertions
cy.get('@add').should('have.been.calledWith',
Cypress.sinon.match.number, Cypress.sinon.match(3))
// you can alias matchers for shorter test code
const { match: M } = Cypress.sinon
cy.get('@add').should('have.been.calledWith', M.number, M(3))
})
})

View File

@@ -1,121 +0,0 @@
/// <reference types="cypress" />
context('Traversal', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/traversal')
})
it('.children() - get child DOM elements', () => {
// https://on.cypress.io/children
cy.get('.traversal-breadcrumb')
.children('.active')
.should('contain', 'Data')
})
it('.closest() - get closest ancestor DOM element', () => {
// https://on.cypress.io/closest
cy.get('.traversal-badge')
.closest('ul')
.should('have.class', 'list-group')
})
it('.eq() - get a DOM element at a specific index', () => {
// https://on.cypress.io/eq
cy.get('.traversal-list>li')
.eq(1).should('contain', 'siamese')
})
it('.filter() - get DOM elements that match the selector', () => {
// https://on.cypress.io/filter
cy.get('.traversal-nav>li')
.filter('.active').should('contain', 'About')
})
it('.find() - get descendant DOM elements of the selector', () => {
// https://on.cypress.io/find
cy.get('.traversal-pagination')
.find('li').find('a')
.should('have.length', 7)
})
it('.first() - get first DOM element', () => {
// https://on.cypress.io/first
cy.get('.traversal-table td')
.first().should('contain', '1')
})
it('.last() - get last DOM element', () => {
// https://on.cypress.io/last
cy.get('.traversal-buttons .btn')
.last().should('contain', 'Submit')
})
it('.next() - get next sibling DOM element', () => {
// https://on.cypress.io/next
cy.get('.traversal-ul')
.contains('apples').next().should('contain', 'oranges')
})
it('.nextAll() - get all next sibling DOM elements', () => {
// https://on.cypress.io/nextall
cy.get('.traversal-next-all')
.contains('oranges')
.nextAll().should('have.length', 3)
})
it('.nextUntil() - get next sibling DOM elements until next el', () => {
// https://on.cypress.io/nextuntil
cy.get('#veggies')
.nextUntil('#nuts').should('have.length', 3)
})
it('.not() - remove DOM elements from set of DOM elements', () => {
// https://on.cypress.io/not
cy.get('.traversal-disabled .btn')
.not('[disabled]').should('not.contain', 'Disabled')
})
it('.parent() - get parent DOM element from DOM elements', () => {
// https://on.cypress.io/parent
cy.get('.traversal-mark')
.parent().should('contain', 'Morbi leo risus')
})
it('.parents() - get parent DOM elements from DOM elements', () => {
// https://on.cypress.io/parents
cy.get('.traversal-cite')
.parents().should('match', 'blockquote')
})
it('.parentsUntil() - get parent DOM elements from DOM elements until el', () => {
// https://on.cypress.io/parentsuntil
cy.get('.clothes-nav')
.find('.active')
.parentsUntil('.clothes-nav')
.should('have.length', 2)
})
it('.prev() - get previous sibling DOM element', () => {
// https://on.cypress.io/prev
cy.get('.birds').find('.active')
.prev().should('contain', 'Lorikeets')
})
it('.prevAll() - get all previous sibling DOM elements', () => {
// https://on.cypress.io/prevall
cy.get('.fruits-list').find('.third')
.prevAll().should('have.length', 2)
})
it('.prevUntil() - get all previous sibling DOM elements until el', () => {
// https://on.cypress.io/prevuntil
cy.get('.foods-list').find('#nuts')
.prevUntil('#veggies').should('have.length', 3)
})
it('.siblings() - get all sibling DOM elements', () => {
// https://on.cypress.io/siblings
cy.get('.traversal-pills .active')
.siblings().should('have.length', 2)
})
})

View File

@@ -1,110 +0,0 @@
/// <reference types="cypress" />
context('Utilities', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/utilities')
})
it('Cypress._ - call a lodash method', () => {
// https://on.cypress.io/_
cy.request('https://jsonplaceholder.cypress.io/users')
.then((response) => {
let ids = Cypress._.chain(response.body).map('id').take(3).value()
expect(ids).to.deep.eq([1, 2, 3])
})
})
it('Cypress.$ - call a jQuery method', () => {
// https://on.cypress.io/$
let $li = Cypress.$('.utility-jquery li:first')
cy.wrap($li)
.should('not.have.class', 'active')
.click()
.should('have.class', 'active')
})
it('Cypress.Blob - blob utilities and base64 string conversion', () => {
// https://on.cypress.io/blob
cy.get('.utility-blob').then(($div) => {
// https://github.com/nolanlawson/blob-util#imgSrcToDataURL
// get the dataUrl string for the javascript-logo
return Cypress.Blob.imgSrcToDataURL('https://example.cypress.io/assets/img/javascript-logo.png', undefined, 'anonymous')
.then((dataUrl) => {
// create an <img> element and set its src to the dataUrl
let img = Cypress.$('<img />', { src: dataUrl })
// need to explicitly return cy here since we are initially returning
// the Cypress.Blob.imgSrcToDataURL promise to our test
// append the image
$div.append(img)
cy.get('.utility-blob img').click()
.should('have.attr', 'src', dataUrl)
})
})
})
it('Cypress.minimatch - test out glob patterns against strings', () => {
// https://on.cypress.io/minimatch
let matching = Cypress.minimatch('/users/1/comments', '/users/*/comments', {
matchBase: true,
})
expect(matching, 'matching wildcard').to.be.true
matching = Cypress.minimatch('/users/1/comments/2', '/users/*/comments', {
matchBase: true,
})
expect(matching, 'comments').to.be.false
// ** matches against all downstream path segments
matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/**', {
matchBase: true,
})
expect(matching, 'comments').to.be.true
// whereas * matches only the next path segment
matching = Cypress.minimatch('/foo/bar/baz/123/quux?a=b&c=2', '/foo/*', {
matchBase: false,
})
expect(matching, 'comments').to.be.false
})
it('Cypress.Promise - instantiate a bluebird promise', () => {
// https://on.cypress.io/promise
let waited = false
/**
* @return Bluebird<string>
*/
function waitOneSecond () {
// return a promise that resolves after 1 second
// @ts-ignore TS2351 (new Cypress.Promise)
return new Cypress.Promise((resolve, reject) => {
setTimeout(() => {
// set waited to true
waited = true
// resolve with 'foo' string
resolve('foo')
}, 1000)
})
}
cy.then(() => {
// return a promise to cy.then() that
// is awaited until it resolves
// @ts-ignore TS7006
return waitOneSecond().then((str) => {
expect(str).to.eq('foo')
expect(waited).to.be.true
})
})
})
})

View File

@@ -1,59 +0,0 @@
/// <reference types="cypress" />
context('Viewport', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/viewport')
})
it('cy.viewport() - set the viewport size and dimension', () => {
// https://on.cypress.io/viewport
cy.get('#navbar').should('be.visible')
cy.viewport(320, 480)
// the navbar should have collapse since our screen is smaller
cy.get('#navbar').should('not.be.visible')
cy.get('.navbar-toggle').should('be.visible').click()
cy.get('.nav').find('a').should('be.visible')
// lets see what our app looks like on a super large screen
cy.viewport(2999, 2999)
// cy.viewport() accepts a set of preset sizes
// to easily set the screen to a device's width and height
// We added a cy.wait() between each viewport change so you can see
// the change otherwise it is a little too fast to see :)
cy.viewport('macbook-15')
cy.wait(200)
cy.viewport('macbook-13')
cy.wait(200)
cy.viewport('macbook-11')
cy.wait(200)
cy.viewport('ipad-2')
cy.wait(200)
cy.viewport('ipad-mini')
cy.wait(200)
cy.viewport('iphone-6+')
cy.wait(200)
cy.viewport('iphone-6')
cy.wait(200)
cy.viewport('iphone-5')
cy.wait(200)
cy.viewport('iphone-4')
cy.wait(200)
cy.viewport('iphone-3')
cy.wait(200)
// cy.viewport() accepts an orientation for all presets
// the default orientation is 'portrait'
cy.viewport('ipad-2', 'portrait')
cy.wait(200)
cy.viewport('iphone-4', 'landscape')
cy.wait(200)
// The viewport will be reset back to the default dimensions
// in between tests (the default can be set in cypress.json)
})
})

View File

@@ -1,31 +0,0 @@
/// <reference types="cypress" />
context('Waiting', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/waiting')
})
// BE CAREFUL of adding unnecessary wait times.
// https://on.cypress.io/best-practices#Unnecessary-Waiting
// https://on.cypress.io/wait
it('cy.wait() - wait for a specific amount of time', () => {
cy.get('.wait-input1').type('Wait 1000ms after typing')
cy.wait(1000)
cy.get('.wait-input2').type('Wait 1000ms after typing')
cy.wait(1000)
cy.get('.wait-input3').type('Wait 1000ms after typing')
cy.wait(1000)
})
it('cy.wait() - wait for a specific route', () => {
// Listen to GET to comments/1
cy.intercept('GET', '**/comments/*').as('getComment')
// we have code that gets a comment when
// the button is clicked in scripts.js
cy.get('.network-btn').click()
// wait for GET comments/1
cy.wait('@getComment').its('response.statusCode').should('be.oneOf', [200, 304])
})
})

View File

@@ -1,22 +0,0 @@
/// <reference types="cypress" />
context('Window', () => {
beforeEach(() => {
cy.visit('https://example.cypress.io/commands/window')
})
it('cy.window() - get the global window object', () => {
// https://on.cypress.io/window
cy.window().should('have.property', 'top')
})
it('cy.document() - get the document object', () => {
// https://on.cypress.io/document
cy.document().should('have.property', 'charset').and('eq', 'UTF-8')
})
it('cy.title() - get the title', () => {
// https://on.cypress.io/title
cy.title().should('include', 'Kitchen Sink')
})
})

View File

@@ -1,22 +0,0 @@
/// <reference types="cypress" />
// ***********************************************************
// This example plugins/index.js can be used to load plugins
//
// You can change the location of this file or turn off loading
// the plugins file with the 'pluginsFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/plugins-guide
// ***********************************************************
// This function is called when a project is opened or re-opened (e.g. due to
// the project's config changing)
/**
* @type {Cypress.PluginConfig}
*/
// eslint-disable-next-line no-unused-vars
module.exports = (on, config) => {
// `on` is used to hook into various events Cypress emits
// `config` is the resolved Cypress config
}

View File

@@ -22,6 +22,4 @@
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
import "@testing-library/cypress/add-commands";
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })

View File

@@ -1,5 +1,5 @@
// ***********************************************************
// This example support/index.js is processed and
// This example support/e2e.js is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
@@ -17,4 +17,4 @@
import './commands'
// Alternatively you can use CommonJS syntax:
// require('./commands')
// require('./commands')

View File

@@ -1,8 +0,0 @@
{
"compilerOptions": {
"allowJs": true,
"baseUrl": "../node_modules",
"types": ["cypress"]
},
"include": ["**/*.*"]
}

View File

@@ -4,53 +4,54 @@
"private": true,
"proxy": "http://localhost:4000",
"dependencies": {
"@apollo/client": "^3.5.10",
"@apollo/client": "^3.6.9",
"@asseinfo/react-kanban": "^2.2.0",
"@craco/craco": "^6.4.3",
"@craco/craco": "^6.4.5",
"@fingerprintjs/fingerprintjs": "^3.3.3",
"@sentry/react": "^6.19.6",
"@sentry/tracing": "^6.19.6",
"@splitsoftware/splitio-react": "^1.3.1-rc.1",
"@stripe/react-stripe-js": "^1.7.1",
"@stripe/stripe-js": "^1.27.0",
"@tanem/react-nprogress": "^4.0.12",
"antd": "^4.19.5",
"@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",
"@tanem/react-nprogress": "^5.0.8",
"antd": "^4.22.3",
"apollo-link-logger": "^2.0.0",
"axios": "^0.26.1",
"axios": "^0.27.2",
"craco-less": "^1.20.0",
"dinero.js": "^1.9.1",
"dotenv": "^16.0.0",
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
"firebase": "^9.6.10",
"graphql": "^16.3.0",
"i18next": "^21.6.16",
"firebase": "^9.9.1",
"graphql": "^16.5.0",
"i18next": "^21.8.14",
"i18next-browser-languagedetector": "^6.1.4",
"jsoneditor": "^9.7.4",
"jsoneditor": "^9.9.0",
"jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.9.51",
"logrocket": "^2.2.1",
"markerjs2": "^2.21.0",
"libphonenumber-js": "^1.10.9",
"logrocket": "^3.0.1",
"markerjs2": "^2.22.0",
"moment-business-days": "^1.2.0",
"moment-timezone": "^0.5.34",
"phone": "^3.1.15",
"normalize-url": "^7.0.3",
"phone": "^3.1.23",
"preval.macro": "^5.0.0",
"prop-types": "^15.8.1",
"query-string": "^7.1.1",
"rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6",
"react": "^17.0.2",
"react-big-calendar": "^0.38.2",
"react-big-calendar": "^1.5.0",
"react-color": "^2.19.3",
"react-cookie": "^4.1.1",
"react-dom": "^17.0.2",
"react-drag-listview": "^0.1.9",
"react-drag-listview": "^0.2.1",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.3.4",
"react-i18next": "^11.16.5",
"react-icons": "^4.3.1",
"react-number-format": "^4.9.1",
"react-i18next": "^11.18.1",
"react-icons": "^4.4.0",
"react-number-format": "^4.9.3",
"react-redux": "^7.2.8",
"react-resizable": "^3.0.4",
"react-router-dom": "^5.3.0",
@@ -58,30 +59,29 @@
"react-sticky": "^6.0.3",
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3",
"recharts": "^2.1.9",
"redux": "^4.1.2",
"recharts": "^2.1.12",
"redux": "^4.2.0",
"redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.2",
"reselect": "^4.1.5",
"sass": "^1.50.0",
"socket.io-client": "^4.4.1",
"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",
"subscriptions-transport-ws": "^0.11.0",
"web-vitals": "^2.1.4",
"workbox-background-sync": "^6.5.2",
"workbox-broadcast-update": "^6.5.2",
"workbox-cacheable-response": "^6.5.2",
"workbox-core": "^6.5.2",
"workbox-expiration": "^6.5.2",
"workbox-google-analytics": "^6.5.2",
"workbox-navigation-preload": "^6.5.2",
"workbox-precaching": "^6.5.2",
"workbox-range-requests": "^6.5.2",
"workbox-routing": "^6.5.2",
"workbox-strategies": "^6.5.2",
"workbox-streams": "^6.5.2",
"yauzl": "^2.10.0"
"workbox-background-sync": "^6.5.3",
"workbox-broadcast-update": "^6.5.3",
"workbox-cacheable-response": "^6.5.3",
"workbox-core": "^6.5.3",
"workbox-expiration": "^6.5.3",
"workbox-google-analytics": "^6.5.3",
"workbox-navigation-preload": "^6.5.3",
"workbox-precaching": "^6.5.3",
"workbox-range-requests": "^6.5.3",
"workbox-routing": "^6.5.3",
"workbox-strategies": "^6.5.3",
"workbox-streams": "^6.5.3"
},
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'",
@@ -91,8 +91,7 @@
"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",
"madge": "madge --image ./madge-graph.svg --extensions js,jsx,ts,tsx --circular ."
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
@@ -117,11 +116,10 @@
"react-error-overlay": "6.0.9"
},
"devDependencies": {
"@sentry/webpack-plugin": "^1.18.8",
"@testing-library/cypress": "^8.0.2",
"cypress": "^9.5.3",
"@sentry/webpack-plugin": "^1.19.0",
"cypress": "^12.2.0",
"eslint-plugin-cypress": "^2.12.1",
"react-error-overlay": "6.0.10",
"react-error-overlay": "6.0.11",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.2"
}

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,4 @@
import { useTreatments } from "@splitsoftware/splitio-react";
import { useClient } from "@splitsoftware/splitio-react";
import { Button, Result } from "antd";
import LogRocket from "logrocket";
import React, { lazy, Suspense, useEffect } from "react";
@@ -51,11 +51,7 @@ export function App({
online,
setOnline,
}) {
const { LogRocket_Tracking } = useTreatments(
["LogRocket_Tracking"],
{},
bodyshop && bodyshop.imexshopid
);
const client = useClient();
useEffect(() => {
if (!navigator.onLine) {
@@ -78,15 +74,15 @@ export function App({
setOnline(true);
});
useEffect(() => {
if (currentUser.authorized) {
if (
process.env.NODE_ENV === "production" &&
LogRocket_Tracking.treatment === "on"
) {
if (currentUser.authorized && bodyshop) {
client.setAttribute("imexshopid", bodyshop.imexshopid);
if (client.getTreatment("LogRocket_Tracking") === "on") {
console.log("LR Start");
LogRocket.init("gvfvfw/bodyshopapp");
}
}
}, [currentUser.authorized, LogRocket_Tracking.treatment]);
}, [bodyshop, client, currentUser.authorized]);
if (currentUser.authorized === null) {
return <LoadingSpinner message={t("general.labels.loggingin")} />;

View File

@@ -142,3 +142,9 @@
}
}
}
//Update row highlighting on production board.
.ant-table-tbody > tr.ant-table-row:hover > td {
background: #eaeaea !important;
}

BIN
client/src/assets/banner4.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 KiB

View File

@@ -13,6 +13,8 @@ import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import BillMarkSelectedExported from "../payable-mark-selected-exported/payable-mark-selected-exported.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -27,7 +29,12 @@ export default connect(
mapDispatchToProps
)(AccountingPayablesTableComponent);
export function AccountingPayablesTableComponent({ bodyshop, loading, bills }) {
export function AccountingPayablesTableComponent({
bodyshop,
loading,
bills,
refetch,
}) {
const { t } = useTranslation();
const [selectedBills, setSelectedBills] = useState([]);
const [transInProgress, setTransInProgress] = useState(false);
@@ -131,11 +138,9 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills }) {
dataIndex: "attempts",
key: "attempts",
render: (text, record) => {
const success = record.exportlogs.filter((e) => e.successful).length;
const attempts = record.exportlogs.length;
return `${success}/${attempts}`;
},
render: (text, record) => (
<ExportLogsCountDisplay logs={record.exportlogs} />
),
},
{
title: t("general.labels.actions"),
@@ -144,14 +149,13 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills }) {
sorter: (a, b) => a.clm_total - b.clm_total,
render: (text, record) => (
<div>
<PayableExportButton
billId={record.id}
disabled={transInProgress || !!record.exported}
loadingCallback={setTransInProgress}
setSelectedBills={setSelectedBills}
/>
</div>
<PayableExportButton
billId={record.id}
disabled={transInProgress || !!record.exported}
loadingCallback={setTransInProgress}
setSelectedBills={setSelectedBills}
refetch={refetch}
/>
),
},
];
@@ -177,11 +181,19 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills }) {
<Card
extra={
<Space wrap>
<BillMarkSelectedExported
billids={selectedBills}
disabled={transInProgress || selectedBills.length === 0}
loadingCallback={setTransInProgress}
completedCallback={setSelectedBills}
refetch={refetch}
/>
<PayableExportAll
billids={selectedBills}
disabled={transInProgress || selectedBills.length === 0}
loadingCallback={setTransInProgress}
completedCallback={setSelectedBills}
refetch={refetch}
/>
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
<QboAuthorizeComponent />

View File

@@ -1,18 +1,20 @@
import { Card, Input, Space, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
import PaymentMarkSelectedExported from "../payment-mark-selected-exported/payment-mark-selected-exported.component";
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -31,6 +33,7 @@ export function AccountingPayablesTableComponent({
bodyshop,
loading,
payments,
refetch,
}) {
const { t } = useTranslation();
const [selectedPayments, setSelectedPayments] = useState([]);
@@ -130,11 +133,9 @@ export function AccountingPayablesTableComponent({
dataIndex: "attempts",
key: "attempts",
render: (text, record) => {
const success = record.exportlogs.filter((e) => e.successful).length;
const attempts = record.exportlogs.length;
return `${success}/${attempts}`;
},
render: (text, record) => (
<ExportLogsCountDisplay logs={record.exportlogs} />
),
},
{
title: t("general.labels.actions"),
@@ -148,6 +149,7 @@ export function AccountingPayablesTableComponent({
disabled={transInProgress || !!record.exportedat}
loadingCallback={setTransInProgress}
setSelectedPayments={setSelectedPayments}
refetch={refetch}
/>
),
},
@@ -183,11 +185,19 @@ export function AccountingPayablesTableComponent({
<Card
extra={
<Space wrap>
<PaymentMarkSelectedExported
paymentIds={selectedPayments}
disabled={transInProgress || selectedPayments.length === 0}
loadingCallback={setTransInProgress}
completedCallback={setSelectedPayments}
refetch={refetch}
/>
<PaymentsExportAllButton
paymentIds={selectedPayments}
disabled={transInProgress || selectedPayments.length === 0}
loadingCallback={setTransInProgress}
completedCallback={setSelectedPayments}
refetch={refetch}
/>
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
<QboAuthorizeComponent />

View File

@@ -14,6 +14,7 @@ import { selectBodyshop } from "../../redux/user/user.selectors";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { DateFormatter } from "../../utils/DateFormatter";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -30,6 +31,7 @@ export function AccountingReceivablesTableComponent({
bodyshop,
loading,
jobs,
refetch,
}) {
const { t } = useTranslation();
const [selectedJobs, setSelectedJobs] = useState([]);
@@ -139,12 +141,9 @@ export function AccountingReceivablesTableComponent({
title: t("exportlogs.labels.attempts"),
dataIndex: "attempts",
key: "attempts",
render: (text, record) => {
const success = record.exportlogs.filter((e) => e.successful).length;
const attempts = record.exportlogs.length;
return `${success}/${attempts}`;
},
render: (text, record) => (
<ExportLogsCountDisplay logs={record.exportlogs} />
),
},
{
title: t("general.labels.actions"),
@@ -157,6 +156,7 @@ export function AccountingReceivablesTableComponent({
jobId={record.id}
disabled={!!record.date_exported}
setSelectedJobs={setSelectedJobs}
refetch={refetch}
/>
<Link to={`/manage/jobs/${record.id}/close`}>
<Button>{t("jobs.labels.viewallocations")}</Button>
@@ -207,6 +207,7 @@ export function AccountingReceivablesTableComponent({
disabled={transInProgress || selectedJobs.length === 0}
loadingCallback={setTransInProgress}
completedCallback={setSelectedJobs}
refetch={refetch}
/>
)}
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (

View File

@@ -4,6 +4,8 @@ import { useQuery } from "@apollo/client";
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import AlertComponent from "../alert/alert.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
import EmailAuditTrailListComponent from "./email-audit-trail-list.component";
import { Card, Row } from "antd";
export default function AuditTrailListContainer({ recordId }) {
const { loading, error, data } = useQuery(QUERY_AUDIT_TRAIL, {
@@ -18,10 +20,20 @@ export default function AuditTrailListContainer({ recordId }) {
{error ? (
<AlertComponent type="error" message={error.message} />
) : (
<AuditTrailListComponent
loading={loading}
data={data ? data.audit_trail : null}
/>
<Row gutter={[16, 16]}>
<Card>
<AuditTrailListComponent
loading={loading}
data={data ? data.audit_trail : []}
/>
</Card>
<Card>
<EmailAuditTrailListComponent
loading={loading}
data={data ? data.audit_trail : []}
/>
</Card>
</Row>
)}
</div>
);

View File

@@ -0,0 +1,63 @@
import { Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
export default function EmailAuditTrailListComponent({ loading, data }) {
const [state, setState] = useState({
sortedInfo: {},
filteredInfo: {},
});
const { t } = useTranslation();
const columns = [
{
title: t("audit.fields.created"),
dataIndex: " created",
key: " created",
width: "10%",
render: (text, record) => (
<DateTimeFormatter>{record.created}</DateTimeFormatter>
),
sorter: (a, b) => a.created - b.created,
sortOrder:
state.sortedInfo.columnKey === "created" && state.sortedInfo.order,
},
{
title: t("audit.fields.useremail"),
dataIndex: "useremail",
key: "useremail",
width: "10%",
sorter: (a, b) => alphaSort(a.useremail, b.useremail),
sortOrder:
state.sortedInfo.columnKey === "useremail" && state.sortedInfo.order,
},
];
const formItemLayout = {
labelCol: {
xs: { span: 12 },
sm: { span: 5 },
},
wrapperCol: {
xs: { span: 24 },
sm: { span: 12 },
},
};
const handleTableChange = (pagination, filters, sorter) => {
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
return (
<Table
{...formItemLayout}
loading={loading}
pagination={{ position: "top", defaultPageSize: 25 }}
columns={columns}
rowKey="id"
dataSource={data}
onChange={handleTableChange}
/>
);
}

View File

@@ -0,0 +1,135 @@
import { Checkbox, Form, Skeleton, Typography } from "antd";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
import "./bill-cm-returns-table.styles.scss";
export default function BillCmdReturnsTableComponent({
form,
returnLoading,
returnData,
}) {
const { t } = useTranslation();
useEffect(() => {
if (returnData) {
form.setFieldsValue({
outstanding_returns: returnData.parts_order_lines,
});
}
}, [returnData, form]);
return (
<Form.Item
shouldUpdate={(prev, cur) =>
prev.jobid !== cur.jobid ||
prev.is_credit_memo !== cur.is_credit_memo ||
prev.vendorid !== cur.vendorid
}
noStyle
>
{() => {
const isReturn = form.getFieldValue("is_credit_memo");
if (!isReturn) {
return null;
}
if (returnLoading) return <Skeleton />;
return (
<Form.List name="outstanding_returns">
{(fields, { add, remove, move }) => {
return (
<>
<Typography.Title level={4}>
{t("bills.labels.creditsnotreceived")}
</Typography.Title>
<table className="bill-cm-returns-table">
<thead>
<tr>
<th>{t("parts_orders.fields.line_desc")}</th>
<th>{t("parts_orders.fields.part_type")}</th>
<th>{t("parts_orders.fields.quantity")}</th>
<th>{t("parts_orders.fields.act_price")}</th>
<th>{t("parts_orders.fields.cost")}</th>
<th>{t("parts_orders.labels.mark_as_received")}</th>
</tr>
</thead>
<tbody>
{fields.map((field, index) => (
<tr key={field.key}>
<td>
<Form.Item
// label={t("joblines.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
>
<ReadOnlyFormItemComponent />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
//label={t("joblines.fields.mod_lb_hrs")}
key={`${index}part_type`}
name={[field.name, "part_type"]}
>
<ReadOnlyFormItemComponent />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
//label={t("joblines.fields.mod_lb_hrs")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
>
<ReadOnlyFormItemComponent />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
//label={t("joblines.fields.mod_lb_hrs")}
key={`${index}act_price`}
name={[field.name, "act_price"]}
>
<ReadOnlyFormItemComponent type="currency" />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
//label={t("joblines.fields.mod_lb_hrs")}
key={`${index}cost`}
name={[field.name, "cost"]}
>
<ReadOnlyFormItemComponent type="currency" />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
//label={t("joblines.fields.mod_lb_hrs")}
key={`${index}cm_received`}
name={[field.name, "cm_received"]}
valuePropName="checked"
>
<Checkbox />
</Form.Item>
</td>
</tr>
))}
</tbody>
</table>
</>
);
}}
</Form.List>
);
}}
</Form.Item>
);
}

View File

@@ -0,0 +1,19 @@
.bill-cm-returns-table {
table-layout: fixed;
width: 100%;
th,
td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
.ant-form-item {
margin-bottom: 0px !important;
}
}
tr:hover {
background-color: #f5f5f5;
}
}

View File

@@ -15,7 +15,8 @@ export default function BillDeleteButton({ bill }) {
setLoading(true);
const result = await deleteBill({
variables: { billId: bill.id },
update(cache) {
update(cache, { errors }) {
if (errors) return;
cache.modify({
fields: {
bills(existingBills, { readField }) {
@@ -36,11 +37,22 @@ export default function BillDeleteButton({ bill }) {
if (!!!result.errors) {
notification["success"]({ message: t("bills.successes.deleted") });
} else {
notification["error"]({
message: t("bills.errors.deleting", {
error: JSON.stringify(result.errors),
}),
});
//Check if it's an fkey violation.
const error = JSON.stringify(result.errors);
if (error.toLowerCase().includes("inventory_billid_fkey")) {
notification["error"]({
message: t("bills.errors.deleting", {
error: t("bills.errors.existinginventoryline"),
}),
});
} else {
notification["error"]({
message: t("bills.errors.deleting", {
error: JSON.stringify(result.errors),
}),
});
}
}
setLoading(false);

View File

@@ -0,0 +1,251 @@
import { useMutation, useQuery } from "@apollo/client";
import { Button, Form, PageHeader, Popconfirm, Space } from "antd";
import moment from "moment";
import queryString from "query-string";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {
DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES,
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";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import AlertComponent from "../alert/alert.component";
import BillFormContainer from "../bill-form/bill-form.container";
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
import JobsDocumentsLocalGallery from "../jobs-documents-local-gallery/jobs-documents-local-gallery.container";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import BillDetailEditReturn from "./bill-detail-edit-return.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillDetailEditcontainer);
export function BillDetailEditcontainer({
setPartsOrderContext,
insertAuditTrail,
bodyshop,
}) {
const search = queryString.parse(useLocation().search);
const { t } = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const [updateLoading, setUpdateLoading] = useState(false);
const [update_bill] = useMutation(UPDATE_BILL);
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
variables: { billid: search.billid },
skip: !!!search.billid,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const handleSave = () => {
//It's got a previously deducted bill line!
if (
data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 ||
form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length >
0
)
setVisible(true);
else {
form.submit();
}
};
const handleFinish = async (values) => {
setUpdateLoading(true);
//let adjustmentsToInsert = {};
const { billlines, upload, ...bill } = values;
const updates = [];
updates.push(
update_bill({
variables: { billId: search.billid, bill: bill },
})
);
billlines.forEach((l) => {
delete l.selected;
});
//Find bill lines that were deleted.
const deletedJobLines = [];
data.bills_by_pk.billlines.forEach((a) => {
const matchingRecord = billlines.find((b) => b.id === a.id);
if (!matchingRecord) {
deletedJobLines.push(a);
}
});
deletedJobLines.forEach((d) => {
updates.push(deleteBillLine({ variables: { id: d.id } }));
});
billlines.forEach((billline) => {
const { deductedfromlbr, inventories, jobline, ...il } = billline;
delete il.__typename;
if (il.id) {
updates.push(
updateBillLine({
variables: {
billLineId: il.id,
billLine: {
...il,
deductedfromlbr: deductedfromlbr,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
},
})
);
} else {
//It's a new line, have to insert it.
updates.push(
insertBillLine({
variables: {
billLines: [
{
...il,
deductedfromlbr: deductedfromlbr,
billid: search.billid,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
],
},
})
);
}
});
await Promise.all(updates);
insertAuditTrail({
jobid: bill.jobid,
billid: search.billid,
operation: AuditTrailMapping.billupdated(bill.invoice_number),
});
await refetch();
form.setFieldsValue(transformData(data));
form.resetFields();
setVisible(false);
setUpdateLoading(false);
};
if (error) return <AlertComponent message={error.message} type="error" />;
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
return (
<>
{loading && <LoadingSkeleton />}
{data && (
<>
<PageHeader
title={
data &&
`${data.bills_by_pk.invoice_number} - ${data.bills_by_pk.vendor.name}`
}
extra={
<Space>
<BillDetailEditReturn data={data} />
<Popconfirm
visible={visible}
onConfirm={() => form.submit()}
onCancel={() => setVisible(false)}
okButtonProps={{ loading: updateLoading }}
title={t("bills.labels.editadjwarning")}
>
<Button
htmlType="submit"
disabled={exported}
onClick={handleSave}
loading={updateLoading}
type="primary"
>
{t("general.actions.save")}
</Button>
</Popconfirm>
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
<BillMarkExportedButton bill={data && data.bills_by_pk} />
</Space>
}
/>
<Form
form={form}
onFinish={handleFinish}
initialValues={transformData(data)}
layout="vertical"
>
<BillFormContainer form={form} billEdit disabled={exported} />
{bodyshop.uselocalmediaserver ? (
<JobsDocumentsLocalGallery
job={{ id: data ? data.bills_by_pk.jobid : null }}
invoice_number={data ? data.bills_by_pk.invoice_number : null}
vendorid={data ? data.bills_by_pk.vendorid : null}
/>
) : (
<JobDocumentsGallery
jobId={data ? data.bills_by_pk.jobid : null}
billId={search.billid}
documentsList={data ? data.bills_by_pk.documents : []}
billsCallback={refetch}
/>
)}
</Form>
</>
)}
</>
);
}
const transformData = (data) => {
return data
? {
...data.bills_by_pk,
billlines: data.bills_by_pk.billlines.map((i) => {
return {
...i,
joblineid: !!i.joblineid ? i.joblineid : "noline",
applicable_taxes: {
federal:
(i.applicable_taxes && i.applicable_taxes.federal) || false,
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
},
};
}),
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
}
: {};
};

View File

@@ -0,0 +1,185 @@
import { Button, Checkbox, Form, Modal } from "antd";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillDetailEditReturn);
export function BillDetailEditReturn({
setPartsOrderContext,
insertAuditTrail,
bodyshop,
data,
disabled,
}) {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const { t } = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const handleFinish = ({ billlines }) => {
const selectedLines = billlines.filter((l) => l.selected).map((l) => l.id);
setPartsOrderContext({
actions: {},
context: {
jobId: data.bills_by_pk.jobid,
vendorId: data.bills_by_pk.vendorid,
returnFromBill: data.bills_by_pk.id,
invoiceNumber: data.bills_by_pk.invoice_number,
linesToOrder: data.bills_by_pk.billlines
.filter((l) => selectedLines.includes(l.id))
.map((i) => {
return {
line_desc: i.line_desc,
// db_price: i.actual_price,
act_price: i.actual_price,
cost: i.actual_cost,
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type,
};
}),
isReturn: true,
},
});
delete search.billid;
history.push({ search: queryString.stringify(search) });
setVisible(false);
};
useEffect(() => {
if (visible === false) form.resetFields();
}, [visible, form]);
return (
<>
<Modal
visible={visible}
onCancel={() => setVisible(false)}
destroyOnClose
title={t("bills.actions.return")}
onOk={() => form.submit()}
>
<Form
initialValues={data && data.bills_by_pk}
onFinish={handleFinish}
form={form}
>
<Form.List name={["billlines"]}>
{(fields, { add, remove, move }) => {
return (
<table style={{ tableLayout: "auto", width: "100%" }}>
<thead>
<tr>
<td>
<Checkbox
onChange={(e) => {
form.setFieldsValue({
billlines: form
.getFieldsValue()
.billlines.map((b) => ({
...b,
selected: e.target.checked,
})),
});
}}
/>
</td>
<td>{t("billlines.fields.line_desc")}</td>
<td>{t("billlines.fields.quantity")}</td>
<td>{t("billlines.fields.actual_price")}</td>
<td>{t("billlines.fields.actual_cost")}</td>
</tr>
</thead>
<tbody>
{fields.map((field, index) => (
<tr key={field.key}>
<td>
<Form.Item
// label={t("joblines.fields.selected")}
key={`${index}selected`}
name={[field.name, "selected"]}
valuePropName="checked"
>
<Checkbox />
</Form.Item>
</td>
<td>
<Form.Item
// label={t("joblines.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
>
<ReadOnlyFormItemComponent />
</Form.Item>
</td>
<td>
<Form.Item
// label={t("joblines.fields.quantity")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
>
<ReadOnlyFormItemComponent />
</Form.Item>
</td>
<td>
<Form.Item
// label={t("joblines.fields.actual_price")}
key={`${index}actual_price`}
name={[field.name, "actual_price"]}
>
<ReadOnlyFormItemComponent type="currency" />
</Form.Item>
</td>
<td>
<Form.Item
// label={t("joblines.fields.actual_cost")}
key={`${index}actual_cost`}
name={[field.name, "actual_cost"]}
>
<ReadOnlyFormItemComponent type="currency" />
</Form.Item>
</td>
</tr>
))}
</tbody>
</table>
);
}}
</Form.List>
</Form>
</Modal>
<Button
disabled={data.bills_by_pk.is_credit_memo || disabled}
onClick={() => {
setVisible(true);
}}
>
{t("bills.actions.return")}
</Button>
</>
);
}

View File

@@ -1,65 +1,12 @@
import { useMutation, useQuery } from "@apollo/client";
import {
Button,
Drawer,
Form,
Grid,
PageHeader,
Popconfirm,
Space,
} from "antd";
import moment from "moment";
import { Drawer, Grid } from "antd";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { useLocation, useHistory } from "react-router-dom";
import {
DELETE_BILL_LINE,
INSERT_NEW_BILL_LINES,
UPDATE_BILL_LINE,
} from "../../graphql/bill-lines.queries";
import { QUERY_BILL_BY_PK, UPDATE_BILL } from "../../graphql/bills.queries";
import AlertComponent from "../alert/alert.component";
import BillFormContainer from "../bill-form/bill-form.container";
import JobDocumentsGallery from "../jobs-documents-gallery/jobs-documents-gallery.container";
import LoadingSkeleton from "../loading-skeleton/loading-skeleton.component";
import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-button.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import BillMarkExportedButton from "../bill-mark-exported-button/bill-mark-exported-button.component";
import React from "react";
import { useHistory, useLocation } from "react-router-dom";
import BillDetailEditComponent from "./bill-detail-edit-component";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BillDetailEditcontainer);
export function BillDetailEditcontainer({
setPartsOrderContext,
insertAuditTrail,
}) {
export default function BillDetailEditcontainer() {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const { t } = useTranslation();
const [form] = Form.useForm();
const [visible, setVisible] = useState(false);
const [updateLoading, setUpdateLoading] = useState(false);
const [update_bill] = useMutation(UPDATE_BILL);
const [insertBillLine] = useMutation(INSERT_NEW_BILL_LINES);
const [updateBillLine] = useMutation(UPDATE_BILL_LINE);
const [deleteBillLine] = useMutation(DELETE_BILL_LINE);
const selectedBreakpoint = Object.entries(Grid.useBreakpoint())
.filter((screen) => !!screen[1])
@@ -70,121 +17,13 @@ export function BillDetailEditcontainer({
sm: "100%",
md: "100%",
lg: "100%",
xl: "80%",
xxl: "80%",
xl: "90%",
xxl: "90%",
};
const drawerPercentage = selectedBreakpoint
? bpoints[selectedBreakpoint[0]]
: "100%";
const { loading, error, data, refetch } = useQuery(QUERY_BILL_BY_PK, {
variables: { billid: search.billid },
skip: !!!search.billid,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
});
const handleSave = () => {
//It's got a previously deducted bill line!
if (
data.bills_by_pk.billlines.filter((b) => b.deductedfromlbr).length > 0 ||
form.getFieldValue("billlines").filter((b) => b.deductedfromlbr).length >
0
)
setVisible(true);
else {
form.submit();
}
};
const handleFinish = async (values) => {
setUpdateLoading(true);
//let adjustmentsToInsert = {};
const { billlines, upload, ...bill } = values;
const updates = [];
updates.push(
update_bill({
variables: { billId: search.billid, bill: bill },
})
);
//Find bill lines that were deleted.
const deletedJobLines = [];
data.bills_by_pk.billlines.forEach((a) => {
const matchingRecord = billlines.find((b) => b.id === a.id);
if (!matchingRecord) {
deletedJobLines.push(a);
}
});
deletedJobLines.forEach((d) => {
updates.push(deleteBillLine({ variables: { id: d.id } }));
});
billlines.forEach((billline) => {
const { deductedfromlbr, jobline, ...il } = billline;
delete il.__typename;
if (il.id) {
updates.push(
updateBillLine({
variables: {
billLineId: il.id,
billLine: {
...il,
deductedfromlbr: deductedfromlbr,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
},
})
);
} else {
//It's a new line, have to insert it.
updates.push(
insertBillLine({
variables: {
billLines: [
{
...il,
deductedfromlbr: deductedfromlbr,
billid: search.billid,
joblineid: il.joblineid === "noline" ? null : il.joblineid,
},
],
},
})
);
}
});
await Promise.all(updates);
insertAuditTrail({
jobid: bill.jobid,
billid: search.billid,
operation: AuditTrailMapping.billupdated(bill.invoice_number),
});
await refetch();
form.setFieldsValue(transformData(data));
form.resetFields();
setVisible(false);
setUpdateLoading(false);
};
useEffect(() => {
if (search.billid && data) {
form.resetFields();
}
}, [form, search.billid, data]);
if (error) return <AlertComponent message={error.message} type="error" />;
if (!search.billid) return <></>; //<div>{t("bills.labels.noneselected")}</div>;
const exported = data && data.bills_by_pk && data.bills_by_pk.exported;
return (
<Drawer
width={drawerPercentage}
@@ -192,110 +31,10 @@ export function BillDetailEditcontainer({
delete search.billid;
history.push({ search: queryString.stringify(search) });
}}
destroyOnClose
visible={search.billid}
>
{loading && <LoadingSkeleton />}
{!loading && (
<>
<PageHeader
title={
data &&
`${data.bills_by_pk.invoice_number} - ${data.bills_by_pk.vendor.name}`
}
extra={
<Space>
<Button
disabled={data.bills_by_pk.is_credit_memo}
onClick={() => {
delete search.billid;
history.push({ search: queryString.stringify(search) });
setPartsOrderContext({
actions: {},
context: {
jobId: data.bills_by_pk.jobid,
vendorId: data.bills_by_pk.vendorid,
returnFromBill: data.bills_by_pk.id,
invoiceNumber: data.bills_by_pk.invoice_number,
linesToOrder: data.bills_by_pk.billlines.map((i) => {
return {
line_desc: i.line_desc,
// db_price: i.actual_price,
act_price: i.actual_price,
cost: i.actual_cost,
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type,
};
}),
isReturn: true,
},
});
}}
>
{t("bills.actions.return")}
</Button>
<Popconfirm
visible={visible}
onConfirm={() => form.submit()}
onCancel={() => setVisible(false)}
okButtonProps={{ loading: updateLoading }}
title={t("bills.labels.editadjwarning")}
>
<Button
htmlType="submit"
disabled={exported}
onClick={handleSave}
loading={updateLoading}
type="primary"
>
{t("general.actions.save")}
</Button>
</Popconfirm>
<BillReeportButtonComponent bill={data && data.bills_by_pk} />
<BillMarkExportedButton bill={data && data.bills_by_pk} />
</Space>
}
/>
<Form
form={form}
onFinish={handleFinish}
initialValues={transformData(data)}
layout="vertical"
>
<BillFormContainer form={form} billEdit disabled={exported} />
<JobDocumentsGallery
jobId={data ? data.bills_by_pk.jobid : null}
billId={search.billid}
documentsList={data ? data.bills_by_pk.documents : []}
billsCallback={refetch}
/>
</Form>
</>
)}
<BillDetailEditComponent />
</Drawer>
);
}
const transformData = (data) => {
return data
? {
...data.bills_by_pk,
billlines: data.bills_by_pk.billlines.map((i) => {
return {
...i,
joblineid: !!i.joblineid ? i.joblineid : "noline",
applicable_taxes: {
federal:
(i.applicable_taxes && i.applicable_taxes.federal) || false,
state: (i.applicable_taxes && i.applicable_taxes.state) || false,
local: (i.applicable_taxes && i.applicable_taxes.local) || false,
},
};
}),
date: data.bills_by_pk ? moment(data.bills_by_pk.date) : null,
}
: {};
};

View File

@@ -1,5 +1,5 @@
import { useApolloClient, useMutation } from "@apollo/client";
import { Button, Form, Modal, notification, Space } from "antd";
import { Button, Checkbox, Form, Modal, notification, Space } from "antd";
import _ from "lodash";
import React, { useEffect, useState, useMemo } from "react";
import { useTranslation } from "react-i18next";
@@ -11,6 +11,8 @@ import {
QUERY_JOB_LBR_ADJUSTMENTS,
UPDATE_JOB,
} from "../../graphql/jobs.queries";
import { MUTATION_MARK_RETURN_RECEIVED } from "../../graphql/parts-orders.queries";
import { UPDATE_INVENTORY_LINES } from "../../graphql/inventory.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
@@ -23,6 +25,10 @@ import AuditTrailMapping from "../../utils/AuditTrailMappings";
import BillFormContainer from "../bill-form/bill-form.container";
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
import { handleUpload } from "../documents-upload/documents-upload.utility";
import { handleUpload as handleLocalUpload } from "../documents-local-upload/documents-local-upload.utility";
import useLocalStorage from "../../utils/useLocalStorage";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
const mapStateToProps = createStructuredSelector({
billEnterModal: selectBillEnterModal,
@@ -35,6 +41,8 @@ const mapDispatchToProps = (dispatch) => ({
dispatch(insertAuditTrail({ jobid, operation })),
});
const Templates = TemplateList("job_special");
function BillEnterModalContainer({
billEnterModal,
toggleModalVisible,
@@ -47,9 +55,14 @@ function BillEnterModalContainer({
const [enterAgain, setEnterAgain] = useState(false);
const [insertBill] = useMutation(INSERT_NEW_BILL);
const [updateJobLines] = useMutation(UPDATE_JOB_LINE);
const [updatePartsOrderLines] = useMutation(MUTATION_MARK_RETURN_RECEIVED);
const [updateInventoryLines] = useMutation(UPDATE_INVENTORY_LINES);
const [loading, setLoading] = useState(false);
const client = useApolloClient();
const [generateLabel, setGenerateLabel] = useLocalStorage(
"enter_bill_generate_label",
false
);
const formValues = useMemo(() => {
return {
...billEnterModal.context.bill,
@@ -76,7 +89,13 @@ function BillEnterModalContainer({
}
setLoading(true);
const { upload, location, ...remainingValues } = values;
const {
upload,
location,
outstanding_returns,
inventory,
...remainingValues
} = values;
let adjustmentsToInsert = {};
@@ -133,6 +152,14 @@ function BillEnterModalContainer({
adjKeys.forEach((key) => {
newAdjustments[key] =
(newAdjustments[key] || 0) + adjustmentsToInsert[key];
insertAuditTrail({
jobid: values.jobid,
operation: AuditTrailMapping.jobmodifylbradj({
mod_lbr_ty: key,
hours: adjustmentsToInsert[key].toFixed(1),
}),
});
});
const jobUpdate = client.mutate({
@@ -150,10 +177,25 @@ function BillEnterModalContainer({
});
return;
}
insertAuditTrail({
jobid: values.jobid,
operation: AuditTrailMapping.jobmodifylbradj(),
}
const markPolReceived =
outstanding_returns &&
outstanding_returns.filter((o) => o.cm_received === true);
if (markPolReceived && markPolReceived.length > 0) {
const r2 = await updatePartsOrderLines({
variables: { partsLineIds: markPolReceived.map((p) => p.id) },
});
if (!!r2.errors) {
setLoading(false);
setEnterAgain(false);
notification["error"]({
message: t("parts_orders.errors.updating", {
message: JSON.stringify(r2.errors),
}),
});
}
}
if (!!r1.errors) {
@@ -167,46 +209,97 @@ function BillEnterModalContainer({
}
const billId = r1.data.insert_bills.returning[0].id;
const markInventoryConsumed =
inventory && inventory.filter((i) => i.consumefrominventory);
await Promise.all(
remainingValues.billlines
.filter((il) => il.joblineid !== "noline")
.map((li) => {
return updateJobLines({
variables: {
lineId: li.joblineid,
line: {
location: li.location || location,
status:
bodyshop.md_order_statuses.default_received || "Received*",
if (markInventoryConsumed && markInventoryConsumed.length > 0) {
const r2 = await updateInventoryLines({
variables: {
InventoryIds: markInventoryConsumed.map((p) => p.id),
consumedbybillid: billId,
},
});
if (!!r2.errors) {
setLoading(false);
setEnterAgain(false);
notification["error"]({
message: t("inventory.errors.updating", {
message: JSON.stringify(r2.errors),
}),
});
}
}
//If it's not a credit memo, update the statuses.
if (!values.is_credit_memo) {
await Promise.all(
remainingValues.billlines
.filter((il) => il.joblineid !== "noline")
.map((li) => {
return updateJobLines({
variables: {
lineId: li.joblineid,
line: {
location: li.location || location,
status:
bodyshop.md_order_statuses.default_received || "Received*",
},
},
},
});
})
);
});
})
);
}
/////////////////////////
if (upload && upload.length > 0) {
//insert Each of the documents?
upload.forEach((u) => {
handleUpload(
{ file: u.originFileObj },
{
bodyshop: bodyshop,
uploaded_by: currentUser.email,
jobId: values.jobid,
billId: billId,
tagsArray: null,
callback: null,
}
);
});
if (bodyshop.uselocalmediaserver) {
upload.forEach((u) => {
handleLocalUpload({
ev: { file: u.originFileObj },
context: {
jobid: values.jobid,
invoice_number: remainingValues.invoice_number,
vendorid: remainingValues.vendorid,
},
});
});
} else {
upload.forEach((u) => {
handleUpload(
{ file: u.originFileObj },
{
bodyshop: bodyshop,
uploaded_by: currentUser.email,
jobId: values.jobid,
billId: billId,
tagsArray: null,
callback: null,
}
);
});
}
}
///////////////////////////
setLoading(false);
notification["success"]({
message: t("bills.successes.created"),
});
if (generateLabel) {
GenerateDocument(
{
name: Templates.parts_invoice_label_single.key,
variables: {
id: billId,
},
},
{},
"p"
);
}
if (billEnterModal.actions.refetch) billEnterModal.actions.refetch();
insertAuditTrail({
@@ -262,6 +355,12 @@ function BillEnterModalContainer({
}}
footer={
<Space>
<Checkbox
checked={generateLabel}
onChange={(e) => setGenerateLabel(e.target.checked)}
>
{t("bills.labels.generatepartslabel")}
</Checkbox>
<Button onClick={handleCancel}>{t("general.actions.cancel")}</Button>
<Button loading={loading} onClick={() => form.submit()}>
{t("general.actions.save")}

View File

@@ -47,6 +47,8 @@ export function BillFormComponent({
billEdit,
disableInvNumber,
job,
loadOutstandingReturns,
loadInventory,
}) {
const { t } = useTranslation();
const client = useApolloClient();
@@ -56,8 +58,18 @@ export function BillFormComponent({
{},
bodyshop.imexshopid
);
const handleVendorSelect = (props, opt) => {
setDiscount(opt.discount);
opt &&
!billEdit &&
loadOutstandingReturns({
variables: {
jobId: form.getFieldValue("jobid"),
vendorId: opt.value,
},
});
};
useEffect(() => {
@@ -65,8 +77,8 @@ export function BillFormComponent({
}, [job, form]);
useEffect(() => {
if (form.getFieldValue("vendorid") && vendorAutoCompleteOptions) {
const vendorId = form.getFieldValue("vendorid");
const vendorId = form.getFieldValue("vendorid");
if (vendorId && vendorAutoCompleteOptions) {
const matchingVendors = vendorAutoCompleteOptions.filter(
(v) => v.id === vendorId
);
@@ -74,10 +86,32 @@ export function BillFormComponent({
setDiscount(matchingVendors[0].discount);
}
}
if (form.getFieldValue("jobid")) {
loadLines({ variables: { id: form.getFieldValue("jobid") } });
const jobId = form.getFieldValue("jobid");
if (jobId) {
loadLines({ variables: { id: jobId } });
if (form.getFieldValue("is_credit_memo") && vendorId && !billEdit) {
loadOutstandingReturns({
variables: {
jobId: jobId,
vendorId: vendorId,
},
});
}
}
}, [form, setDiscount, vendorAutoCompleteOptions, loadLines]);
if (vendorId === bodyshop.inhousevendorid && !billEdit) {
loadInventory();
}
}, [
form,
billEdit,
loadOutstandingReturns,
loadInventory,
setDiscount,
vendorAutoCompleteOptions,
loadLines,
bodyshop.inhousevendorid,
]);
return (
<div>
@@ -107,6 +141,14 @@ export function BillFormComponent({
onBlur={() => {
if (form.getFieldValue("jobid") !== null) {
loadLines({ variables: { id: form.getFieldValue("jobid") } });
if (form.getFieldValue("vendorid") !== null) {
loadOutstandingReturns({
variables: {
jobId: form.getFieldValue("jobid"),
vendorId: form.getFieldValue("vendorid"),
},
});
}
}
}}
/>
@@ -228,8 +270,23 @@ export function BillFormComponent({
rules={[
({ getFieldValue }) => ({
validator(rule, value) {
if (
value === true &&
getFieldValue("jobid") &&
getFieldValue("vendorid")
) {
//Removed as this would cause an additional reload when validating the form on submit and clear the values.
// loadOutstandingReturns({
// variables: {
// jobId: form.getFieldValue("jobid"),
// vendorId: form.getFieldValue("vendorid"),
// },
// });
}
if (
!bodyshop.bill_allow_post_to_closed &&
job &&
(job.status === bodyshop.md_ro_statuses.default_invoiced ||
job.status === bodyshop.md_ro_statuses.default_exported ||
job.status === bodyshop.md_ro_statuses.default_void) &&
@@ -257,15 +314,17 @@ export function BillFormComponent({
>
<CurrencyInput min={0} disabled={disabled} />
</Form.Item>
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
<Select style={{ width: "10rem" }} disabled={disabled} allowClear>
{bodyshop.md_parts_locations.map((loc, idx) => (
<Select.Option key={idx} value={loc}>
{loc}
</Select.Option>
))}
</Select>
</Form.Item>
{!billEdit && (
<Form.Item label={t("bills.fields.allpartslocation")} name="location">
<Select style={{ width: "10rem" }} disabled={disabled} allowClear>
{bodyshop.md_parts_locations.map((loc, idx) => (
<Select.Option key={idx} value={loc}>
{loc}
</Select.Option>
))}
</Select>
</Form.Item>
)}
</LayoutFormRow>
<LayoutFormRow>
<Form.Item
@@ -380,6 +439,7 @@ export function BillFormComponent({
form={form}
responsibilityCenters={responsibilityCenters}
disabled={disabled}
billEdit={billEdit}
/>
)}

View File

@@ -6,6 +6,11 @@ import { GET_JOB_LINES_TO_ENTER_BILL } from "../../graphql/jobs-lines.queries";
import { SEARCH_VENDOR_AUTOCOMPLETE } from "../../graphql/vendors.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import BillFormComponent from "./bill-form.component";
import BillCmdReturnsTableComponent from "../bill-cm-returns-table/bill-cm-returns-table.component";
import { QUERY_UNRECEIVED_LINES } from "../../graphql/parts-orders.queries";
import BillInventoryTable from "../bill-inventory-table/bill-inventory-table.component";
import { QUERY_OUTSTANDING_INVENTORY } from "../../graphql/inventory.queries";
import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -18,6 +23,12 @@ export function BillFormContainer({
disabled,
disableInvNumber,
}) {
const { Simple_Inventory } = useTreatments(
["Simple_Inventory"],
{},
bodyshop && bodyshop.imexshopid
);
const { data: VendorAutoCompleteData } = useQuery(
SEARCH_VENDOR_AUTOCOMPLETE,
{ fetchPolicy: "network-only", nextFetchPolicy: "network-only" }
@@ -27,20 +38,44 @@ export function BillFormContainer({
GET_JOB_LINES_TO_ENTER_BILL
);
const [loadOutstandingReturns, { loading: returnLoading, data: returnData }] =
useLazyQuery(QUERY_UNRECEIVED_LINES);
const [loadInventory, { loading: inventoryLoading, data: inventoryData }] =
useLazyQuery(QUERY_OUTSTANDING_INVENTORY);
return (
<BillFormComponent
disabled={disabled}
form={form}
billEdit={billEdit}
vendorAutoCompleteOptions={
VendorAutoCompleteData && VendorAutoCompleteData.vendors
}
loadLines={loadLines}
lineData={lineData ? lineData.joblines : []}
job={lineData ? lineData.jobs_by_pk : null}
responsibilityCenters={bodyshop.md_responsibility_centers || null}
disableInvNumber={disableInvNumber}
/>
<>
<BillFormComponent
disabled={disabled}
form={form}
billEdit={billEdit}
vendorAutoCompleteOptions={
VendorAutoCompleteData && VendorAutoCompleteData.vendors
}
loadLines={loadLines}
lineData={lineData ? lineData.joblines : []}
job={lineData ? lineData.jobs_by_pk : null}
responsibilityCenters={bodyshop.md_responsibility_centers || null}
disableInvNumber={disableInvNumber}
loadOutstandingReturns={loadOutstandingReturns}
loadInventory={loadInventory}
/>
{!billEdit && (
<BillCmdReturnsTableComponent
form={form}
returnLoading={returnLoading}
returnData={returnData}
/>
)}
{Simple_Inventory.treatment === "on" && (
<BillInventoryTable
form={form}
inventoryLoading={inventoryLoading}
inventoryData={billEdit ? [] : inventoryData}
billEdit={billEdit}
/>
)}
</>
);
}
export default connect(mapStateToProps, null)(BillFormContainer);

View File

@@ -1,7 +1,7 @@
import { DeleteFilled, DollarCircleFilled } from "@ant-design/icons";
import { useTreatments } from "@splitsoftware/splitio-react";
import {
Button,
Form,
Button, Form,
Input,
InputNumber,
Select,
@@ -17,6 +17,7 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CiecaSelect from "../../utils/Ciecaselect";
import BillLineSearchSelect from "../bill-line-search-select/bill-line-search-select.component";
import BilllineAddInventory from "../billline-add-inventory/billline-add-inventory.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
const mapStateToProps = createStructuredSelector({
@@ -34,10 +35,16 @@ export function BillEnterModalLinesComponent({
discount,
form,
responsibilityCenters,
billEdit,
billid,
}) {
const { t } = useTranslation();
const { setFieldsValue, getFieldsValue, getFieldValue } = form;
const { Simple_Inventory } = useTreatments(
["Simple_Inventory"],
{},
bodyshop && bodyshop.imexshopid
);
const columns = (remove) => {
return [
{
@@ -142,11 +149,29 @@ export function BillEnterModalLinesComponent({
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
validator(rule, value) {
if (
value &&
getFieldValue("billlines")[field.fieldKey]?.inventories
?.length > value
) {
return Promise.reject(
t("bills.validation.inventoryquantity", {
number:
getFieldValue("billlines")[field.fieldKey]
?.inventories?.length,
})
);
}
return Promise.resolve();
},
}),
],
};
},
formInput: (record, index) => (
<InputNumber precision={0} min={0} disabled={disabled} />
<InputNumber precision={0} min={1} disabled={disabled} />
),
},
{
@@ -297,28 +322,31 @@ export function BillEnterModalLinesComponent({
</Select>
),
},
{
title: t("billlines.fields.location"),
dataIndex: "location",
editable: true,
label: t("billlines.fields.location"),
formItemProps: (field) => {
return {
key: `${field.index}location`,
name: [field.name, "location"],
};
},
formInput: (record, index) => (
<Select disabled={disabled}>
{bodyshop.md_parts_locations.map((loc, idx) => (
<Select.Option key={idx} value={loc}>
{loc}
</Select.Option>
))}
</Select>
),
},
...(billEdit
? []
: [
{
title: t("billlines.fields.location"),
dataIndex: "location",
editable: true,
label: t("billlines.fields.location"),
formItemProps: (field) => {
return {
key: `${field.index}location`,
name: [field.name, "location"],
};
},
formInput: (record, index) => (
<Select disabled={disabled}>
{bodyshop.md_parts_locations.map((loc, idx) => (
<Select.Option key={idx} value={loc}>
{loc}
</Select.Option>
))}
</Select>
),
},
]),
{
title: t("billlines.labels.deductedfromlbr"),
dataIndex: "deductedfromlbr",
@@ -477,9 +505,33 @@ export function BillEnterModalLinesComponent({
dataIndex: "actions",
render: (text, record) => (
<Button disabled={disabled} onClick={() => remove(record.name)}>
<DeleteFilled />
</Button>
<Form.Item shouldUpdate noStyle>
{() => (
<Space wrap>
<Button
disabled={
disabled ||
getFieldValue("billlines")[record.fieldKey]?.inventories
?.length > 0
}
onClick={() => remove(record.name)}
>
<DeleteFilled />
</Button>
{Simple_Inventory.treatment === "on" && (
<BilllineAddInventory
disabled={
!billEdit ||
form.isFieldsTouched() ||
form.getFieldValue("is_credit_memo")
}
billline={getFieldValue("billlines")[record.fieldKey]}
jobid={getFieldValue("jobid")}
/>
)}
</Space>
)}
</Form.Item>
),
},
];
@@ -502,7 +554,20 @@ export function BillEnterModalLinesComponent({
});
return (
<Form.List name="billlines">
<Form.List
name="billlines"
rules={[
{
validator: async (_, billlines) => {
if (!billlines || billlines.length < 1) {
return Promise.reject(
new Error(t("billlines.validation.atleastone"))
);
}
},
},
]}
>
{(fields, { add, remove, move }) => {
return (
<>

View File

@@ -0,0 +1,173 @@
import { Checkbox, Form, Skeleton, Typography } from "antd";
import React, { useEffect } from "react";
import { useTranslation } from "react-i18next";
import ReadOnlyFormItemComponent from "../form-items-formatted/read-only-form-item.component";
import "./bill-inventory-table.styles.scss";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
billEnterModal: selectBillEnterModal,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(BillInventoryTable);
export function BillInventoryTable({
billEnterModal,
bodyshop,
form,
billEdit,
inventoryLoading,
inventoryData,
}) {
const { t } = useTranslation();
useEffect(() => {
if (inventoryData && inventoryData.inventory) {
form.setFieldsValue({
inventory: billEnterModal.context.consumeinventoryid
? inventoryData.inventory.map((i) => {
if (i.id === billEnterModal.context.consumeinventoryid)
i.consumefrominventory = true;
return i;
})
: inventoryData.inventory,
});
}
}, [inventoryData, form, billEnterModal.context.consumeinventoryid]);
return (
<Form.Item
shouldUpdate={(prev, cur) => prev.vendorid !== cur.vendorid}
noStyle
>
{() => {
const is_inhouse =
form.getFieldValue("vendorid") === bodyshop.inhousevendorid;
if (!is_inhouse || billEdit) {
return null;
}
if (inventoryLoading) return <Skeleton />;
return (
<Form.List name="inventory">
{(fields, { add, remove, move }) => {
return (
<>
<Typography.Title level={4}>
{t("inventory.labels.inventory")}
</Typography.Title>
<table className="bill-inventory-table">
<thead>
<tr>
<th>{t("billlines.fields.line_desc")}</th>
<th>{t("vendors.fields.name")}</th>
<th>{t("billlines.fields.quantity")}</th>
<th>{t("billlines.fields.actual_price")}</th>
<th>{t("billlines.fields.actual_cost")}</th>
<th>{t("inventory.fields.comment")}</th>
<th>{t("inventory.actions.consumefrominventory")}</th>
</tr>
</thead>
<tbody>
{fields.map((field, index) => (
<tr key={field.key}>
<td>
<Form.Item
// label={t("joblines.fields.line_desc")}
key={`${index}line_desc`}
name={[field.name, "line_desc"]}
>
<ReadOnlyFormItemComponent />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
//label={t("joblines.fields.mod_lb_hrs")}
key={`${index}part_type`}
name={[
field.name,
"billline",
"bill",
"vendor",
"name",
]}
>
<ReadOnlyFormItemComponent />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
//label={t("joblines.fields.mod_lb_hrs")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
>
<ReadOnlyFormItemComponent />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
//label={t("joblines.fields.mod_lb_hrs")}
key={`${index}act_price`}
name={[field.name, "actual_price"]}
>
<ReadOnlyFormItemComponent type="currency" />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
//label={t("joblines.fields.mod_lb_hrs")}
key={`${index}cost`}
name={[field.name, "actual_cost"]}
>
<ReadOnlyFormItemComponent type="currency" />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
//label={t("joblines.fields.mod_lb_hrs")}
key={`${index}comment`}
name={[field.name, "comment"]}
>
<ReadOnlyFormItemComponent />
</Form.Item>
</td>
<td>
<Form.Item
span={2}
//label={t("joblines.fields.mod_lb_hrs")}
key={`${index}consumefrominventory`}
name={[field.name, "consumefrominventory"]}
valuePropName="checked"
>
<Checkbox />
</Form.Item>
</td>
</tr>
))}
</tbody>
</table>
</>
);
}}
</Form.List>
);
}}
</Form.Item>
);
}

View File

@@ -0,0 +1,19 @@
.bill-inventory-table {
table-layout: fixed;
width: 100%;
th,
td {
padding: 8px;
text-align: left;
border-bottom: 1px solid #ddd;
.ant-form-item {
margin-bottom: 0px !important;
}
}
tr:hover {
background-color: #f5f5f5;
}
}

View File

@@ -27,6 +27,10 @@ const BillLineSearchSelect = (
option.oem_partno
.toLowerCase()
.includes(inputValue.toLowerCase())) ||
(option.alt_partno &&
option.alt_partno
.toLowerCase()
.includes(inputValue.toLowerCase())) ||
(option.act_price &&
option.act_price.toString().startsWith(inputValue.toString()))
);
@@ -48,14 +52,17 @@ const BillLineSearchSelect = (
line_desc={item.line_desc}
part_qty={item.part_qty}
oem_partno={item.oem_partno}
alt_partno={item.alt_partno}
act_price={item.act_price}
style={{
...(item.removed ? { textDecoration: "line-through" } : {}),
}}
>
<span>{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}`}</span>
<span>
{`${item.removed ? `(REMOVED) ` : ""}${item.line_desc}${
item.oem_partno ? ` - ${item.oem_partno}` : ""
}${item.alt_partno ? ` (${item.alt_partno})` : ""}`.trim()}
</span>
<span style={{ float: "right", paddingleft: "1rem" }}>
{item.act_price
? `$${item.act_price && item.act_price.toFixed(2)}`

View File

@@ -9,11 +9,14 @@ import { createStructuredSelector } from "reselect";
import {
selectAuthLevel,
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { HasRbacAccess } from "../rbac-wrapper/rbac-wrapper.component";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
@@ -24,9 +27,15 @@ export default connect(
mapDispatchToProps
)(BillMarkExportedButton);
export function BillMarkExportedButton({ bodyshop, authLevel, bill }) {
export function BillMarkExportedButton({
currentUser,
bodyshop,
authLevel,
bill,
}) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [insertExportLog] = useMutation(INSERT_EXPORT_LOG);
const [updateBill] = useMutation(gql`
mutation UPDATE_BILL($billId: uuid!) {
@@ -46,6 +55,20 @@ export function BillMarkExportedButton({ bodyshop, authLevel, bill }) {
variables: { billId: bill.id },
});
await insertExportLog({
variables: {
logs: [
{
bodyshopid: bodyshop.id,
billid: bill.id,
successful: true,
message: JSON.stringify([t("general.labels.markedexported")]),
useremail: currentUser.email,
},
],
},
});
if (!result.errors) {
notification["success"]({
message: t("bills.successes.markexported"),
@@ -69,11 +92,7 @@ export function BillMarkExportedButton({ bodyshop, authLevel, bill }) {
if (hasAccess)
return (
<Button
loading={loading}
disabled={bill.exported}
onClick={handleUpdate}
>
<Button loading={loading} disabled={bill.exported} onClick={handleUpdate}>
{t("bills.labels.markexported")}
</Button>
);

View File

@@ -0,0 +1,155 @@
import { FileAddFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, notification, Tooltip } from "antd";
import { t } from "i18next";
import moment from "moment";
import React, { useState } from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { INSERT_INVENTORY_AND_CREDIT } from "../../graphql/inventory.queries";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
import queryString from "query-string";
import { useLocation } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(BilllineAddInventory);
export function BilllineAddInventory({
currentUser,
bodyshop,
billline,
disabled,
jobid,
}) {
const [loading, setLoading] = useState(false);
const { billid } = queryString.parse(useLocation().search);
const [insertInventoryLine] = useMutation(INSERT_INVENTORY_AND_CREDIT);
const addToInventory = async () => {
setLoading(true);
//Check to make sure there are no existing items already in the inventory.
const cm = {
vendorid: bodyshop.inhousevendorid,
invoice_number: "ih",
jobid: jobid,
isinhouse: true,
is_credit_memo: true,
date: moment().format("YYYY-MM-DD"),
federal_tax_rate: bodyshop.bill_tax_rates.federal_tax_rate,
state_tax_rate: bodyshop.bill_tax_rates.state_tax_rate,
local_tax_rate: bodyshop.bill_tax_rates.local_tax_rate,
total: 0,
billlines: [
{
actual_price: billline.actual_price,
actual_cost: billline.actual_cost,
quantity: billline.quantity,
line_desc: billline.line_desc,
cost_center: billline.cost_center,
deductedfromlbr: billline.deductedfromlbr,
applicable_taxes: {
local: billline.applicable_taxes.local,
state: billline.applicable_taxes.state,
federal: billline.applicable_taxes.federal,
},
},
],
};
cm.total = CalculateBillTotal(cm).enteredTotal.getAmount() / 100;
const insertResult = await insertInventoryLine({
variables: {
joblineId:
billline.joblineid === "noline" ? billline.id : billline.joblineid, //This will return null as there will be no jobline that has the id of the bill line.
//Unfortunately, we can't send null as the GQL syntax validation fails.
joblineStatus: bodyshop.md_order_statuses.default_returned,
inv: {
shopid: bodyshop.id,
billlineid: billline.id,
actual_price: billline.actual_price,
actual_cost: billline.actual_cost,
quantity: billline.quantity,
line_desc: billline.line_desc,
},
cm: { ...cm, billlines: { data: cm.billlines } }, //Fix structure for apollo insert.
pol: {
returnfrombill: billid,
vendorid: bodyshop.inhousevendorid,
deliver_by: moment().format("YYYY-MM-DD"),
parts_order_lines: {
data: [
{
line_desc: billline.line_desc,
act_price: billline.actual_price,
cost: billline.actual_cost,
quantity: billline.quantity,
job_line_id:
billline.joblineid === "noline" ? null : billline.joblineid,
part_type: billline.jobline && billline.jobline.part_type,
cm_received: true,
},
],
},
order_date: "2022-06-01",
orderedby: currentUser.email,
jobid: jobid,
user_email: currentUser.email,
return: true,
status: "Ordered",
},
},
refetchQueries: ["QUERY_BILL_BY_PK"],
});
if (!insertResult.errors) {
notification.open({
type: "success",
message: t("inventory.successes.inserted"),
});
} else {
notification.open({
type: "error",
message: t("inventory.errors.inserting", {
error: JSON.stringify(insertResult.errors),
}),
});
}
setLoading(false);
};
return (
<Tooltip title={t("inventory.actions.addtoinventory")}>
<Button
loading={loading}
disabled={
disabled || billline?.inventories?.length >= billline.quantity
}
onClick={addToInventory}
>
<FileAddFilled />
{billline?.inventories?.length > 0 && (
<div>({billline?.inventories?.length} in inv)</div>
)}
</Button>
</Tooltip>
);
}

View File

@@ -4,6 +4,7 @@ import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
@@ -11,10 +12,11 @@ import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters";
import { TemplateList } from "../../utils/TemplateConstants";
import BillDeleteButton from "../bill-delete-button/bill-delete-button.component";
import BillDetailEditReturnComponent from "../bill-detail-edit/bill-detail-edit-return.component";
import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
const mapStateToProps = createStructuredSelector({
//jobRO: selectJobReadOnly,
jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
});
@@ -29,6 +31,7 @@ const mapDispatchToProps = (dispatch) => ({
export function BillsListTableComponent({
bodyshop,
jobRO,
job,
billsQuery,
handleOnRowClick,
@@ -43,6 +46,8 @@ export function BillsListTableComponent({
});
// const search = queryString.parse(useLocation().search);
// const selectedBill = search.billid;
const [searchText, setSearchText] = useState("");
const Templates = TemplateList("bill");
const bills = billsQuery.data ? billsQuery.data.bills : [];
const { refetch } = billsQuery;
@@ -54,38 +59,15 @@ export function BillsListTableComponent({
</Button>
)}
<BillDeleteButton bill={record} />
<Button
<BillDetailEditReturnComponent
data={{ bills_by_pk: { ...record, jobid: job.id } }}
disabled={
record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid
record.is_credit_memo ||
record.vendorid === bodyshop.inhousevendorid ||
jobRO
}
onClick={() => {
console.log(record);
setPartsOrderContext({
actions: {},
context: {
jobId: job.id,
vendorId: record.vendorid,
returnFromBill: record.id,
invoiceNumber: record.invoice_number,
linesToOrder: record.billlines.map((i) => {
return {
line_desc: i.line_desc,
// db_price: i.actual_price,
act_price: i.actual_price,
cost: i.actual_cost,
quantity: i.quantity,
joblineid: i.joblineid,
oem_partno: i.jobline && i.jobline.oem_partno,
part_type: i.jobline && i.jobline.part_type,
};
}),
isReturn: true,
},
});
}}
>
{t("bills.actions.return")}
</Button>
/>
{record.isinhouse && (
<PrintWrapperComponent
templateObject={{
@@ -167,6 +149,24 @@ export function BillsListTableComponent({
setState({ ...state, filteredInfo: filters, sortedInfo: sorter });
};
const filteredBills = bills
? searchText === ""
? bills
: bills.filter(
(b) =>
(b.invoice_number || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(b.vendor.name || "")
.toLowerCase()
.includes(searchText.toLowerCase()) ||
(b.total || "")
.toString()
.toLowerCase()
.includes(searchText.toLowerCase())
)
: [];
return (
<Card
title={t("bills.labels.bills")}
@@ -207,8 +207,10 @@ export function BillsListTableComponent({
<Input.Search
placeholder={t("general.labels.search")}
value={searchText}
onChange={(e) => {
e.preventDefault();
setSearchText(e.target.value);
}}
/>
</Space>
@@ -221,7 +223,7 @@ export function BillsListTableComponent({
}}
columns={columns}
rowKey="id"
dataSource={bills}
dataSource={filteredBills}
onChange={handleTableChange}
/>
</Card>

View File

@@ -39,7 +39,13 @@ export function ContractsFindModalContainer({
if (!claim || !shortclaim) return;
const trimmedShortClaim = shortclaim.trim();
// const trimmedClaim = claim.trim();
claimNumbers.push({ claim: trimmedShortClaim, amount });
if (amount.slice(-1) === "-") {
}
claimNumbers.push({
claim: trimmedShortClaim,
amount: amount.slice(-1) === "-" ? parseFloat(amount) * -1 : amount,
});
});
await GenerateDocument(

View File

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

View File

@@ -6,8 +6,8 @@ import { setSelectedConversation } from "../../redux/messaging/messaging.actions
import { selectSelectedConversation } from "../../redux/messaging/messaging.selectors";
import { TimeAgoFormatter } from "../../utils/DateFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import "./chat-conversation-list.styles.scss";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import "./chat-conversation-list.styles.scss";
const mapStateToProps = createStructuredSelector({
selectedConversation: selectSelectedConversation,
@@ -38,17 +38,20 @@ export function ChatConversationListComponent({
: null
}`}
>
{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 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

View File

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

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