Compare commits

...

116 Commits

Author SHA1 Message Date
Patrick Fic
2f9d025fbe Merged in hotfix/2021-08-12 (pull request #178)
hotfix/2021-08-12

Approved-by: Patrick Fic
2021-08-12 20:43:16 +00:00
Patrick Fic
ef79ccc299 Additional Checklist resolutions. 2021-08-12 13:20:56 -07:00
Patrick Fic
8ff2a6e6c4 Resolve delete checklist item. 2021-08-12 13:16:26 -07:00
Patrick Fic
be2cfb908a IO-1312 Resolve delivery checklist issue. 2021-08-12 13:01:20 -07:00
Patrick Fic
c1d7168260 Resolve CI issue. 2021-08-12 09:52:44 -07:00
Patrick Fic
dd59f3d026 IO-1286 Retry Supplement Importing fix. 2021-08-12 09:46:50 -07:00
Patrick Fic
b37970a6df IO-1300 Line Desc Null for Job costing 2021-08-11 13:50:37 -07:00
Patrick Fic
2cd7fcfbd8 Merged in hotfix/2021-08-11 (pull request #170)
hotfix/2021-08-11

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

Approved-by: Patrick Fic
2021-08-11 20:03:10 +00:00
Patrick Fic
f45f351678 IO-1310 resolve production list issue. 2021-08-11 12:57:46 -07:00
Patrick Fic
18618efdc0 Include sentry username 2021-08-11 11:16:22 -07:00
Patrick Fic
fe5f4f2727 IO-1300 Resolve null line desc. 2021-08-11 11:09:16 -07:00
Patrick Fic
e86160e530 IO-1307 Resolve table check error on export tables. 2021-08-11 08:06:51 -07:00
Patrick Fic
9f7e0d611a IO-1300 Resolve line desc to lower case error. 2021-08-11 07:59:32 -07:00
Patrick Fic
0b523efa95 IO-1304 Resolve ins co nm update on import. 2021-08-11 07:58:01 -07:00
Patrick Fic
3e802c1465 Merged in feature/2021-08-06 (pull request #162)
Feature/2021 08 06
2021-08-06 19:31:18 +00:00
Patrick Fic
ac18e78897 Merged in feature/2021-08-06 (pull request #161)
Resolve CI Error
2021-08-05 16:27:15 +00:00
Patrick Fic
e37ee39e88 Resolve CI Error 2021-08-05 09:25:59 -07:00
Patrick Fic
fed16a4aa3 Merged in feature/2021-08-06 (pull request #160)
Feature/2021 08 06
2021-08-05 16:18:03 +00:00
Patrick Fic
8c94dfce9e Time ticket sort update & remove io event logging. 2021-08-05 09:11:53 -07:00
Patrick Fic
69c13dd052 IO-1286 Resolve supplementing issues. 2021-08-05 09:10:09 -07:00
Patrick Fic
a5fe54164e IO-1285 Resolve production not saving sort order. 2021-08-04 09:30:53 -07:00
Patrick Fic
5ecd5a5a5c IO-1122 Add permission to text to intake checklist. 2021-08-04 09:10:23 -07:00
Patrick Fic
ed45347c23 Merged in feature/2021-07-30 (pull request #159)
feature/2021-07-30

Approved-by: Patrick Fic
2021-08-04 00:42:40 +00:00
Patrick Fic
3e0385479f IO-1234 Remove excess resp. center setup. 2021-08-03 17:28:32 -07:00
Patrick Fic
36f833be91 IO-1281 Prevent posting of tickets to closed RO 2021-08-03 16:52:06 -07:00
Patrick Fic
8fb39f9ea4 Merged in feature/2021-07-30 (pull request #158)
feature/2021-07-30

Approved-by: Patrick Fic
2021-08-03 18:46:24 +00:00
Patrick Fic
5b84ebbc25 IO-1284 Job Admin updates 2021-08-03 11:45:36 -07:00
Patrick Fic
b0ec7867b5 IO-1281 System setting for posting time tickets to closed RO 2021-08-03 11:27:03 -07:00
Patrick Fic
4e4c59ce4d IO-1262 Add SMS reminder to schedule. 2021-08-03 11:14:49 -07:00
Patrick Fic
e9bf1c05ad IO-594 Adjust Autohouse FIle Name 2021-08-03 10:56:33 -07:00
Patrick Fic
7ac1fa5abf Merged in feature/2021-07-30 (pull request #157)
Feature/2021 07 30
2021-07-30 22:25:33 +00:00
Patrick Fic
a489ac1d26 Merged in feature/2021-07-30 (pull request #156)
Remove FCM token.

Approved-by: Patrick Fic
2021-07-30 22:21:27 +00:00
Patrick Fic
4d7a7442ce Remove FCM token. 2021-07-30 15:21:04 -07:00
Patrick Fic
a19bce5a37 Merged in feature/2021-07-30 (pull request #155)
feature/2021-07-30

Approved-by: Patrick Fic
2021-07-30 22:11:48 +00:00
Patrick Fic
35782244bf Merge branch 'feature/2021-07-30' of bitbucket.org:snaptsoft/bodyshop into feature/2021-07-30 2021-07-30 15:11:21 -07:00
Patrick Fic
7407429344 Final phone resolution. 2021-07-30 15:11:16 -07:00
Patrick Fic
55c532f6e2 Merged in feature/2021-07-30 (pull request #154)
Resolve more Phone Lib issues.
2021-07-30 17:24:36 +00:00
Patrick Fic
d3c8b5d731 Resolve more Phone Lib issues. 2021-07-30 10:23:57 -07:00
Patrick Fic
cf09f98d7e Merged in feature/2021-07-30 (pull request #153)
Resolve break with Phone Package.
2021-07-30 16:46:38 +00:00
Patrick Fic
d8e8a8e4e9 Resolve break with Phone Package. 2021-07-30 09:45:59 -07:00
Patrick Fic
65210dea2f Merged in feature/2021-07-30 (pull request #152)
Add several reports to report center.

Approved-by: Patrick Fic
2021-07-29 21:06:18 +00:00
Patrick Fic
0c9b850872 Add several reports to report center. 2021-07-29 14:05:46 -07:00
Patrick Fic
99486830b7 Merged in feature/2021-07-30 (pull request #151)
feature/2021-07-30

Approved-by: Patrick Fic
2021-07-29 20:24:45 +00:00
Patrick Fic
74a62a46d3 IO-1280 Sorting on Available jobs. 2021-07-29 13:24:14 -07:00
Patrick Fic
d306041bcf IO-992 Audit trail bugfixes. 2021-07-29 13:18:28 -07:00
Patrick Fic
ae8a924cd6 IO-1273 Resolve dashboard error 2 2021-07-28 16:31:32 -07:00
Patrick Fic
6ab1b9f787 Merged in feature/2021-07-30 (pull request #150)
feature/2021-07-30

Approved-by: Patrick Fic
2021-07-28 22:27:36 +00:00
Patrick Fic
46ddc440fe IO-992 WIP Job Audits 2021-07-28 15:25:01 -07:00
Patrick Fic
6bf8eacfbd IO-1280 Resolve available jobs sort. 2021-07-28 15:24:58 -07:00
Patrick Fic
b2fa4f220d IO-1277 Protect production note on checklist. 2021-07-28 12:13:17 -07:00
Patrick Fic
2f175c304c IO-1276 Remove ability to return IH invoice. 2021-07-28 12:02:43 -07:00
Patrick Fic
79714e5708 IO-992 Job Audit additions. 2021-07-28 11:58:31 -07:00
Patrick Fic
7c5aa9c913 IO-1275 Finish appointment notes. 2021-07-28 11:34:35 -07:00
Patrick Fic
59b8bae182 IO-1275 WIP Appointment notes. 2021-07-28 11:00:02 -07:00
Patrick Fic
35323ba624 IO-1273 Graceful error on job totals. 2021-07-27 11:19:23 -07:00
Patrick Fic
8ca3741a52 IO-1274 Change Password on profilel 2021-07-26 16:54:31 -07:00
Patrick Fic
c3c021774e Merged in feature/2021-07-30 (pull request #149)
feature/2021-07-30

Approved-by: Patrick Fic
2021-07-22 23:30:47 +00:00
Patrick Fic
6b811d635b IO-992 Job Audit Logs 2021-07-22 16:29:41 -07:00
Patrick Fic
97aecd3ddc IO-594 add SSH key support for AH 2021-07-22 08:40:05 -07:00
Patrick Fic
c4fdef445e Merged in feature/2021-07-23 (pull request #148)
Feature/2021 07 23
2021-07-22 00:23:07 +00:00
Patrick Fic
e642087360 Merged in feature/2021-07-23 (pull request #147)
IO-594 Add AH Settings
2021-07-21 20:18:47 +00:00
Patrick Fic
098754125b IO-594 Add AH Settings 2021-07-21 13:11:51 -07:00
Patrick Fic
6ce2d2723b Merged in feature/2021-07-23 (pull request #146)
Feature/2021 07 23
2021-07-21 18:21:44 +00:00
Patrick Fic
ae4a864533 IO-1264 IO-1271 Report Center additions & format. 2021-07-21 11:19:23 -07:00
Patrick Fic
27d9322ced IO-594 Include AH Requested changes 2021-07-21 08:50:29 -07:00
Patrick Fic
f5003080db Removed unnecessary import. 2021-07-20 16:37:19 -07:00
Patrick Fic
79e11dda4c Merged in feature/2021-07-23 (pull request #145)
IO-594 Create schedulable AH export.
2021-07-20 23:26:16 +00:00
Patrick Fic
3b992edc21 IO-594 Create schedulable AH export. 2021-07-20 16:25:08 -07:00
Patrick Fic
f75f88840f Merged in feature/2021-07-23 (pull request #144)
Feature/2021 07 23
2021-07-20 17:56:01 +00:00
Patrick Fic
3e1663bf18 IO-1267 missing query info in detail cards. 2021-07-20 10:36:49 -07:00
Patrick Fic
9a60149d75 IO-1266 attach pdf copy of email. 2021-07-20 10:34:03 -07:00
Patrick Fic
11cfef904b Merged in feature/2021-07-16 (pull request #143)
Feature/2021 07 16
2021-07-16 21:51:15 +00:00
Patrick Fic
a45dcd307b Merged in feature/2021-07-16 (pull request #142)
Feature/2021 07 16
2021-07-16 21:45:25 +00:00
Patrick Fic
54b483333f Revert "IO-1257 Accept special characters"
This reverts commit a8ad65000d.
2021-07-16 14:42:12 -07:00
Patrick Fic
7f4a36038e Merged in feature/2021-07-16 (pull request #141)
IO-1189 IO-1190 IO-1259 Added gen doc keys.
2021-07-16 17:15:46 +00:00
Patrick Fic
990ec1a553 IO-1189 IO-1190 IO-1259 Added gen doc keys. 2021-07-16 10:13:50 -07:00
Patrick Fic
12307cbd56 Merged in feature/2021-07-16 (pull request #140)
IO-1264 Add CSR reports
2021-07-16 16:26:46 +00:00
Patrick Fic
6bd49a461e IO-1264 Add CSR reports 2021-07-16 09:25:03 -07:00
Patrick Fic
c9ed8a9360 Merged in feature/2021-07-16 (pull request #139)
Feature/2021 07 16
2021-07-16 15:32:01 +00:00
Patrick Fic
120d6f9f5f Merged in feature/2021-07-16 (pull request #138)
feature/2021-07-16

Approved-by: Patrick Fic
2021-07-15 22:11:58 +00:00
Patrick Fic
0d30fc0e32 IO-1219 Improve display of job status. 2021-07-15 13:59:23 -07:00
Patrick Fic
6f64cb71f2 Merged in feature/2021-07-16 (pull request #137)
feature/2021-07-16
2021-07-15 18:42:45 +00:00
Patrick Fic
7ce4264309 IO-1260 Remove VIN Unique Key 2021-07-15 11:40:09 -07:00
Patrick Fic
5385e6918b IO-1219 Add status to job search select. 2021-07-14 15:48:27 -07:00
Patrick Fic
a8ad65000d IO-1257 Accept special characters 2021-07-14 14:51:19 -07:00
Patrick Fic
7a6a834998 Merged in feature/2021-07-16 (pull request #136)
feature/2021-07-16

Approved-by: Patrick Fic
2021-07-13 19:32:15 +00:00
Patrick Fic
ae9ca0ac3b IO-1258 Update reconciliation on posting bill 2021-07-13 12:30:12 -07:00
Patrick Fic
9fd23e9181 IO-1253 Disable receiving inhouse bills. 2021-07-13 12:14:28 -07:00
Patrick Fic
a288a1a2a4 IO-1250 Mark job as PST Exempt 2021-07-13 11:49:27 -07:00
Patrick Fic
33e2201524 IO-1256 Payments on job missing field & edit from list. 2021-07-13 11:09:15 -07:00
Patrick Fic
34422dfef7 IO-1255 Resolve parts return and posting for non-defined lines 2021-07-13 11:02:54 -07:00
Patrick Fic
7999895323 Merged in feature/2021-07-09 (pull request #135)
Feature/2021 07 09
2021-07-09 20:52:55 +00:00
Patrick Fic
c6635845f5 Merged in feature/2021-07-09 (pull request #134)
Removed uneeded imports.

Approved-by: Patrick Fic
2021-07-08 18:46:03 +00:00
Patrick Fic
e770232e1d Removed uneeded imports. 2021-07-08 11:45:43 -07:00
Patrick Fic
51dcf3a7c6 Merged in feature/2021-07-09 (pull request #133)
feature/2021-07-09

Approved-by: Patrick Fic
2021-07-08 18:38:34 +00:00
Patrick Fic
afd745917d IO-1230 remove sort on export logs for ro num 2021-07-08 11:34:39 -07:00
Patrick Fic
aa8e12ef58 IO-1248 IO-1247 Resolve nulls in system & payment search update. 2021-07-08 11:33:02 -07:00
Patrick Fic
41bbda7bcf IO-12424 Delete line to mark as removed. 2021-07-08 11:17:51 -07:00
Patrick Fic
f19289362d IO-1249 Adjust reconciliation window sizing. 2021-07-08 11:12:58 -07:00
Patrick Fic
71ef3dadc5 Merged in feature/2021-07-09 (pull request #132)
Feature/2021 07 09
2021-07-08 01:06:58 +00:00
Patrick Fic
2d546d92b5 Merged in feature/2021-07-09 (pull request #131)
IO-1245 Change Address 1

Approved-by: Patrick Fic
2021-07-07 23:30:51 +00:00
Patrick Fic
1360a73028 IO-1245 Change Address 1 2021-07-07 16:30:05 -07:00
Patrick Fic
7de224831f Merged in feature/2021-07-09 (pull request #130)
feature/2021-07-09

Approved-by: Patrick Fic
2021-07-07 22:14:01 +00:00
Patrick Fic
e9cda93898 IO-1245 Resolve QB Consistency Issue 2021-07-07 15:04:56 -07:00
Patrick Fic
2c1f5a9f34 IO-1241 Supplement Merge with discarded changes. 2021-07-06 09:45:02 -07:00
Patrick Fic
d88d7ebebd Merged in feature/2021-07-09 (pull request #129)
IO-1239 Resolve extra lines on glass claim export
2021-07-05 17:37:52 +00:00
Patrick Fic
17dcc2efd8 IO-1239 Resolve extra lines on glass claim export 2021-07-05 10:35:45 -07:00
Patrick Fic
991df9c48f Merged in hotfix/2021-07-02 (pull request #128)
hotfix/2021-07-02

Approved-by: Patrick Fic
2021-07-02 20:34:59 +00:00
Patrick Fic
3391d7d3f4 Merged in hotfix/2021-07-02 (pull request #127)
IO-1231 Resolve dates not saving on job close.

Approved-by: Patrick Fic
2021-07-02 20:13:40 +00:00
Patrick Fic
bccb5e353b IO-1231 Resolve dates not saving on job close. 2021-07-02 13:13:19 -07:00
Patrick Fic
8cbef14ea3 Merged in hotfix/2021-06-30 (pull request #126)
Hotfix/2021 06 30
2021-06-30 20:47:05 +00:00
Patrick Fic
8e05105917 Merged in hotfix/2021-06-30 (pull request #125)
Hotfix/2021 06 30
2021-06-30 20:46:41 +00:00
Patrick Fic
81babca775 Landing page update. 2021-06-30 13:44:46 -07:00
Patrick Fic
fe8dd2a920 Landing page updates. 2021-06-30 13:44:26 -07:00
Patrick Fic
3176cfcc56 Merged in hotfix/2021-06-30 (pull request #124)
Hotfix/2021 06 30
2021-06-30 20:17:34 +00:00
162 changed files with 5332 additions and 6256 deletions

View File

@@ -1 +1 @@
client_max_body_size 15M; client_max_body_size 50M;

File diff suppressed because it is too large Load Diff

View File

@@ -4,39 +4,39 @@
"private": true, "private": true,
"proxy": "http://localhost:5000", "proxy": "http://localhost:5000",
"dependencies": { "dependencies": {
"@apollo/client": "^3.3.17", "@apollo/client": "^3.3.21",
"@craco/craco": "^5.9.0", "@craco/craco": "^6.2.0",
"@fingerprintjs/fingerprintjs": "^3.1.2", "@fingerprintjs/fingerprintjs": "^3.2.0",
"@lourenci/react-kanban": "^2.1.0", "@lourenci/react-kanban": "^2.1.0",
"@sentry/react": "^6.3.6", "@sentry/react": "^6.10.0",
"@sentry/tracing": "^6.3.6", "@sentry/tracing": "^6.10.0",
"@stripe/react-stripe-js": "^1.4.0", "@stripe/react-stripe-js": "^1.4.0",
"@stripe/stripe-js": "^1.14.0", "@stripe/stripe-js": "^1.16.0",
"@tanem/react-nprogress": "^3.0.65", "@tanem/react-nprogress": "^3.0.74",
"antd": "^4.15.5", "antd": "^4.16.8",
"apollo-link-logger": "^2.0.0", "apollo-link-logger": "^2.0.0",
"axios": "^0.21.1", "axios": "^0.21.1",
"craco-less": "^1.17.1", "craco-less": "^1.18.0",
"dinero.js": "^1.8.1", "dinero.js": "^1.9.0",
"dotenv": "^9.0.2", "dotenv": "^10.0.0",
"enquire-js": "^0.2.1", "enquire-js": "^0.2.1",
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"exifr": "^7.0.0", "exifr": "^7.1.2",
"firebase": "^8.6.0", "firebase": "^8.7.1",
"graphql": "^15.5.0", "graphql": "^15.5.1",
"i18next": "^20.2.2", "i18next": "^20.3.4",
"i18next-browser-languagedetector": "^6.1.1", "i18next-browser-languagedetector": "^6.1.2",
"jsoneditor": "^9.4.1", "jsoneditor": "^9.5.2",
"jsreport-browser-client-dist": "^1.3.0", "jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.9.17", "libphonenumber-js": "^1.9.22",
"logrocket": "^1.2.0", "logrocket": "^1.2.0",
"markerjs2": "^2.8.1", "markerjs2": "^2.9.0",
"moment-business-days": "^1.2.0", "moment-business-days": "^1.2.0",
"phone": "^2.4.21", "phone": "^3.1.2",
"preval.macro": "^5.0.0", "preval.macro": "^5.0.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"query-string": "^7.0.0", "query-string": "^7.0.1",
"rc-queue-anim": "^1.8.5", "rc-queue-anim": "^2.0.0",
"rc-scroll-anim": "^2.7.6", "rc-scroll-anim": "^2.7.6",
"react": "^17.0.1", "react": "^17.0.1",
"react-big-calendar": "^0.33.2", "react-big-calendar": "^0.33.2",
@@ -45,26 +45,26 @@
"react-drag-listview": "^0.1.8", "react-drag-listview": "^0.1.8",
"react-grid-gallery": "^0.5.5", "react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.2.5", "react-grid-layout": "^1.2.5",
"react-i18next": "^11.8.15", "react-i18next": "^11.11.3",
"react-icons": "^4.2.0", "react-icons": "^4.2.0",
"react-number-format": "^4.5.5", "react-number-format": "^4.6.4",
"react-redux": "^7.2.4", "react-redux": "^7.2.4",
"react-resizable": "^3.0.1", "react-resizable": "^3.0.4",
"react-router-dom": "^5.2.0", "react-router-dom": "^5.2.0",
"react-scripts": "^4.0.3", "react-scripts": "^4.0.3",
"react-sublime-video": "^0.2.5", "react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3", "react-virtualized": "^9.22.3",
"recharts": "^2.0.7", "recharts": "^2.0.10",
"redux": "^4.1.0", "redux": "^4.1.0",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"redux-saga": "^1.1.3", "redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.2", "redux-state-sync": "^3.1.2",
"reselect": "^4.0.0", "reselect": "^4.0.0",
"sass": "^1.32.13", "sass": "^1.35.2",
"socket.io-client": "^4.1.2", "socket.io-client": "^4.1.3",
"styled-components": "^5.3.0", "styled-components": "^5.3.0",
"subscriptions-transport-ws": "^0.9.18", "subscriptions-transport-ws": "^0.9.18",
"web-vitals": "^1.1.2", "web-vitals": "^2.1.0",
"workbox-background-sync": "^6.1.5", "workbox-background-sync": "^6.1.5",
"workbox-broadcast-update": "^6.1.5", "workbox-broadcast-update": "^6.1.5",
"workbox-cacheable-response": "^6.1.5", "workbox-cacheable-response": "^6.1.5",

View File

@@ -121,6 +121,7 @@ export default function AccountingPayablesTableComponent({ loading, bills }) {
billId={record.id} billId={record.id}
disabled={transInProgress || !!record.exported} disabled={transInProgress || !!record.exported}
loadingCallback={setTransInProgress} loadingCallback={setTransInProgress}
setSelectedBills={setSelectedBills}
/> />
</div> </div>
), ),

View File

@@ -120,6 +120,7 @@ export default function AccountingPayablesTableComponent({
paymentId={record.id} paymentId={record.id}
disabled={transInProgress || !!record.exportedat} disabled={transInProgress || !!record.exportedat}
loadingCallback={setTransInProgress} loadingCallback={setTransInProgress}
setSelectedPayments={setSelectedPayments}
/> />
), ),
}, },

View File

@@ -125,6 +125,7 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) {
<JobExportButton <JobExportButton
jobId={record.id} jobId={record.id}
disabled={!!record.date_exported} disabled={!!record.date_exported}
setSelectedJobs={setSelectedJobs}
/> />
<Link to={`/manage/jobs/${record.id}/close`}> <Link to={`/manage/jobs/${record.id}/close`}>
<Button>{t("jobs.labels.viewallocations")}</Button> <Button>{t("jobs.labels.viewallocations")}</Button>

View File

@@ -26,6 +26,8 @@ import BillReeportButtonComponent from "../bill-reexport-button/bill-reexport-bu
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -33,6 +35,8 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) => setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })), dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
}); });
export default connect( export default connect(
@@ -40,7 +44,10 @@ export default connect(
mapDispatchToProps mapDispatchToProps
)(BillDetailEditcontainer); )(BillDetailEditcontainer);
export function BillDetailEditcontainer({ setPartsOrderContext }) { export function BillDetailEditcontainer({
setPartsOrderContext,
insertAuditTrail,
}) {
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
const history = useHistory(); const history = useHistory();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -134,6 +141,12 @@ export function BillDetailEditcontainer({ setPartsOrderContext }) {
}); });
await Promise.all(updates); await Promise.all(updates);
insertAuditTrail({
jobid: bill.jobid,
billid: search.billid,
operation: AuditTrailMapping.billupdated(bill.invoice_number),
});
await refetch(); await refetch();
form.setFieldsValue(transformData(data)); form.setFieldsValue(transformData(data));
form.resetFields(); form.resetFields();

View File

@@ -11,12 +11,14 @@ import {
QUERY_JOB_LBR_ADJUSTMENTS, QUERY_JOB_LBR_ADJUSTMENTS,
UPDATE_JOB, UPDATE_JOB,
} from "../../graphql/jobs.queries"; } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectBillEnterModal } from "../../redux/modals/modals.selectors"; import { selectBillEnterModal } from "../../redux/modals/modals.selectors";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import BillFormContainer from "../bill-form/bill-form.container"; import BillFormContainer from "../bill-form/bill-form.container";
import { handleUpload } from "../documents-upload/documents-upload.utility"; import { handleUpload } from "../documents-upload/documents-upload.utility";
@@ -27,6 +29,8 @@ const mapStateToProps = createStructuredSelector({
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")), toggleModalVisible: () => dispatch(toggleModalVisible("billEnter")),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
}); });
function BillEnterModalContainer({ function BillEnterModalContainer({
@@ -34,6 +38,7 @@ function BillEnterModalContainer({
toggleModalVisible, toggleModalVisible,
bodyshop, bodyshop,
currentUser, currentUser,
insertAuditTrail,
}) { }) {
const [form] = Form.useForm(); const [form] = Form.useForm();
const { t } = useTranslation(); const { t } = useTranslation();
@@ -81,8 +86,9 @@ function BillEnterModalContainer({
}, },
], ],
}, },
refetchQueries: ["QUERY_PARTS_BILLS_BY_JOBID"],
}); });
console.log("adjustmentsToInsert", adjustmentsToInsert);
const adjKeys = Object.keys(adjustmentsToInsert); const adjKeys = Object.keys(adjustmentsToInsert);
if (adjKeys.length > 0) { if (adjKeys.length > 0) {
//Query the adjustments, merge, and update them. //Query the adjustments, merge, and update them.
@@ -115,7 +121,12 @@ function BillEnterModalContainer({
message: JSON.stringify(jobUpdate.errors), message: JSON.stringify(jobUpdate.errors),
}), }),
}); });
return;
} }
insertAuditTrail({
jobid: values.jobid,
operation: AuditTrailMapping.jobmodifylbradj(),
});
} }
if (!!r1.errors) { if (!!r1.errors) {
@@ -171,6 +182,12 @@ function BillEnterModalContainer({
}); });
if (billEnterModal.actions.refetch) billEnterModal.actions.refetch(); if (billEnterModal.actions.refetch) billEnterModal.actions.refetch();
insertAuditTrail({
jobid: values.jobid,
billid: billId,
operation: AuditTrailMapping.billposted(remainingValues.invoice_number),
});
if (enterAgain) { if (enterAgain) {
form.resetFields(); form.resetFields();
form.setFieldsValue({ billlines: [] }); form.setFieldsValue({ billlines: [] });

View File

@@ -5,6 +5,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort } from "../../utils/sorters"; import { alphaSort, dateSort } from "../../utils/sorters";
@@ -14,6 +15,7 @@ import PrintWrapperComponent from "../print-wrapper/print-wrapper.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//jobRO: selectJobReadOnly, //jobRO: selectJobReadOnly,
bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
@@ -26,6 +28,7 @@ const mapDispatchToProps = (dispatch) => ({
}); });
export function BillsListTableComponent({ export function BillsListTableComponent({
bodyshop,
job, job,
billsQuery, billsQuery,
handleOnRowClick, handleOnRowClick,
@@ -52,7 +55,9 @@ export function BillsListTableComponent({
)} )}
<BillDeleteButton bill={record} /> <BillDeleteButton bill={record} />
<Button <Button
disabled={record.is_credit_memo} disabled={
record.is_credit_memo || record.vendorid === bodyshop.inhousevendorid
}
onClick={() => onClick={() =>
setPartsOrderContext({ setPartsOrderContext({
actions: {}, actions: {},

View File

@@ -34,7 +34,9 @@ export default function DashboardMonthlyRevenueGraph({ data, ...cardProps }) {
let dailySales; let dailySales;
if (!!jobsByDate[val]) { if (!!jobsByDate[val]) {
dailySales = jobsByDate[val].reduce((dayAcc, dayVal) => { dailySales = jobsByDate[val].reduce((dayAcc, dayVal) => {
return dayAcc.add(Dinero(dayVal.job_totals.totals.subtotal)); return dayAcc.add(
Dinero((dayVal.job_totals && dayVal.job_totals.totals.subtotal) || 0)
);
}, Dinero()); }, Dinero());
} else { } else {
dailySales = Dinero(); dailySales = Dinero();

View File

@@ -13,7 +13,14 @@ export default function DashboardProjectedMonthlySales({ data, ...cardProps }) {
const dollars = const dollars =
data.projected_monthly_sales && data.projected_monthly_sales &&
data.projected_monthly_sales.reduce( data.projected_monthly_sales.reduce(
(acc, val) => acc.add(Dinero(val.job_totals.totals.subtotal)), (acc, val) =>
acc.add(
Dinero(
val.job_totals &&
val.job_totals.totals &&
val.job_totals.totals.subtotal
)
),
Dinero() Dinero()
); );
return ( return (

View File

@@ -14,7 +14,8 @@ export default function DashboardTotalProductionDollars({
const dollars = const dollars =
data.production_jobs && data.production_jobs &&
data.production_jobs.reduce( data.production_jobs.reduce(
(acc, val) => acc.add(Dinero(val.job_totals.totals.subtotal)), (acc, val) =>
acc.add(Dinero(val.job_totals && val.job_totals.totals.subtotal)),
Dinero() Dinero()
); );

View File

@@ -37,6 +37,8 @@ export default function EmailOverlayComponent({ form, selectedMediaState }) {
</Form.Item> </Form.Item>
<Divider>{t("emails.labels.preview")}</Divider> <Divider>{t("emails.labels.preview")}</Divider>
<strong>{t("emails.labels.pdfcopywillbeattached")}</strong>
<Form.Item shouldUpdate> <Form.Item shouldUpdate>
{() => { {() => {
return ( return (

View File

@@ -43,6 +43,10 @@ export function EmailOverlayContainer({
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [sending, setSending] = useState(false); const [sending, setSending] = useState(false);
const [rawHtml, setRawHtml] = useState(""); const [rawHtml, setRawHtml] = useState("");
const [pdfCopytoAttach, setPdfCopytoAttach] = useState({
filename: null,
pdf: null,
});
const [selectedMedia, setSelectedMedia] = useState([]); const [selectedMedia, setSelectedMedia] = useState([]);
const defaultEmailFrom = { const defaultEmailFrom = {
@@ -59,17 +63,17 @@ export function EmailOverlayContainer({
const handleFinish = async (values) => { const handleFinish = async (values) => {
logImEXEvent("email_send_from_modal"); logImEXEvent("email_send_from_modal");
const attachments = []; //const attachments = [];
if (values.fileList) // if (values.fileList)
await asyncForEach(values.fileList, async (f) => { // await asyncForEach(values.fileList, async (f) => {
const t = { // const t = {
ContentType: f.type, // ContentType: f.type,
Filename: f.name, // Filename: f.name,
Base64Content: (await toBase64(f.originFileObj)).split(",")[1], // Base64Content: (await toBase64(f.originFileObj)).split(",")[1],
}; // };
attachments.push(t); // attachments.push(t);
}); // });
setSending(true); setSending(true);
try { try {
@@ -77,11 +81,28 @@ export function EmailOverlayContainer({
...defaultEmailFrom, ...defaultEmailFrom,
...values, ...values,
html: rawHtml, html: rawHtml,
attachments: attachments: [
values.fileList && ...(values.fileList
(await Promise.all( ? await Promise.all(
values.fileList.map(async (f) => await toBase64(f.originFileObj)) values.fileList.map(async (f) => {
)), return {
filename: f.name,
path: await toBase64(f.originFileObj),
};
})
)
: []),
...(pdfCopytoAttach.pdf
? [
{
path: pdfCopytoAttach.pdf,
filename:
pdfCopytoAttach.filename &&
`${pdfCopytoAttach.filename}.pdf`,
},
]
: []),
],
media: selectedMedia.filter((m) => m.isSelected).map((m) => m.src), media: selectedMedia.filter((m) => m.isSelected).map((m) => m.src),
//attachments, //attachments,
}); });
@@ -99,13 +120,22 @@ export function EmailOverlayContainer({
const render = async () => { const render = async () => {
logImEXEvent("email_render_template", { template: emailConfig.template }); logImEXEvent("email_render_template", { template: emailConfig.template });
setLoading(true); setLoading(true);
let html = await RenderTemplate(emailConfig.template, bodyshop, true); let { html, pdf, filename } = await RenderTemplate(
emailConfig.template,
bodyshop,
true
);
const response = await axios.post("/render/inlinecss", { const response = await axios.post("/render/inlinecss", {
html: html, html: html,
url: `${window.location.protocol}://${window.location.host}/`, url: `${window.location.protocol}://${window.location.host}/`,
}); });
setRawHtml(response.data); setRawHtml(response.data);
if (pdf) {
setPdfCopytoAttach({ pdf, filename });
}
form.setFieldsValue({ form.setFieldsValue({
...emailConfig.messageOptions, ...emailConfig.messageOptions,
cc: cc:
@@ -166,8 +196,8 @@ const toBase64 = (file) =>
reader.onerror = (error) => reject(error); reader.onerror = (error) => reject(error);
}); });
const asyncForEach = async (array, callback) => { // const asyncForEach = async (array, callback) => {
for (let index = 0; index < array.length; index++) { // for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array); // await callback(array[index], index, array);
} // }
}; // };

View File

@@ -45,7 +45,7 @@ export default function GlobalSearch() {
<span>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${ <span>{`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${
job.v_model_desc || "" job.v_model_desc || ""
}`}</span> }`}</span>
<span>{`${job.clm_no}`}</span> <span>{`${job.clm_no || ""}`}</span>
</Space> </Space>
</Link> </Link>
), ),
@@ -91,8 +91,8 @@ export default function GlobalSearch() {
vehicle.v_make_desc || "" vehicle.v_make_desc || ""
} ${vehicle.v_model_desc || ""}`} } ${vehicle.v_model_desc || ""}`}
</span> </span>
<span>{vehicle.plate_no}</span> <span>{vehicle.plate_no || ""}</span>
<span> {vehicle.v_vin}</span> <span> {vehicle.v_vin || ""}</span>
</Space> </Space>
</Link> </Link>
), ),
@@ -108,10 +108,11 @@ export default function GlobalSearch() {
label: ( label: (
<Link to={`/manage/jobs/${payment.job.id}`}> <Link to={`/manage/jobs/${payment.job.id}`}>
<Space size="small" split={<Divider type="vertical" />}> <Space size="small" split={<Divider type="vertical" />}>
<span>{payment.paymentnum}</span>
<span>{payment.job.ro_number}</span> <span>{payment.job.ro_number}</span>
<span>{payment.job.memo}</span> <span>{payment.memo || ""}</span>
<span>{payment.job.amount}</span> <span>{payment.amount || ""}</span>
<span>{payment.job.transactionid}</span> <span>{payment.transactionid || ""}</span>
</Space> </Space>
</Link> </Link>
), ),

View File

@@ -161,7 +161,7 @@ export function Jobd3RdPartyModal({ bodyshop, jobId }) {
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("printcenter.jobs.3rdpartyfields.ponumber")} label={t("printcenter.jobs.3rdpartyfields.refnumber")}
name="ponumber" name="ponumber"
> >
<Input /> <Input />

View File

@@ -1,24 +1,50 @@
import { Button, Popover, Space } from "antd"; import { AlertFilled } from "@ant-design/icons";
import {
Button,
Divider,
Dropdown,
Menu,
notification,
Popover,
Space,
} from "antd";
import parsePhoneNumber from "libphonenumber-js";
import moment from "moment";
import queryString from "query-string";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useHistory, useLocation } from "react-router-dom"; import { Link, useHistory, useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {
openChatByPhone,
setMessage,
} from "../../redux/messaging/messaging.actions";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import ScheduleAtChange from "./job-at-change.component"; import ScheduleAtChange from "./job-at-change.component";
import ScheduleEventColor from "./schedule-event.color.component"; import ScheduleEventColor from "./schedule-event.color.component";
import queryString from "query-string"; import ScheduleEventNote from "./schedule-event.note.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setScheduleContext: (context) => setScheduleContext: (context) =>
dispatch(setModalContext({ context: context, modal: "schedule" })), dispatch(setModalContext({ context: context, modal: "schedule" })),
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text)),
}); });
export function ScheduleEventComponent({ export function ScheduleEventComponent({
bodyshop,
setMessage,
openChatByPhone,
event, event,
refetch, refetch,
handleCancel, handleCancel,
@@ -38,7 +64,7 @@ export function ScheduleEventComponent({
); );
const popoverContent = ( const popoverContent = (
<div> <div style={{ maxWidth: "40vw" }}>
{!event.isintake ? ( {!event.isintake ? (
<strong>{event.title}</strong> <strong>{event.title}</strong>
) : ( ) : (
@@ -75,17 +101,19 @@ export function ScheduleEventComponent({
{(event.job && event.job.ownr_ea) || ""} {(event.job && event.job.ownr_ea) || ""}
</DataLabel> </DataLabel>
<DataLabel label={t("jobs.fields.ownr_ph1")}> <DataLabel label={t("jobs.fields.ownr_ph1")}>
<PhoneFormatter> <ChatOpenButton
{(event.job && event.job.ownr_ph1) || ""} phone={event.job && event.job.ownr_ph1}
</PhoneFormatter> jobid={event.job.id}
/>
</DataLabel> </DataLabel>
<DataLabel label={t("jobs.fields.alt_transport")}> <DataLabel label={t("jobs.fields.alt_transport")}>
{(event.job && event.job.alt_transport) || ""} {(event.job && event.job.alt_transport) || ""}
<ScheduleAtChange job={event && event.job} /> <ScheduleAtChange job={event && event.job} />
</DataLabel> </DataLabel>
<ScheduleEventNote event={event} />
</div> </div>
) : null} ) : null}
<Divider />
<Space wrap> <Space wrap>
{event.job ? ( {event.job ? (
<Link to={`/manage/jobs/${event.job && event.job.id}`}> <Link to={`/manage/jobs/${event.job && event.job.id}`}>
@@ -106,23 +134,62 @@ export function ScheduleEventComponent({
{t("appointments.actions.preview")} {t("appointments.actions.preview")}
</Button> </Button>
) : null} ) : null}
<Button
onClick={() => { <Dropdown
const Template = TemplateList("job").appointment_reminder; overlay={
GenerateDocument( <Menu>
{ <Menu.Item
name: Template.key, onClick={() => {
variables: { id: event.job.id }, const Template = TemplateList("job").appointment_reminder;
}, GenerateDocument(
{ to: event.job && event.job.ownr_ea, subject: Template.subject }, {
"e", name: Template.key,
event.job && event.job.id variables: { id: event.job.id },
); },
}} {
disabled={event.arrived} to: event.job && event.job.ownr_ea,
subject: Template.subject,
},
"e",
event.job && event.job.id
);
}}
disabled={event.arrived}
>
{t("general.labels.email")}
</Menu.Item>
<Menu.Item
onClick={() => {
const p = parsePhoneNumber(event.job.ownr_ph1, "CA");
if (p && p.isValid()) {
openChatByPhone({
phone_num: p.formatInternational(),
jobid: event.job.id,
});
setMessage(
t("appointments.labels.reminder", {
shopname: bodyshop.shopname,
date: moment(event.start).format("MM/DD/YYYY"),
time: moment(event.start).format("HH:MM a"),
})
);
setVisible(false);
} else {
notification["error"]({
message: t("messaging.error.invalidphone"),
});
}
}}
disabled={event.arrived || !bodyshop.messagingservicesid}
>
{t("general.labels.sms")}
</Menu.Item>
</Menu>
}
> >
{t("appointments.actions.sendreminder")} <Button>{t("appointments.actions.sendreminder")}</Button>
</Button> </Dropdown>
<Button onClick={() => handleCancel(event.id)} disabled={event.arrived}> <Button onClick={() => handleCancel(event.id)} disabled={event.arrived}>
{t("appointments.actions.cancel")} {t("appointments.actions.cancel")}
</Button> </Button>
@@ -161,6 +228,7 @@ export function ScheduleEventComponent({
const RegularEvent = event.isintake ? ( const RegularEvent = event.isintake ? (
<div style={{ display: "flex", flexWrap: "wrap" }}> <div style={{ display: "flex", flexWrap: "wrap" }}>
<Space> <Space>
{event.note && <AlertFilled className="production-alert" />}
<strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong> <strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong>
<span>{`${(event.job && event.job.ownr_fn) || ""} ${ <span>{`${(event.job && event.job.ownr_fn) || ""} ${
(event.job && event.job.ownr_ln) || "" (event.job && event.job.ownr_ln) || ""
@@ -202,4 +270,7 @@ export function ScheduleEventComponent({
</Popover> </Popover>
); );
} }
export default connect(null, mapDispatchToProps)(ScheduleEventComponent); export default connect(
mapStateToProps,
mapDispatchToProps
)(ScheduleEventComponent);

View File

@@ -0,0 +1,74 @@
import { EditFilled, SaveFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, Input, notification, Space } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
import DataLabel from "../data-label/data-label.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function ScheduleEventNote({ event }) {
const [editing, setEditing] = useState(false);
const [note, setNote] = useState(event.note || "");
const [loading, setLoading] = useState(false);
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
const { t } = useTranslation();
const toggleEdit = async () => {
if (editing) {
//Await the update
setLoading(true);
const result = await updateAppointment({
variables: {
appid: event.id,
app: { note },
},
});
if (!!!result.errors) {
// notification["success"]({ message: t("appointments.successes.saved") });
} else {
notification["error"]({
message: t("jobs.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
setEditing(false);
} else {
setEditing(true);
}
setLoading(false);
};
return (
<DataLabel label={t("appointments.fields.note")}>
<Space flex>
{!editing ? (
event.note || ""
) : (
<Input.TextArea
rows={3}
value={note}
onChange={(e) => setNote(e.target.value)}
style={{ maxWidth: "8vw" }}
/>
)}
<Button onClick={toggleEdit} loading={loading}>
{editing ? <SaveFilled /> : <EditFilled />}
</Button>
</Space>
</DataLabel>
);
}
export default connect(mapStateToProps, mapDispatchToProps)(ScheduleEventNote);

View File

@@ -0,0 +1,46 @@
import { useQuery } from "@apollo/client";
import { Card, Table } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { QUERY_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import { DateTimeFormatter } from "../../utils/DateFormatter";
export default function JobAuditTrail({ jobId }) {
const { t } = useTranslation();
const { loading, data } = useQuery(QUERY_AUDIT_TRAIL, {
variables: { jobid: jobId },
skip: !jobId,
});
const columns = [
{
title: t("audit.fields.created"),
dataIndex: "created",
key: "created",
render: (text, record) => (
<DateTimeFormatter>{record.created}</DateTimeFormatter>
),
},
{
title: t("audit.fields.useremail"),
dataIndex: "useremail",
key: "useremail",
},
{
title: t("audit.fields.operation"),
dataIndex: "operation",
key: "operation",
},
];
return (
<Card title={t("jobs.labels.audit")}>
<Table
loading={loading}
columns={columns}
rowKey="id"
dataSource={data ? data.audit_trail : []}
/>
</Card>
);
}

View File

@@ -16,16 +16,21 @@ import {
import ConfigFormComponents from "../../../config-form-components/config-form-components.component"; import ConfigFormComponents from "../../../config-form-components/config-form-components.component";
import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component"; import DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
import moment from "moment-business-days"; import moment from "moment-business-days";
import { insertAuditTrail } from "../../../../redux/application/application.actions";
import AuditTrailMapping from "../../../../utils/AuditTrailMappings";
import { UPDATE_OWNER } from "../../../../graphql/owners.queries";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
}); });
export function JobChecklistForm({ export function JobChecklistForm({
insertAuditTrail,
formItems, formItems,
bodyshop, bodyshop,
currentUser, currentUser,
@@ -37,6 +42,8 @@ export function JobChecklistForm({
const [intakeJob] = useMutation(UPDATE_JOB); const [intakeJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [markAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_AS_ARRIVED); const [markAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_AS_ARRIVED);
const [updateOwner] = useMutation(UPDATE_OWNER);
const { jobId } = useParams(); const { jobId } = useParams();
const history = useHistory(); const history = useHistory();
const search = queryString.parse(useLocation().search); const search = queryString.parse(useLocation().search);
@@ -59,6 +66,12 @@ export function JobChecklistForm({
production_vars: { production_vars: {
...job.production_vars, ...job.production_vars,
...values.production_vars, ...values.production_vars,
note:
values.production_vars &&
values.production_vars.note &&
values.production_vars.note !== ""
? job.production_vars && values.production_vars.note
: job.production_vars && job.production_vars.note,
}, },
}), }),
...(type === "intake" && { ...(type === "intake" && {
@@ -104,11 +117,40 @@ export function JobChecklistForm({
}); });
} }
} }
if (type === "intake" && job.owner && job.owner.id) {
//Updae Owner Allow to Text
const updateOwnerResult = await updateOwner({
variables: {
ownerId: job.owner.id,
owner: { allow_text_message: values.allow_text_message },
},
});
if (!!updateOwnerResult.errors) {
notification["error"]({
message: t("checklist.errors.complete", {
error: JSON.stringify(result.errors),
}),
});
}
}
setLoading(false); setLoading(false);
if (!!!result.errors) { if (!!!result.errors) {
notification["success"]({ message: t("checklist.successes.completed") }); notification["success"]({ message: t("checklist.successes.completed") });
history.push(`/manage/jobs/${jobId}`); history.push(`/manage/jobs/${jobId}`);
insertAuditTrail({
jobid: jobId,
operation: AuditTrailMapping.jobchecklist(
type,
(type === "deliver" && values.removeFromProduction && false) ||
(type === "intake" && values.addToProduction),
(type === "intake" && bodyshop.md_ro_statuses.default_arrived) ||
(type === "deliver" && bodyshop.md_ro_statuses.default_delivered)
),
});
} else { } else {
notification["error"]({ notification["error"]({
message: t("checklist.errors.complete", { message: t("checklist.errors.complete", {
@@ -135,6 +177,7 @@ export function JobChecklistForm({
initialValues={{ initialValues={{
...(type === "intake" && { ...(type === "intake" && {
addToProduction: true, addToProduction: true,
allow_text_message: job.owner && job.owner.allow_text_message,
scheduled_completion: scheduled_completion:
(job && job.scheduled_completion) || (job && job.scheduled_completion) ||
moment().businessAdd( moment().businessAdd(
@@ -170,6 +213,14 @@ export function JobChecklistForm({
> >
<Switch disabled={readOnly} /> <Switch disabled={readOnly} />
</Form.Item> </Form.Item>
<Form.Item
name="allow_text_message"
valuePropName="checked"
label={t("checklist.labels.allow_text_message")}
disabled={readOnly}
>
<Switch disabled={readOnly} />
</Form.Item>
<Form.Item <Form.Item
name="scheduled_completion" name="scheduled_completion"
label={t("jobs.fields.scheduled_completion")} label={t("jobs.fields.scheduled_completion")}

View File

@@ -295,18 +295,18 @@ export function JobLinesComponent({
onClick={async () => { onClick={async () => {
await deleteJobLine({ await deleteJobLine({
variables: { joblineId: record.id }, variables: { joblineId: record.id },
update(cache) { // update(cache) {
cache.modify({ // cache.modify({
id: cache.identify(job), // id: cache.identify(job),
fields: { // fields: {
joblines(existingJobLines, { readField }) { // joblines(existingJobLines, { readField }) {
return existingJobLines.filter( // return existingJobLines.filter(
(jlRef) => record.id !== readField("id", jlRef) // (jlRef) => record.id !== readField("id", jlRef)
); // );
}, // },
}, // },
}); // });
}, // },
}); });
await axios.post("/job/totalsssu", { await axios.post("/job/totalsssu", {
id: job.id, id: job.id,

View File

@@ -37,8 +37,8 @@ export function JobEmployeeAssignments({
}); });
const [visibility, setVisibility] = useState(false); const [visibility, setVisibility] = useState(false);
const onChange = (e) => { const onChange = (value, option) => {
setAssignment({ ...assignment, employeeid: e }); setAssignment({ ...assignment, employeeid: value, name: option.name });
}; };
const popContent = ( const popContent = (
@@ -56,7 +56,11 @@ export function JobEmployeeAssignments({
} }
> >
{bodyshop.employees.map((emp) => ( {bodyshop.employees.map((emp) => (
<Select.Option value={emp.id} key={emp.id}> <Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`} {`${emp.first_name} ${emp.last_name}`}
</Select.Option> </Select.Option>
))} ))}

View File

@@ -6,14 +6,34 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB_ASSIGNMENTS } from "../../graphql/jobs.queries"; import { UPDATE_JOB_ASSIGNMENTS } from "../../graphql/jobs.queries";
import JobEmployeeAssignmentsComponent from "./job-employee-assignments.component"; import JobEmployeeAssignmentsComponent from "./job-employee-assignments.component";
export default function JobEmployeeAssignmentsContainer({ job, refetch }) { import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
});
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(JobEmployeeAssignmentsContainer);
export function JobEmployeeAssignmentsContainer({
job,
refetch,
insertAuditTrail,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updateJob] = useMutation(UPDATE_JOB_ASSIGNMENTS); const [updateJob] = useMutation(UPDATE_JOB_ASSIGNMENTS);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleAdd = async (assignment) => { const handleAdd = async (assignment) => {
setLoading(true); setLoading(true);
const { operation, employeeid } = assignment; const { operation, employeeid, name } = assignment;
logImEXEvent("job_assign_employee", { operation }); logImEXEvent("job_assign_employee", { operation });
let empAssignment = determineFieldName(operation); let empAssignment = determineFieldName(operation);
@@ -23,6 +43,11 @@ export default function JobEmployeeAssignmentsContainer({ job, refetch }) {
}); });
if (refetch) refetch(); if (refetch) refetch();
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobassignmentchange(operation, name),
});
if (!!result.errors) { if (!!result.errors) {
notification["error"]({ notification["error"]({
message: t("jobs.errors.assigning", { message: t("jobs.errors.assigning", {
@@ -48,6 +73,10 @@ export default function JobEmployeeAssignmentsContainer({ job, refetch }) {
}), }),
}); });
} }
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobassignmentremoved(operation),
});
setLoading(false); setLoading(false);
}; };

View File

@@ -1,4 +1,5 @@
import { Button, Card, Space, Table } from "antd"; import { Button, Card, Space, Table } from "antd";
import { EditFilled } from "@ant-design/icons";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import React, { useMemo, useState } from "react"; import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -115,16 +116,29 @@ export function JobPayments({
dataIndex: "actions", dataIndex: "actions",
key: "actions", key: "actions",
render: (text, record) => ( render: (text, record) => (
<PrintWrapperComponent <Space wrap>
templateObject={{ <Button
name: TemplateList("payment").payment_receipt.key, disabled={record.exportedat}
variables: { id: record.id }, onClick={() => {
}} setPaymentContext({
messageObject={{ actions: { refetch: refetch },
to: job.ownr_ea, context: record,
}} });
id={job.id} }}
/> >
<EditFilled />
</Button>
<PrintWrapperComponent
templateObject={{
name: TemplateList("payment").payment_receipt.key,
variables: { id: record.id },
}}
messageObject={{
to: job.ownr_ea,
}}
id={job.id}
/>
</Space>
), ),
}, },
]; ];

View File

@@ -1,4 +1,4 @@
import { Checkbox, PageHeader, Table } from "antd"; import { Checkbox, Table, Typography } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
@@ -21,6 +21,7 @@ export default function JobReconciliationBillsTable({
title: t("billlines.fields.line_desc"), title: t("billlines.fields.line_desc"),
dataIndex: "line_desc", dataIndex: "line_desc",
key: "line_desc", key: "line_desc",
width: "35%",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc), sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order, state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
@@ -29,6 +30,8 @@ export default function JobReconciliationBillsTable({
title: t("billlines.labels.from"), title: t("billlines.labels.from"),
dataIndex: "from", dataIndex: "from",
key: "from", key: "from",
width: "20%",
ellipsis: true,
render: (text, record) => render: (text, record) =>
`${record.bill.vendor && record.bill.vendor.name} / ${ `${record.bill.vendor && record.bill.vendor.name} / ${
record.bill.invoice_number record.bill.invoice_number
@@ -57,7 +60,7 @@ export default function JobReconciliationBillsTable({
), ),
}, },
{ {
title: t("billlines.fields.quantity"), title: t("joblines.fields.part_qty"),
dataIndex: "quantity", dataIndex: "quantity",
key: "quantity", key: "quantity",
sorter: (a, b) => a.quantity - b.quantity, sorter: (a, b) => a.quantity - b.quantity,
@@ -86,10 +89,12 @@ export default function JobReconciliationBillsTable({
}; };
return ( return (
<PageHeader title={t("bills.labels.bills")}> <div>
<Typography.Title level={4}>{t("bills.labels.bills")}</Typography.Title>
<Table <Table
pagination={false} pagination={false}
scroll={{ y: "40vh", x: true }} size="small"
scroll={{ y: "80vh", x: true }}
columns={columns} columns={columns}
rowKey="id" rowKey="id"
dataSource={invoiceLineData} dataSource={invoiceLineData}
@@ -99,6 +104,6 @@ export default function JobReconciliationBillsTable({
selectedRowKeys: selectedLines, selectedRowKeys: selectedLines,
}} }}
/> />
</PageHeader> </div>
); );
} }

View File

@@ -22,21 +22,23 @@ export default function JobReconciliationModalComponent({ job, bills }) {
); );
return ( return (
<div> <div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
<Row gutter={[16, 16]}> <div style={{ flex: 1 }}>
<Col span={12}> <Row gutter={8}>
<JobReconciliationPartsTable <Col span={12}>
jobLineData={jobLineData} <JobReconciliationPartsTable
jobLineState={jobLineState} jobLineData={jobLineData}
/> jobLineState={jobLineState}
</Col> />
<Col span={12}> </Col>
<JobReconciliationBillsTable <Col span={12}>
invoiceLineData={invoiceLineData} <JobReconciliationBillsTable
billLineState={billLineState} invoiceLineData={invoiceLineData}
/> billLineState={billLineState}
</Col> />
</Row> </Col>
</Row>
</div>
<Row> <Row>
<JobReconciliationTotals <JobReconciliationTotals
jobLines={jobLineData} jobLines={jobLineData}

View File

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

View File

@@ -10,6 +10,7 @@ import { selectReconciliation } from "../../redux/modals/modals.selectors";
import JobReconciliationModalComponent from "./job-reconciliation-modal.component"; import JobReconciliationModalComponent from "./job-reconciliation-modal.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component"; import LoadingSpinner from "../loading-spinner/loading-spinner.component";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import "./job-reconciliation-modal.styles.scss";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
reconciliationModal: selectReconciliation, reconciliationModal: selectReconciliation,
@@ -38,23 +39,23 @@ function JobReconciliationModalContainer({
return ( return (
<Modal <Modal
title={t("jobs.labels.reconciliationheader")} title={t("jobs.labels.reconciliationheader")}
width={"90%"} width={"95%"}
visible={visible} visible={visible}
okText={t("general.actions.close")} okText={t("general.actions.close")}
onOk={handleCancel} onOk={handleCancel}
onCancel={handleCancel} onCancel={handleCancel}
cancelButtonProps={{ display: "none" }} cancelButtonProps={{ display: "none" }}
destroyOnClose destroyOnClose
className="imex-reconciliation-modal"
> >
<LoadingSpinner loading={loading}> {loading && <LoadingSpinner loading={loading} />}
{error && <AlertComponent message={error.message} type="error" />} {error && <AlertComponent message={error.message} type="error" />}
{data && ( {data && (
<JobReconciliationModalComponent <JobReconciliationModalComponent
job={data && data.jobs_by_pk} job={data && data.jobs_by_pk}
bills={data && data.bills} bills={data && data.bills}
/> />
)} )}
</LoadingSpinner>
</Modal> </Modal>
); );
} }

View File

@@ -1,4 +1,4 @@
import { PageHeader, Table } from "antd"; import { Table, Typography } from "antd";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
@@ -102,11 +102,13 @@ export default function JobReconcilitionPartsTable({
}; };
return ( return (
<PageHeader title={t("jobs.labels.lines")}> <div>
<Typography.Title level={4}>{t("jobs.labels.lines")}</Typography.Title>
<Table <Table
pagination={false} pagination={false}
columns={columns} columns={columns}
scroll={{ y: "40vh", x: true }} size="small"
scroll={{ y: "80vh", x: true }}
rowKey="id" rowKey="id"
dataSource={jobLineData} dataSource={jobLineData}
onChange={handleTableChange} onChange={handleTableChange}
@@ -122,6 +124,6 @@ export default function JobReconcilitionPartsTable({
<div style={{ fontStyle: "italic", margin: "4px" }}> <div style={{ fontStyle: "italic", margin: "4px" }}>
{t("jobs.labels.reconciliation.removedpartsstrikethrough")} {t("jobs.labels.reconciliation.removedpartsstrikethrough")}
</div> </div>
</PageHeader> </div>
); );
} }

View File

@@ -1,6 +1,6 @@
import { LoadingOutlined } from "@ant-design/icons"; import { LoadingOutlined } from "@ant-design/icons";
import { useLazyQuery } from "@apollo/client"; import { useLazyQuery } from "@apollo/client";
import { Empty, Select } from "antd"; import { Empty, Select, Space, Tag } from "antd";
import _ from "lodash"; import _ from "lodash";
import React, { forwardRef, useEffect } from "react"; import React, { forwardRef, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -15,6 +15,7 @@ const JobSearchSelect = (
{ {
disabled, disabled,
convertedOnly = false, convertedOnly = false,
notInvoiced = false,
notExported = true, notExported = true,
clm_no = false, clm_no = false,
...restProps ...restProps
@@ -30,6 +31,7 @@ const JobSearchSelect = (
variables: { variables: {
...(convertedOnly ? { isConverted: true } : {}), ...(convertedOnly ? { isConverted: true } : {}),
...(notExported ? { notExported: true } : {}), ...(notExported ? { notExported: true } : {}),
...(notInvoiced ? { notInvoiced: true } : {}),
}, },
} }
: {}), : {}),
@@ -80,13 +82,20 @@ const JobSearchSelect = (
{theOptions {theOptions
? theOptions.map((o) => ( ? theOptions.map((o) => (
<Option key={o.id} value={o.id} status={o.status}> <Option key={o.id} value={o.id} status={o.status}>
{`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${ <Space align="center">
o.ro_number || t("general.labels.na") <span>
} | ${o.ownr_ln || ""} ${o.ownr_fn || ""} ${ {`${clm_no && o.clm_no ? `${o.clm_no} | ` : ""}${
o.ownr_co_nm ? ` ${o.ownr_co_num}` : "" o.ro_number || t("general.labels.na")
}| ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${ } | ${o.ownr_ln || ""} ${o.ownr_fn || ""} ${
o.v_model_desc || "" o.ownr_co_nm ? ` ${o.ownr_co_num}` : ""
}`} }| ${o.v_model_yr || ""} ${o.v_make_desc || ""} ${
o.v_model_desc || ""
}`}
</span>
<Tag>
<strong>{o.status}</strong>
</Tag>
</Space>
</Option> </Option>
)) ))
: null} : null}

View File

@@ -0,0 +1,57 @@
import { DownCircleFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, Dropdown, Menu, notification } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(JobsAdminStatus);
export function JobsAdminStatus({ bodyshop, job }) {
const { t } = useTranslation();
const [mutationUpdateJobstatus] = useMutation(UPDATE_JOB_STATUS);
const updateJobStatus = (status) => {
mutationUpdateJobstatus({
variables: { jobId: job.id, status: status },
})
.then((r) => {
notification["success"]({ message: t("jobs.successes.save") });
// refetch();
})
.catch((error) => {
notification["error"]({ message: t("jobs.errors.saving") });
});
};
const statusmenu = (
<Menu
onClick={(e) => {
updateJobStatus(e.key);
}}
>
{bodyshop.md_ro_statuses.statuses.map((item) => (
<Menu.Item key={item}>{item}</Menu.Item>
))}
</Menu>
);
return (
<Dropdown overlay={statusmenu} trigger={["click"]} key="changestatus">
<Button shape="round">
<span>{job.status}</span>
<DownCircleFilled />
</Button>
</Dropdown>
);
}

View File

@@ -6,8 +6,8 @@ import { useTranslation } from "react-i18next";
export default function JobAdminDeleteIntake({ job }) { export default function JobAdminDeleteIntake({ job }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [updateJob] = useMutation(gql` const [deleteIntake] = useMutation(gql`
mutation UPDATE_JOB($jobId: uuid!) { mutation DELETE_INTAKE($jobId: uuid!) {
update_jobs_by_pk( update_jobs_by_pk(
pk_columns: { id: $jobId } pk_columns: { id: $jobId }
_set: { intakechecklist: null } _set: { intakechecklist: null }
@@ -18,9 +18,39 @@ export default function JobAdminDeleteIntake({ job }) {
} }
`); `);
const [DELETE_DELIVERY] = useMutation(gql`
mutation DELETE_DELIVERY($jobId: uuid!) {
update_jobs_by_pk(
pk_columns: { id: $jobId }
_set: { deliverchecklist: null }
) {
id
deliverchecklist
}
}
`);
const handleDelete = async (values) => { const handleDelete = async (values) => {
setLoading(true); setLoading(true);
const result = await updateJob({ const result = await deleteIntake({
variables: { jobId: job.id },
});
if (!!!result.errors) {
notification["success"]({ message: t("jobs.successes.save") });
} else {
notification["error"]({
message: t("jobs.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
setLoading(false);
};
const handleDeleteDelivery = async (values) => {
setLoading(true);
const result = await DELETE_DELIVERY({
variables: { jobId: job.id }, variables: { jobId: job.id },
}); });
@@ -34,12 +64,16 @@ export default function JobAdminDeleteIntake({ job }) {
}); });
} }
setLoading(false); setLoading(false);
//Get the owner details, populate it all back into the job.
}; };
return ( return (
<Button loading={loading} onClick={handleDelete}> <>
{t("jobs.labels.deleteintake")} <Button loading={loading} onClick={handleDelete}>
</Button> {t("jobs.labels.deleteintake")}
</Button>
<Button loading={loading} onClick={handleDeleteDelivery}>
{t("jobs.labels.deletedelivery")}
</Button>
</>
); );
} }

View File

@@ -7,6 +7,7 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import moment from "moment";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
@@ -21,8 +22,8 @@ export default connect(
export function JobAdminMarkReexport({ bodyshop, job }) { export function JobAdminMarkReexport({ bodyshop, job }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [updateJob] = useMutation(gql` const [markJobForReexport] = useMutation(gql`
mutation UPDATE_JOB($jobId: uuid!) { mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!) {
update_jobs_by_pk( update_jobs_by_pk(
pk_columns: { id: $jobId } pk_columns: { id: $jobId }
_set: { date_exported: null _set: { date_exported: null
@@ -30,14 +31,84 @@ export function JobAdminMarkReexport({ bodyshop, job }) {
} }
) { ) {
id id
intakechecklist date_exported
status
date_invoiced
} }
} }
`); `);
const handleUpdate = async (values) => { const [markJobExported] = useMutation(gql`
mutation MARK_JOB_AS_EXPORTED($jobId: uuid!, $date_exported: timestamptz!) {
update_jobs_by_pk(
pk_columns: { id: $jobId }
_set: { date_exported: $date_exported
status: "${bodyshop.md_ro_statuses.default_exported}"
}
) {
id
date_exported
date_invoiced
status
}
}
`);
const [markJobUninvoiced] = useMutation(gql`
mutation MARK_JOB_AS_UNINVOICED($jobId: uuid!, ) {
update_jobs_by_pk(
pk_columns: { id: $jobId }
_set: { date_exported: null
date_invoiced: null
status: "${bodyshop.md_ro_statuses.default_delivered}"
}
) {
id
date_exported
date_invoiced
status
}
}
`);
const handleMarkForExport = async () => {
setLoading(true); setLoading(true);
const result = await updateJob({ const result = await markJobForReexport({
variables: { jobId: job.id },
});
if (!result.errors) {
notification["success"]({ message: t("jobs.successes.save") });
} else {
notification["error"]({
message: t("jobs.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
setLoading(false);
};
const handleMarkExported = async () => {
setLoading(true);
const result = await markJobExported({
variables: { jobId: job.id, date_exported: moment() },
});
if (!result.errors) {
notification["success"]({ message: t("jobs.successes.save") });
} else {
notification["error"]({
message: t("jobs.errors.saving", {
error: JSON.stringify(result.errors),
}),
});
}
setLoading(false);
};
const handleUninvoice = async () => {
setLoading(true);
const result = await markJobUninvoiced({
variables: { jobId: job.id }, variables: { jobId: job.id },
}); });
@@ -51,16 +122,31 @@ export function JobAdminMarkReexport({ bodyshop, job }) {
}); });
} }
setLoading(false); setLoading(false);
//Get the owner details, populate it all back into the job.
}; };
return ( return (
<Button <>
loading={loading} <Button
disabled={!job.date_exported} loading={loading}
onClick={handleUpdate} disabled={!job.date_exported}
> onClick={handleMarkForExport}
{t("jobs.labels.markforreexport")} >
</Button> {t("jobs.labels.markforreexport")}
</Button>
<Button
loading={loading}
disabled={job.date_exported}
onClick={handleMarkExported}
>
{t("jobs.actions.markasexported")}
</Button>
<Button
loading={loading}
disabled={!job.date_invoiced || job.date_exported}
onClick={handleUninvoice}
>
{t("jobs.actions.uninvoice")}
</Button>
</>
); );
} }

View File

@@ -11,6 +11,7 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
query: GET_ALL_JOBLINES_BY_PK, query: GET_ALL_JOBLINES_BY_PK,
variables: { id: jobId }, variables: { id: jobId },
}); });
const existingLines = _.cloneDeep(existingLinesFromDb); const existingLines = _.cloneDeep(existingLinesFromDb);
const linesToInsert = []; const linesToInsert = [];
const linesToUpdate = []; const linesToUpdate = [];
@@ -19,11 +20,14 @@ export const GetSupplementDelta = async (client, jobId, newLines) => {
const matchingIndex = existingLines.findIndex( const matchingIndex = existingLines.findIndex(
(eL) => eL.unq_seq === newLine.unq_seq (eL) => eL.unq_seq === newLine.unq_seq
); );
//Should do a check to make sure there is only 1 matching unq sequence number.
if (matchingIndex >= 0) { if (matchingIndex >= 0) {
//Found a relevant matching line. Add it to lines to update. //Found a relevant matching line. Add it to lines to update.
linesToUpdate.push({ linesToUpdate.push({
id: existingLines[matchingIndex].id, id: existingLines[matchingIndex].id,
newData: newLine, newData: { ...newLine, removed: false },
}); });
//Splice out item we found for performance. //Splice out item we found for performance.

View File

@@ -57,7 +57,7 @@ export function JobsAvailableComponent({
title: t("jobs.fields.cieca_id"), title: t("jobs.fields.cieca_id"),
dataIndex: "cieca_id", dataIndex: "cieca_id",
key: "cieca_id", key: "cieca_id",
sorter: (a, b) => alphaSort(a, b), sorter: (a, b) => alphaSort(a.cieca_id, b.cieca_id),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order, state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order,
}, },
@@ -68,9 +68,10 @@ export function JobsAvailableComponent({
//width: "8%", //width: "8%",
// onFilter: (value, record) => record.ro_number.includes(value), // onFilter: (value, record) => record.ro_number.includes(value),
// filteredValue: state.filteredInfo.text || null, // filteredValue: state.filteredInfo.text || null,
sorter: (a, b) => alphaSort(a, b), sorter: (a, b) =>
alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "cieca_id" && state.sortedInfo.order, state.sortedInfo.columnKey === "job_id" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>
record.job ? ( record.job ? (
<Link to={`/manage/jobs/${record.job.id}`}> <Link to={`/manage/jobs/${record.job.id}`}>
@@ -87,7 +88,7 @@ export function JobsAvailableComponent({
dataIndex: "ownr_name", dataIndex: "ownr_name",
key: "ownr_name", key: "ownr_name",
ellipsis: true, ellipsis: true,
sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), sorter: (a, b) => alphaSort(a.ownr_name, b.ownr_name),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "ownr_name" && state.sortedInfo.order, state.sortedInfo.columnKey === "ownr_name" && state.sortedInfo.order,
}, },

View File

@@ -8,7 +8,6 @@ import {
import { Col, notification, Row } from "antd"; import { Col, notification, Row } from "antd";
import Axios from "axios"; import Axios from "axios";
import Dinero from "dinero.js"; import Dinero from "dinero.js";
import _ from "lodash";
import moment from "moment"; import moment from "moment";
import queryString from "query-string"; import queryString from "query-string";
import React, { useCallback, useEffect, useState } from "react"; import React, { useCallback, useEffect, useState } from "react";
@@ -25,10 +24,12 @@ import {
import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries"; import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries";
import { INSERT_NEW_NOTE } from "../../graphql/notes.queries"; import { INSERT_NEW_NOTE } from "../../graphql/notes.queries";
import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries"; import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component"; import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container"; import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
@@ -42,8 +43,15 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
}); });
const mapDispatchToProps = (dispatch) => ({
export function JobsAvailableContainer({ bodyshop, currentUser }) { insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function JobsAvailableContainer({
bodyshop,
currentUser,
insertAuditTrail,
}) {
const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, { const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, {
fetchPolicy: "network-only", fetchPolicy: "network-only",
}); });
@@ -66,7 +74,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
const client = useApolloClient(); const client = useApolloClient();
const estDataLazyLoad = useLazyQuery(QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK); const estDataLazyLoad = useLazyQuery(QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK);
const [loadEstData, estData] = estDataLazyLoad; const [loadEstData, estDataRaw] = estDataLazyLoad;
const importOptionsState = useState({ overrideHeaders: false }); const importOptionsState = useState({ overrideHeaders: false });
const importOptions = importOptionsState[0]; const importOptions = importOptionsState[0];
@@ -79,13 +87,9 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
setOwnerModalVisible(false); setOwnerModalVisible(false);
setInsertLoading(true); setInsertLoading(true);
if ( const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
!(
estData.data && if (!(estData && estData.est_data)) {
estData.data.available_jobs_by_pk &&
estData.data.available_jobs_by_pk.est_data
)
) {
//We don't have the right data. Error! //We don't have the right data. Error!
setInsertLoading(false); setInsertLoading(false);
notification["error"]({ notification["error"]({
@@ -97,28 +101,25 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
const newTotals = ( const newTotals = (
await Axios.post("/job/totals", { await Axios.post("/job/totals", {
job: { job: {
...estData.data.available_jobs_by_pk.est_data, ...estData.est_data,
joblines: estData.data.available_jobs_by_pk.est_data.joblines.data, joblines: estData.est_data.joblines.data,
}, },
}) })
).data; ).data;
let existingVehicles; let existingVehicles;
if ( if (estData.est_data.vehicle && estData.est_data.vin) {
estData.data.available_jobs_by_pk.est_data.vehicle &&
estData.data.available_jobs_by_pk.est_data.vin
) {
//There's vehicle data, need to double check the VIN. //There's vehicle data, need to double check the VIN.
existingVehicles = await client.query({ existingVehicles = await client.query({
query: SEARCH_VEHICLE_BY_VIN, query: SEARCH_VEHICLE_BY_VIN,
variables: { variables: {
vin: estData.data.available_jobs_by_pk.est_data.vehicle.data.v_vin, vin: estData.est_data.vehicle.data.v_vin,
}, },
}); });
} }
const newJob = { const newJob = {
...estData.data.available_jobs_by_pk.est_data, ...estData.est_data,
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"), clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"), owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
job_totals: newTotals, job_totals: newTotals,
@@ -157,8 +158,13 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
}); });
//Job has been inserted. Clean up the available jobs record. //Job has been inserted. Clean up the available jobs record.
insertAuditTrail({
jobid: r.data.insert_jobs.returning[0].id,
operation: AuditTrailMapping.jobimported(),
});
deleteJob({ deleteJob({
variables: { id: estData.data.available_jobs_by_pk.id }, variables: { id: estData.id },
}).then((r) => { }).then((r) => {
refetch(); refetch();
setInsertLoading(false); setInsertLoading(false);
@@ -181,13 +187,9 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
setJobModalVisible(false); setJobModalVisible(false);
setInsertLoading(true); setInsertLoading(true);
if ( const estData = estDataRaw.data.available_jobs_by_pk;
!(
estData.data && if (!(estData && estData.est_data)) {
estData.data.available_jobs_by_pk &&
estData.data.available_jobs_by_pk.est_data
)
) {
//We don't have the right data. Error! //We don't have the right data. Error!
setInsertLoading(false); setInsertLoading(false);
notification["error"]({ notification["error"]({
@@ -195,18 +197,19 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
}); });
} else { } else {
//create upsert job //create upsert job
let supp = _.cloneDeep(estData.data.available_jobs_by_pk.est_data); let supp = replaceEmpty({ ...estData.est_data });
delete supp.owner; delete supp.owner;
delete supp.vehicle; delete supp.vehicle;
if (importOptions.overrideHeaders) { delete supp.ins_co_nm;
if (!importOptions.overrideHeaders) {
HeaderFields.forEach((item) => delete supp[item]); HeaderFields.forEach((item) => delete supp[item]);
} }
let suppDelta = await GetSupplementDelta( let suppDelta = await GetSupplementDelta(
client, client,
selectedJob, selectedJob,
estData.data.available_jobs_by_pk.est_data.joblines.data supp.joblines.data
); );
delete supp.joblines; delete supp.joblines;
@@ -265,7 +268,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
//Job has been inserted. Clean up the available jobs record. //Job has been inserted. Clean up the available jobs record.
deleteJob({ deleteJob({
variables: { id: estData.data.available_jobs_by_pk.id }, variables: { id: estData.id },
}).then((r) => { }).then((r) => {
refetch(); refetch();
setInsertLoading(false); setInsertLoading(false);
@@ -283,17 +286,21 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
], ],
}, },
}); });
insertAuditTrail({
jobid: selectedJob,
operation: AuditTrailMapping.jobsupplement(),
});
} }
}; };
const owner = const owner =
estData.data && estDataRaw.data &&
estData.data.available_jobs_by_pk && estDataRaw.data.available_jobs_by_pk &&
estData.data.available_jobs_by_pk.est_data && estDataRaw.data.available_jobs_by_pk.est_data &&
estData.data.available_jobs_by_pk.est_data.owner && estDataRaw.data.available_jobs_by_pk.est_data.owner &&
estData.data.available_jobs_by_pk.est_data.owner.data && estDataRaw.data.available_jobs_by_pk.est_data.owner.data &&
!estData.data.available_jobs_by_pk.issupplement !estDataRaw.data.available_jobs_by_pk.issupplement
? estData.data.available_jobs_by_pk.est_data.owner.data ? estDataRaw.data.available_jobs_by_pk.est_data.owner.data
: null; : null;
const onOwnerModalCancel = () => { const onOwnerModalCancel = () => {
@@ -331,8 +338,8 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
message={t("jobs.labels.creating_new_job")} message={t("jobs.labels.creating_new_job")}
> >
<OwnerFindModalContainer <OwnerFindModalContainer
loading={estData.loading} loading={estDataRaw.loading}
error={estData.error} error={estDataRaw.error}
owner={owner} owner={owner}
selectedOwner={selectedOwner} selectedOwner={selectedOwner}
setSelectedOwner={setSelectedOwner} setSelectedOwner={setSelectedOwner}
@@ -341,8 +348,8 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
onCancel={onOwnerModalCancel} onCancel={onOwnerModalCancel}
/> />
<JobsFindModalContainer <JobsFindModalContainer
loading={estData.loading} loading={estDataRaw.loading}
error={estData.error} error={estDataRaw.error}
selectedJob={selectedJob} selectedJob={selectedJob}
setSelectedJob={setSelectedJob} setSelectedJob={setSelectedJob}
importOptionsState={importOptionsState} importOptionsState={importOptionsState}
@@ -368,4 +375,16 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
</LoadingSpinner> </LoadingSpinner>
); );
} }
export default connect(mapStateToProps, null)(JobsAvailableContainer); export default connect(
mapStateToProps,
mapDispatchToProps
)(JobsAvailableContainer);
function replaceEmpty(someObj, replaceValue = null) {
const replacer = (key, value) =>
value === "" ? replaceValue || null : value;
//^ because you seem to want to replace (strings) "null" or "undefined" too
const temp = JSON.stringify(someObj, replacer);
console.log("Parsed", JSON.parse(temp));
return JSON.parse(temp);
}

View File

@@ -6,18 +6,21 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries"; import { UPDATE_JOB_STATUS } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
}); });
export function JobsChangeStatus({ job, bodyshop, jobRO }) { export function JobsChangeStatus({ job, bodyshop, jobRO, insertAuditTrail }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [availableStatuses, setAvailableStatuses] = useState([]); const [availableStatuses, setAvailableStatuses] = useState([]);
@@ -29,6 +32,10 @@ export function JobsChangeStatus({ job, bodyshop, jobRO }) {
}) })
.then((r) => { .then((r) => {
notification["success"]({ message: t("jobs.successes.save") }); notification["success"]({ message: t("jobs.successes.save") });
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobstatuschange(status),
});
// refetch(); // refetch();
}) })
.catch((error) => { .catch((error) => {

View File

@@ -32,7 +32,7 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
} }
//Verify that this is also manually updated in server/job-costing //Verify that this is also manually updated in server/job-costing
if (!jl.part_type && !jl.mod_lbr_ty) { if (!jl.part_type && !jl.mod_lbr_ty) {
const lineDesc = jl.line_desc.toLowerCase(); const lineDesc = jl.line_desc ? jl.line_desc.toLowerCase() : "";
if (lineDesc.includes("shop materials")) { if (lineDesc.includes("shop materials")) {
ret.profitcenter_part = defaults.profits["MASH"]; ret.profitcenter_part = defaults.profits["MASH"];
} else if (lineDesc.includes("paint/materials")) { } else if (lineDesc.includes("paint/materials")) {

View File

@@ -24,6 +24,7 @@ export function JobsCloseExportButton({
currentUser, currentUser,
jobId, jobId,
disabled, disabled,
setSelectedJobs,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
@@ -147,6 +148,11 @@ export function JobsCloseExportButton({
}), }),
}); });
} }
if (setSelectedJobs) {
setSelectedJobs((selectedJobs) => {
return selectedJobs.filter((i) => i.id !== jobId);
});
}
} }
setLoading(false); setLoading(false);

View File

@@ -13,8 +13,10 @@ import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { CONVERT_JOB_TO_RO } from "../../graphql/jobs.queries"; import { CONVERT_JOB_TO_RO } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -22,10 +24,17 @@ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly, jobRO: selectJobReadOnly,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
}); });
export function JobsConvertButton({ bodyshop, job, refetch, jobRO }) { export function JobsConvertButton({
bodyshop,
job,
refetch,
jobRO,
insertAuditTrail,
}) {
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO); const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
@@ -43,6 +52,14 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO }) {
notification["success"]({ notification["success"]({
message: t("jobs.successes.converted"), message: t("jobs.successes.converted"),
}); });
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobconverted(
res.data.update_jobs.returning[0].ro_number
),
});
setVisible(false); setVisible(false);
} }
setLoading(false); setLoading(false);

View File

@@ -11,7 +11,8 @@ import FormItemPhone, {
PhoneItemFormatterValidation, PhoneItemFormatterValidation,
} from "../form-items-formatted/phone-form-item.component"; } from "../form-items-formatted/phone-form-item.component";
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component"; import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
import { JobsDetailRatesParts } from "../jobs-detail-rates/jobs-detail-rates.parts.component"; import JobsDetailRatesParts from "../jobs-detail-rates/jobs-detail-rates.parts.component";
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component"; import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser //currentUser: selectCurrentUser
@@ -187,7 +188,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
header={t("menus.jobsdetail.financials")} header={t("menus.jobsdetail.financials")}
> >
<JobsDetailRatesChangeButton form={form} /> <JobsDetailRatesChangeButton form={form} />
<JobsMarkPstExempt form={form} />
<LayoutFormRow> <LayoutFormRow>
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt"> <Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
<CurrencyInput /> <CurrencyInput />

View File

@@ -1,7 +1,10 @@
import { notification } from "antd"; import { notification } from "antd";
import i18n from "i18next"; import i18n from "i18next";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { store } from "../../redux/store";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
export default function AddToProduction( export default function AddToProduction(
apolloClient, apolloClient,
@@ -21,6 +24,13 @@ export default function AddToProduction(
notification["success"]({ notification["success"]({
message: i18n.t("jobs.successes.save"), message: i18n.t("jobs.successes.save"),
}); });
store.dispatch(
insertAuditTrail({
jobid: jobId,
operation: AuditTrailMapping.jobinproductionchange(!remove),
})
);
if (completionCallback) completionCallback(); if (completionCallback) completionCallback();
}) })
.catch((error) => { .catch((error) => {

View File

@@ -142,7 +142,10 @@ export function JobsDetailHeaderActions({
</Menu.Item> </Menu.Item>
<Menu.Item <Menu.Item
key="entertimetickets" key="entertimetickets"
disabled={!job.converted} disabled={
!job.converted ||
(!bodyshop.tt_allow_post_to_invoiced && job.date_invoiced)
}
onClick={() => { onClick={() => {
logImEXEvent("job_header_enter_time_ticekts"); logImEXEvent("job_header_enter_time_ticekts");

View File

@@ -1,4 +1,13 @@
import { Form, Input, InputNumber, Select, Space, Switch, Tooltip } from "antd"; import {
Divider,
Form,
Input,
InputNumber,
Select,
Space,
Switch,
Tooltip,
} from "antd";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -7,6 +16,7 @@ import { selectJobReadOnly } from "../../redux/application/application.selectors
import CABCpvrtCalculator from "../ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component"; import CABCpvrtCalculator from "../ca-bc-pvrt-calculator/ca-bc-pvrt-calculator.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component"; import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component"; import JobsDetailRatesChangeButton from "../jobs-detail-rates-change-button/jobs-detail-rates-change-button.component";
import JobsMarkPstExempt from "../jobs-mark-pst-exempt/jobs-mark-pst-exempt.component";
import FormRow from "../layout-form-row/layout-form-row.component"; import FormRow from "../layout-form-row/layout-form-row.component";
import JobsDetailRatesParts from "./jobs-detail-rates.parts.component"; import JobsDetailRatesParts from "./jobs-detail-rates.parts.component";
@@ -103,8 +113,19 @@ export function JobsDetailRates({ jobRO, form, job }) {
<Switch disabled={jobRO} /> <Switch disabled={jobRO} />
</Form.Item> </Form.Item>
</FormRow> </FormRow>
<JobsDetailRatesChangeButton form={form} disabled={jobRO} /> <Divider
<FormRow header={t("jobs.forms.laborrates")}> orientation="left"
type="horizontal"
style={{ marginTop: ".8rem", float: "right" }}
>
{t("jobs.forms.laborrates")}
</Divider>
<Space>
<div></div>
<JobsDetailRatesChangeButton form={form} disabled={jobRO} />
<JobsMarkPstExempt form={form} />
</Space>
<FormRow noDivider>
<Form.Item <Form.Item
label={t("jobs.fields.labor_rate_desc")} label={t("jobs.fields.labor_rate_desc")}
name="labor_rate_desc" name="labor_rate_desc"
@@ -180,7 +201,6 @@ export function JobsDetailRates({ jobRO, form, job }) {
<CurrencyInput disabled={jobRO} /> <CurrencyInput disabled={jobRO} />
</Form.Item> </Form.Item>
</FormRow> </FormRow>
<JobsDetailRatesParts form={form} /> <JobsDetailRatesParts form={form} />
</div> </div>
); );

View File

@@ -19,7 +19,11 @@ export function JobsDetailRatesParts({
return ( return (
<Collapse defaultActiveKey={expanded && "rates"}> <Collapse defaultActiveKey={expanded && "rates"}>
<Collapse.Panel header={t("jobs.labels.parts_tax_rates")} key="rates"> <Collapse.Panel
forceRender
header={t("jobs.labels.parts_tax_rates")}
key="rates"
>
<LayoutFormRow header={t("joblines.fields.part_types.PAA")}> <LayoutFormRow header={t("joblines.fields.part_types.PAA")}>
<Form.Item <Form.Item
label={t("jobs.fields.parts_tax_rates.prt_discp")} label={t("jobs.fields.parts_tax_rates.prt_discp")}

View File

@@ -0,0 +1,55 @@
import { Popconfirm, Button } from "antd";
import React from "react";
import { createStructuredSelector } from "reselect";
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import _ from "lodash";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
jobRO: selectJobReadOnly,
});
export function JobsMarkPstExempt({ jobRO, form }) {
const { t } = useTranslation();
const handleConfirm = () => {
const newPartRates = _.cloneDeep(form.getFieldValue("parts_tax_rates"));
Object.keys(newPartRates).forEach((key) => {
newPartRates[key] = {
...newPartRates[key],
prt_tax_in: false,
prt_tax_rt: 0,
};
});
form.setFieldsValue({
state_tax_rate: 0,
tax_lbr_rt: 0,
tax_levies_rt: 0,
tax_sub_rt: 0,
tax_shop_mat_rt: 0,
tax_paint_mat_rt: 0,
tax_str_rt: 0,
tax_tow_rt: 0,
parts_tax_rates: newPartRates,
});
};
return (
<Popconfirm
onConfirm={handleConfirm}
disabled={jobRO}
okText={t("general.labels.yes")}
cancelText={t("general.labels.no")}
title={t("jobs.actions.markpstexemptconfirm")}
>
<Button type="link" disabled={jobRO}>
{t("jobs.actions.markpstexempt")}
</Button>
</Popconfirm>
);
}
export default connect(mapStateToProps, null)(JobsMarkPstExempt);

View File

@@ -31,11 +31,14 @@ function OwnerDetailJobsComponent({ bodyshop, owner }) {
title: t("jobs.fields.vehicle"), title: t("jobs.fields.vehicle"),
dataIndex: "vehicleid", dataIndex: "vehicleid",
key: "vehicleid", key: "vehicleid",
render: (text, record) => ( render: (text, record) =>
<Link to={`/manage/vehicles/${record.vehicleid}`}> record.vehicleid ? (
{`${record.v_model_yr} ${record.v_make_desc} ${record.v_model_desc}`} <Link to={`/manage/vehicles/${record.vehicleid}`}>
</Link> {`${record.v_model_yr} ${record.v_make_desc} ${record.v_model_desc}`}
), </Link>
) : (
t("jobs.errors.novehicle")
),
}, },
{ {
title: t("jobs.fields.clm_no"), title: t("jobs.fields.clm_no"),

View File

@@ -90,7 +90,11 @@ export function PartsOrderListTableComponent({
</Button> </Button>
)} )}
<Button <Button
disabled={jobRO || record.return} disabled={
jobRO ||
record.return ||
record.vendor.id === bodyshop.inhousevendorid
}
onClick={() => { onClick={() => {
logImEXEvent("parts_order_receive_bill"); logImEXEvent("parts_order_receive_bill");
setPartsReceiveContext({ setPartsReceiveContext({
@@ -139,7 +143,10 @@ export function PartsOrderListTableComponent({
</Button> </Button>
</Popconfirm> </Popconfirm>
<Button <Button
disabled={jobRO ? !record.return : jobRO} disabled={
(jobRO ? !record.return : jobRO) ||
record.vendor.id === bodyshop.inhousevendorid
}
onClick={() => { onClick={() => {
logImEXEvent("parts_order_receive_bill"); logImEXEvent("parts_order_receive_bill");
@@ -157,7 +164,7 @@ export function PartsOrderListTableComponent({
quantity: pol.quantity, quantity: pol.quantity,
actual_price: pol.act_price, actual_price: pol.act_price,
cost_center: pol.jobline.part_type cost_center: pol.jobline?.part_type
? responsibilityCenters.defaults.costs[ ? responsibilityCenters.defaults.costs[
pol.jobline.part_type pol.jobline.part_type
] || null ] || null

View File

@@ -9,6 +9,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries"; import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries";
import { INSERT_NEW_PARTS_ORDERS } from "../../graphql/parts-orders.queries"; import { INSERT_NEW_PARTS_ORDERS } from "../../graphql/parts-orders.queries";
import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries"; import { QUERY_ALL_VENDORS_FOR_ORDER } from "../../graphql/vendors.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { setEmailOptions } from "../../redux/email/email.actions"; import { setEmailOptions } from "../../redux/email/email.actions";
import { import {
setModalContext, setModalContext,
@@ -19,6 +20,7 @@ import {
selectBodyshop, selectBodyshop,
selectCurrentUser, selectCurrentUser,
} from "../../redux/user/user.selectors"; } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import AlertComponent from "../alert/alert.component"; import AlertComponent from "../alert/alert.component";
@@ -36,6 +38,8 @@ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")), toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")),
setBillEnterContext: (context) => setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })), dispatch(setModalContext({ context: context, modal: "billEnter" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
}); });
export function PartsOrderModalContainer({ export function PartsOrderModalContainer({
@@ -45,6 +49,7 @@ export function PartsOrderModalContainer({
bodyshop, bodyshop,
setEmailOptions, setEmailOptions,
setBillEnterContext, setBillEnterContext,
insertAuditTrail,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -101,12 +106,26 @@ export function PartsOrderModalContainer({
const jobLinesResult = await updateJobLines({ const jobLinesResult = await updateJobLines({
variables: { variables: {
ids: values.parts_order_lines.data.map((item) => item.job_line_id), ids: values.parts_order_lines.data
.filter((item) => item.job_line_id)
.map((item) => item.job_line_id),
status: isReturn status: isReturn
? bodyshop.md_order_statuses.default_returned || "Returned*" ? bodyshop.md_order_statuses.default_returned || "Returned*"
: bodyshop.md_order_statuses.default_ordered || "Ordered*", : bodyshop.md_order_statuses.default_ordered || "Ordered*",
}, },
}); });
insertAuditTrail({
jobid: jobId,
operation: isReturn
? AuditTrailMapping.jobspartsreturn(
insertResult.data.insert_parts_orders.returning[0].order_number
)
: AuditTrailMapping.jobspartsorder(
insertResult.data.insert_parts_orders.returning[0].order_number
),
});
if (!!jobLinesResult.errors) { if (!!jobLinesResult.errors) {
notification["error"]({ notification["error"]({
message: t("parts_orders.errors.creating"), message: t("parts_orders.errors.creating"),

View File

@@ -25,6 +25,7 @@ export function PayableExportButton({
billId, billId,
disabled, disabled,
loadingCallback, loadingCallback,
setSelectedBills,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updateBill] = useMutation(UPDATE_BILLS); const [updateBill] = useMutation(UPDATE_BILLS);
@@ -142,6 +143,11 @@ export function PayableExportButton({
}), }),
}); });
} }
if (setSelectedBills) {
setSelectedBills((selectedBills) => {
return selectedBills.filter((i) => i.id !== billId);
});
}
} }
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);

View File

@@ -24,6 +24,7 @@ export function PaymentExportButton({
paymentId, paymentId,
disabled, disabled,
loadingCallback, loadingCallback,
setSelectedPayments,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updatePayment] = useMutation(UPDATE_PAYMENTS); const [updatePayment] = useMutation(UPDATE_PAYMENTS);
@@ -141,6 +142,12 @@ export function PaymentExportButton({
}), }),
}); });
} }
if (setSelectedPayments) {
setSelectedPayments((selectedBills) => {
return selectedBills.filter((i) => i.id !== paymentId);
});
}
} }
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);

View File

@@ -14,12 +14,25 @@ import IndefiniteLoading from "../indefinite-loading/indefinite-loading.componen
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import ProductionBoardFilters from "../production-board-filters/production-board-filters.component"; import ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
import { selectTechnician } from "../../redux/tech/tech.selectors"; import { selectTechnician } from "../../redux/tech/tech.selectors";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician, technician: selectTechnician,
}); });
export function ProductionBoardKanbanComponent({ data, bodyshop, technician }) { const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function ProductionBoardKanbanComponent({
data,
bodyshop,
technician,
insertAuditTrail,
}) {
const [boardLanes, setBoardLanes] = useState({ const [boardLanes, setBoardLanes] = useState({
columns: [{ id: "Loading...", title: "Loading...", cards: [] }], columns: [{ id: "Loading...", title: "Loading...", cards: [] }],
}); });
@@ -104,6 +117,11 @@ export function ProductionBoardKanbanComponent({ data, bodyshop, technician }) {
newChildCardNewParent newChildCardNewParent
), ),
}); });
insertAuditTrail({
jobid: card.id,
operation: AuditTrailMapping.jobstatuschange(destination.toColumnId),
});
if (update.errors) { if (update.errors) {
notification["error"]({ notification["error"]({
message: t("production.errors.boardupdate", { message: t("production.errors.boardupdate", {
@@ -130,4 +148,7 @@ export function ProductionBoardKanbanComponent({ data, bodyshop, technician }) {
</div> </div>
); );
} }
export default connect(mapStateToProps, null)(ProductionBoardKanbanComponent); export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionBoardKanbanComponent);

View File

@@ -16,24 +16,32 @@ import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const iconStyle = { marginLeft: ".3rem" }; const iconStyle = { marginLeft: ".3rem" };
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language)) insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
}); });
export function ProductionListEmpAssignment({ bodyshop, record, type }) { export function ProductionListEmpAssignment({
insertAuditTrail,
bodyshop,
record,
type,
}) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleAdd = async (assignment) => { const handleAdd = async (assignment) => {
setLoading(true); setLoading(true);
const { operation, employeeid } = assignment; const { operation, employeeid, name } = assignment;
logImEXEvent("job_assign_employee", { operation }); logImEXEvent("job_assign_employee", { operation });
let empAssignment = determineFieldName(operation); let empAssignment = determineFieldName(operation);
@@ -44,6 +52,11 @@ export function ProductionListEmpAssignment({ bodyshop, record, type }) {
awaitRefetchQueries: true, awaitRefetchQueries: true,
}); });
insertAuditTrail({
jobid: record.id,
operation: AuditTrailMapping.jobassignmentchange(empAssignment, name),
});
if (!!result.errors) { if (!!result.errors) {
notification["error"]({ notification["error"]({
message: t("jobs.errors.assigning", { message: t("jobs.errors.assigning", {
@@ -64,6 +77,11 @@ export function ProductionListEmpAssignment({ bodyshop, record, type }) {
awaitRefetchQueries: true, awaitRefetchQueries: true,
}); });
insertAuditTrail({
jobid: record.id,
operation: AuditTrailMapping.jobassignmentremoved(empAssignment),
});
if (!!result.errors) { if (!!result.errors) {
notification["error"]({ notification["error"]({
message: t("jobs.errors.assigning", { message: t("jobs.errors.assigning", {
@@ -80,8 +98,8 @@ export function ProductionListEmpAssignment({ bodyshop, record, type }) {
}); });
const [visibility, setVisibility] = useState(false); const [visibility, setVisibility] = useState(false);
const onChange = (e) => { const onChange = (e, option) => {
setAssignment({ ...assignment, employeeid: e }); setAssignment({ ...assignment, employeeid: e, name: option.name });
}; };
const popContent = ( const popContent = (
@@ -99,7 +117,11 @@ export function ProductionListEmpAssignment({ bodyshop, record, type }) {
} }
> >
{bodyshop.employees.map((emp) => ( {bodyshop.employees.map((emp) => (
<Select.Option value={emp.id} key={emp.id}> <Select.Option
value={emp.id}
key={emp.id}
name={`${emp.first_name} ${emp.last_name}`}
>
{`${emp.first_name} ${emp.last_name}`} {`${emp.first_name} ${emp.last_name}`}
</Select.Option> </Select.Option>
))} ))}

View File

@@ -6,12 +6,21 @@ import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries"; import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({
export function ProductionListColumnStatus({ record, bodyshop }) { insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function ProductionListColumnStatus({
record,
bodyshop,
insertAuditTrail,
}) {
const [updateJob] = useMutation(UPDATE_JOB); const [updateJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
@@ -28,6 +37,11 @@ export function ProductionListColumnStatus({ record, bodyshop }) {
}, },
}, },
}); });
insertAuditTrail({
jobid: record.id,
operation: AuditTrailMapping.jobstatuschange(key),
});
setLoading(false); setLoading(false);
}; };
@@ -52,4 +66,7 @@ export function ProductionListColumnStatus({ record, bodyshop }) {
</Dropdown> </Dropdown>
); );
} }
export default connect(mapStateToProps, null)(ProductionListColumnStatus); export default connect(
mapStateToProps,
mapDispatchToProps
)(ProductionListColumnStatus);

View File

@@ -25,6 +25,7 @@ export function ProductionListTable({
currentUser, currentUser,
state, state,
setColumns, setColumns,
setState,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW); const [updateDefaultProdView] = useMutation(UPDATE_ACTIVE_PROD_LIST_VIEW);
@@ -43,6 +44,10 @@ export function ProductionListTable({
}; };
}) })
); );
setState(
bodyshop.production_config.filter((pc) => pc.name === value)[0].columns
.tableState
);
const assoc = bodyshop.associations.find( const assoc = bodyshop.associations.find(
(a) => a.useremail === currentUser.email (a) => a.useremail === currentUser.email
@@ -77,6 +82,8 @@ export function ProductionListTable({
}; };
}) })
); );
setState(bodyshop.production_config[0].columns.tableState);
}; };
return ( return (

View File

@@ -41,9 +41,9 @@ export function ProductionListTable({
const [state, setState] = useState( const [state, setState] = useState(
(bodyshop.production_config && (bodyshop.production_config &&
bodyshop.production_config.find((p) => p.name === defaultView) bodyshop.production_config.find((p) => p.name === defaultView)?.columns
?.tableState) || .tableState) ||
bodyshop.production_config[0]?.tableState || { bodyshop.production_config[0]?.columns.tableState || {
sortedInfo: {}, sortedInfo: {},
filteredInfo: { text: "" }, filteredInfo: { text: "" },
} }
@@ -173,6 +173,7 @@ export function ProductionListTable({
<ProductionListTableViewSelect <ProductionListTableViewSelect
state={state} state={state}
setState={setState}
setColumns={setColumns} setColumns={setColumns}
/> />

View File

@@ -1,11 +1,16 @@
import { Button, Form, Input } from "antd"; import { Button, Form, Input, notification } from "antd";
import { LockOutlined } from "@ant-design/icons";
import React from "react"; import React from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { updateUserDetails } from "../../redux/user/user.actions"; import { updateUserDetails } from "../../redux/user/user.actions";
import { selectCurrentUser } from "../../redux/user/user.selectors"; import { selectCurrentUser } from "../../redux/user/user.selectors";
import { logImEXEvent } from "../../firebase/firebase.utils"; import {
logImEXEvent,
updateCurrentPassword,
} from "../../firebase/firebase.utils";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser, currentUser: selectCurrentUser,
@@ -28,33 +33,96 @@ export default connect(
}); });
}; };
const handleChangePassword = async ({ password }) => {
logImEXEvent("password_update");
try {
await updateCurrentPassword(password);
notification.success({
message: t("user.successess.passwordchanged"),
});
} catch (error) {
notification.error({
message: error.message,
});
}
};
return ( return (
<div> <div>
<Form <Form
onFinish={handleFinish} onFinish={handleFinish}
autoComplete={"no"} autoComplete={"no"}
initialValues={currentUser} initialValues={currentUser}
layout="vertical"
> >
<Form.Item <LayoutFormRow>
label={t("user.fields.displayname")} <Form.Item
rules={[ label={t("user.fields.displayname")}
{ rules={[
required: true, {
//message: t("general.validation.required"), required: true,
}, //message: t("general.validation.required"),
]} },
name="displayName" ]}
> name="displayName"
<Input /> >
</Form.Item> <Input />
<Form.Item label={t("user.fields.photourl")} name="photoURL"> </Form.Item>
<Input /> <Form.Item label={t("user.fields.photourl")} name="photoURL">
</Form.Item> <Input />
</Form.Item>
</LayoutFormRow>
<Button type="primary" key="submit" htmlType="submit"> <Button type="primary" key="submit" htmlType="submit">
{t("user.actions.updateprofile")} {t("user.actions.updateprofile")}
</Button> </Button>
</Form> </Form>
<Form
onFinish={handleChangePassword}
autoComplete={"no"}
initialValues={currentUser}
layout="vertical"
>
<LayoutFormRow>
<Form.Item label={t("general.labels.newpassword")} name="password">
<Input
prefix={<LockOutlined />}
type="password"
placeholder={t("general.labels.password")}
/>
</Form.Item>
<Form.Item
label={t("general.labels.confirmpassword")}
name="password-confirm"
dependencies={["password"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
({ getFieldValue }) => ({
validator(rule, value) {
if (!value || getFieldValue("password") === value) {
return Promise.resolve();
}
return Promise.reject(
t("general.labels.passwordsdonotmatch")
);
},
}),
]}
>
<Input
prefix={<LockOutlined />}
type="password"
placeholder={t("general.labels.password")}
/>
</Form.Item>
</LayoutFormRow>
<Button type="primary" key="submit" htmlType="submit">
{t("user.actions.changepassword")}
</Button>
</Form>
</div> </div>
); );
}); });

View File

@@ -1,5 +1,5 @@
import { useLazyQuery } from "@apollo/client"; import { useLazyQuery } from "@apollo/client";
import { Button, DatePicker, Form, Radio } from "antd"; import { Button, DatePicker, Form, Radio, Space } from "antd";
import moment from "moment"; import moment from "moment";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -13,6 +13,7 @@ import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component"; import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component"; import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import "./report-center-modal.styles.scss";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
reportCenterModal: selectReportCenter, reportCenterModal: selectReportCenter,
@@ -60,8 +61,12 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
{ {
name: values.key, name: values.key,
variables: { variables: {
...(start ? { start: moment(start).format("YYYY-MM-DD") } : {}), ...(start
...(end ? { end: moment(end).format("YYYY-MM-DD") } : {}), ? { start: moment(start).startOf("day").format("YYYY-MM-DD") }
: {}),
...(end
? { end: moment(end).endOf("day").format("YYYY-MM-DD") }
: {}),
...(id ? { id: id } : {}), ...(id ? { id: id } : {}),
}, },
}, },
@@ -86,6 +91,7 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
<Form.Item <Form.Item
name="key" name="key"
label={t("reportcenter.labels.key")} label={t("reportcenter.labels.key")}
// className="radio-group-columns"
rules={[ rules={[
{ {
required: true, required: true,
@@ -93,12 +99,21 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
}, },
]} ]}
> >
<Radio.Group style={{ columns: "3 auto" }}> <Radio.Group>
{Object.keys(Templates).map((key) => ( <Space
<Radio key={key} value={key}> direction="vertical"
{Templates[key].title} wrap
</Radio> size="small"
))} style={{
maxHeight: "50vh",
}}
>
{Object.keys(Templates).map((key) => (
<Radio key={key} value={key}>
{Templates[key].title}
</Radio>
))}
</Space>
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
<Form.Item dependencies={["key"]}> <Form.Item dependencies={["key"]}>

View File

@@ -31,7 +31,7 @@ export function ReportCenterModalContainer({
onCancel={() => toggleModalVisible()} onCancel={() => toggleModalVisible()}
cancelButtonProps={{ style: { display: "none" } }} cancelButtonProps={{ style: { display: "none" } }}
destroyOnClose destroyOnClose
width="60%" width="80%"
> >
<ReportCenterModalComponent /> <ReportCenterModalComponent />
</Modal> </Modal>

View File

@@ -0,0 +1,11 @@
.radio-group-columns {
.ant-radio-group {
// display: block;
}
.ant-radio-wrapper {
display: block;
span {
word-wrap: break-word;
}
}
}

View File

@@ -1,4 +1,14 @@
import { Button, Card, Col, Form, Row, Select, Switch } from "antd"; import {
Button,
Col,
Form,
Input,
Row,
Select,
Space,
Switch,
Typography,
} from "antd";
import axios from "axios"; import axios from "axios";
import moment from "moment"; import moment from "moment";
import React, { useState } from "react"; import React, { useState } from "react";
@@ -91,31 +101,34 @@ export function ScheduleJobModalComponent({
<DateTimePicker onlyFuture /> <DateTimePicker onlyFuture />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<Card title={t("appointments.labels.smartscheduling")}>
<Typography.Title level={4}>
{t("appointments.labels.smartscheduling")}
</Typography.Title>
{
// smartOptions.length > 0 && (
// <div>{t("appointments.labels.suggesteddates")}</div>
// )
}
<Space wrap>
<Button onClick={handleSmartScheduling} loading={loading}> <Button onClick={handleSmartScheduling} loading={loading}>
{t("appointments.actions.calculate")} {t("appointments.actions.calculate")}
</Button> </Button>
{smartOptions.length > 0 && ( {smartOptions.map((d, idx) => (
<div>{t("appointments.labels.suggesteddates")}</div> <Button
)} className="imex-flex-row__margin"
<div key={idx}
className="imex-flex-row imex-flex-row__flex-space-around" onClick={() => {
style={{ flexWrap: "wrap" }} form.setFieldsValue({ start: new moment(d).add(8, "hours") });
> handleDateBlur();
{smartOptions.map((d, idx) => ( }}
<Button >
className="imex-flex-row__margin" <DateFormatter>{d}</DateFormatter>
key={idx} </Button>
onClick={() => { ))}
form.setFieldsValue({ start: new moment(d).add(8, "hours") }); </Space>
handleDateBlur();
}}
>
<DateFormatter>{d}</DateFormatter>
</Button>
))}
</div>
</Card>
<LayoutFormRow grow> <LayoutFormRow grow>
<Form.Item <Form.Item
name="notifyCustomer" name="notifyCustomer"
@@ -124,12 +137,9 @@ export function ScheduleJobModalComponent({
> >
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item shouldUpdate>
{() => ( <Form.Item name="email" label={t("jobs.fields.ownr_ea")}>
<Form.Item name="email" label={t("jobs.fields.ownr_ea")}> <EmailInput disabled={!form.getFieldValue("notifyCustomer")} />
<EmailInput disabled={!form.getFieldValue("notifyCustomer")} />
</Form.Item>
)}
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow> <LayoutFormRow grow>
@@ -158,6 +168,9 @@ export function ScheduleJobModalComponent({
))} ))}
</Select> </Select>
</Form.Item> </Form.Item>
<Form.Item name={"note"} label={t("appointments.fields.note")}>
<Input />
</Form.Item>
</LayoutFormRow> </LayoutFormRow>
{t("appointments.labels.history")} {t("appointments.labels.history")}
<ScheduleExistingAppointmentsList <ScheduleExistingAppointmentsList

View File

@@ -105,6 +105,7 @@ export function ScheduleJobModalContainer({
start: moment(values.start), start: moment(values.start),
end: moment(values.start).add(bodyshop.appt_length || 60, "minutes"), end: moment(values.start).add(bodyshop.appt_length || 60, "minutes"),
color: values.color, color: values.color,
note:values.note
}, },
jobId: jobId, jobId: jobId,
altTransport: values.alt_transport, altTransport: values.alt_transport,

View File

@@ -436,6 +436,21 @@ export default function ShopInfoGeneral({ form }) {
> >
<CurrencyInput /> <CurrencyInput />
</Form.Item> </Form.Item>
<Form.Item
name={["attach_pdf_to_email"]}
label={t("bodyshop.fields.attach_pdf_to_email")}
valuePropName="checked"
>
<Switch />
</Form.Item>
<Form.Item
name={["tt_allow_post_to_invoiced"]}
label={t("bodyshop.fields.tt_allow_post_to_invoiced")}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}> <LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}>
<Form.List name={["md_messaging_presets"]}> <Form.List name={["md_messaging_presets"]}>

View File

@@ -89,7 +89,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
> >
<Input onBlur={handleBlur} /> <Input onBlur={handleBlur} />
</Form.Item> </Form.Item>
<Form.Item {/* <Form.Item
label={t( label={t(
"bodyshop.fields.responsibilitycenter_accountnumber" "bodyshop.fields.responsibilitycenter_accountnumber"
)} )}
@@ -103,7 +103,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
]} ]}
> >
<Input onBlur={handleBlur} /> <Input onBlur={handleBlur} />
</Form.Item> </Form.Item> */}
<Form.Item <Form.Item
label={t( label={t(
"bodyshop.fields.responsibilitycenter_accountdesc" "bodyshop.fields.responsibilitycenter_accountdesc"
@@ -119,7 +119,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
> >
<Input onBlur={handleBlur} /> <Input onBlur={handleBlur} />
</Form.Item> </Form.Item>
<Form.Item {/* <Form.Item
label={t( label={t(
"bodyshop.fields.responsibilitycenter_accountitem" "bodyshop.fields.responsibilitycenter_accountitem"
)} )}
@@ -133,7 +133,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
]} ]}
> >
<Input onBlur={handleBlur} /> <Input onBlur={handleBlur} />
</Form.Item> </Form.Item> */}
<DeleteFilled <DeleteFilled
onClick={() => { onClick={() => {
remove(field.name); remove(field.name);
@@ -182,7 +182,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
> >
<Input onBlur={handleBlur} /> <Input onBlur={handleBlur} />
</Form.Item> </Form.Item>
<Form.Item {/* <Form.Item
label={t( label={t(
"bodyshop.fields.responsibilitycenter_accountname" "bodyshop.fields.responsibilitycenter_accountname"
)} )}
@@ -211,7 +211,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
]} ]}
> >
<Input onBlur={handleBlur} /> <Input onBlur={handleBlur} />
</Form.Item> </Form.Item> */}
<Form.Item <Form.Item
label={t( label={t(
"bodyshop.fields.responsibilitycenter_accountdesc" "bodyshop.fields.responsibilitycenter_accountdesc"
@@ -1081,7 +1081,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item {/* <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountnumber")} label={t("bodyshop.fields.responsibilitycenter_accountnumber")}
rules={[ rules={[
{ {
@@ -1097,8 +1097,8 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
]} ]}
> >
<Input /> <Input />
</Form.Item> </Form.Item> */}
<Form.Item {/* <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountname")} label={t("bodyshop.fields.responsibilitycenter_accountname")}
rules={[ rules={[
{ {
@@ -1114,7 +1114,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
]} ]}
> >
<Input /> <Input />
</Form.Item> </Form.Item> */}
<Form.Item <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountdesc")} label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
rules={[ rules={[
@@ -1175,7 +1175,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item {/* <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountnumber")} label={t("bodyshop.fields.responsibilitycenter_accountnumber")}
rules={[ rules={[
{ {
@@ -1203,7 +1203,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
name={["md_responsibility_centers", "taxes", "state", "accountname"]} name={["md_responsibility_centers", "taxes", "state", "accountname"]}
> >
<Input /> <Input />
</Form.Item> </Form.Item> */}
<Form.Item <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountdesc")} label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
rules={[ rules={[
@@ -1254,7 +1254,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item {/* <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountnumber")} label={t("bodyshop.fields.responsibilitycenter_accountnumber")}
rules={[ rules={[
{ {
@@ -1282,7 +1282,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
name={["md_responsibility_centers", "taxes", "local", "accountname"]} name={["md_responsibility_centers", "taxes", "local", "accountname"]}
> >
<Input /> <Input />
</Form.Item> </Form.Item> */}
<Form.Item <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountdesc")} label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
rules={[ rules={[
@@ -1320,8 +1320,8 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
<InputNumber precision={2} /> <InputNumber precision={2} />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow> <LayoutFormRow header={<div>AR</div>}>
<Form.Item {/* <Form.Item
label={t("bodyshop.fields.responsibilitycenters.ar")} label={t("bodyshop.fields.responsibilitycenters.ar")}
rules={[ rules={[
{ {
@@ -1344,7 +1344,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
name={["md_responsibility_centers", "ar", "accountnumber"]} name={["md_responsibility_centers", "ar", "accountnumber"]}
> >
<Input /> <Input />
</Form.Item> </Form.Item> */}
<Form.Item <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountname")} label={t("bodyshop.fields.responsibilitycenter_accountname")}
rules={[ rules={[
@@ -1357,7 +1357,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
<Form.Item {/* <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountdesc")} label={t("bodyshop.fields.responsibilitycenter_accountdesc")}
rules={[ rules={[
{ {
@@ -1380,9 +1380,9 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
name={["md_responsibility_centers", "ar", "accountitem"]} name={["md_responsibility_centers", "ar", "accountitem"]}
> >
<Input /> <Input />
</Form.Item> </Form.Item> */}
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow> {/* <LayoutFormRow>
<Form.Item <Form.Item
label={t("bodyshop.fields.responsibilitycenters.ap")} label={t("bodyshop.fields.responsibilitycenters.ap")}
rules={[ rules={[
@@ -1443,9 +1443,9 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
> >
<Input /> <Input />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow> */}
<LayoutFormRow> <LayoutFormRow header={<div>Refund</div>}>
<Form.Item {/* <Form.Item
label={t("bodyshop.fields.responsibilitycenters.refund")} label={t("bodyshop.fields.responsibilitycenters.refund")}
rules={[ rules={[
{ {
@@ -1456,8 +1456,8 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
name={["md_responsibility_centers", "refund", "name"]} name={["md_responsibility_centers", "refund", "name"]}
> >
<Input /> <Input />
</Form.Item> </Form.Item> */}
<Form.Item {/* <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountnumber")} label={t("bodyshop.fields.responsibilitycenter_accountnumber")}
rules={[ rules={[
{ {
@@ -1468,8 +1468,8 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
name={["md_responsibility_centers", "refund", "accountnumber"]} name={["md_responsibility_centers", "refund", "accountnumber"]}
> >
<Input /> <Input />
</Form.Item> </Form.Item> */}
<Form.Item {/* <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountname")} label={t("bodyshop.fields.responsibilitycenter_accountname")}
rules={[ rules={[
{ {
@@ -1492,7 +1492,7 @@ export default function ShopInfoResponsibilityCenterComponent({ form }) {
name={["md_responsibility_centers", "refund", "accountdesc"]} name={["md_responsibility_centers", "refund", "accountdesc"]}
> >
<Input /> <Input />
</Form.Item> </Form.Item> */}
<Form.Item <Form.Item
label={t("bodyshop.fields.responsibilitycenter_accountitem")} label={t("bodyshop.fields.responsibilitycenter_accountitem")}
rules={[ rules={[

View File

@@ -30,7 +30,10 @@ export function TechClockInComponent({ form, bodyshop, technician }) {
}, },
]} ]}
> >
<JobSearchSelect /> <JobSearchSelect
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
notExported={!bodyshop.tt_allow_post_to_invoiced}
/>
</Form.Item> </Form.Item>
<Form.Item <Form.Item

View File

@@ -105,7 +105,8 @@ export function TimeTicketList({
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
sorter: (a, b) => alphaSort(a.job.ro_number, b.job.ro_number), sorter: (a, b) =>
alphaSort(a.job && a.job.ro_number, b.job && b.job.ro_number),
sortOrder: sortOrder:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order, state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) => render: (text, record) =>

View File

@@ -82,7 +82,10 @@ export function TimeTicketModalComponent({
}, },
]} ]}
> >
<JobSearchSelect convertedOnly notExported={false} /> <JobSearchSelect
convertedOnly={!bodyshop.tt_allow_post_to_invoiced}
notExported={!bodyshop.tt_allow_post_to_invoiced}
/>
</Form.Item> </Form.Item>
)} )}
</Form.Item> </Form.Item>

View File

@@ -35,6 +35,24 @@ export const updateCurrentUser = (userDetails) => {
}); });
}; };
export const updateCurrentPassword = async (password) => {
const currentUser = await getCurrentUser();
return currentUser.updatePassword(password);
// return new Promise((resolve, reject) => {
// const unsubscribe = auth.onAuthStateChanged(
// (userAuth) => {
// userAuth.updatePassword(password).then((r) => {
// unsubscribe();
// resolve(userAuth);
// });
// },
// (error) => reject(error)
// );
// });
};
let messaging; let messaging;
try { try {
messaging = firebase.messaging(); messaging = firebase.messaging();

View File

@@ -20,6 +20,7 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
isintake isintake
block block
color color
note
job { job {
alt_transport alt_transport
ro_number ro_number
@@ -69,6 +70,7 @@ export const INSERT_APPOINTMENT_BLOCK = gql`
title title
isintake isintake
block block
note
} }
} }
} }
@@ -90,6 +92,7 @@ export const INSERT_APPOINTMENT = gql`
isintake isintake
block block
color color
note
} }
} }
update_jobs( update_jobs(
@@ -116,6 +119,7 @@ export const QUERY_APPOINTMENT_BY_DATE = gql`
isintake isintake
block block
color color
note
job { job {
alt_transport alt_transport
ro_number ro_number
@@ -168,6 +172,7 @@ export const UPDATE_APPOINTMENT = gql`
isintake isintake
block block
color color
note
} }
} }
} }
@@ -198,6 +203,7 @@ export const QUERY_APPOINTMENTS_BY_JOBID = gql`
canceled canceled
created_at created_at
block block
note
} }
} }
`; `;

View File

@@ -1,18 +1,31 @@
import { gql } from "@apollo/client"; import { gql } from "@apollo/client";
export const QUERY_AUDIT_TRAIL = gql` export const QUERY_AUDIT_TRAIL = gql`
query QUERY_AUDIT_TRAIL($id: uuid!) { query QUERY_AUDIT_TRAIL($jobid: uuid!) {
audit_trail(where: { recordid: { _eq: $id } }) { audit_trail(
where: { jobid: { _eq: $jobid } }
order_by: { created: desc }
) {
useremail useremail
tabname jobid
schemaname
recordid
operation operation
old_val
new_val
id id
created created
bodyshopid bodyshopid
} }
} }
`; `;
export const INSERT_AUDIT_TRAIL = gql`
mutation INSERT_AUDIT_TRAIL($auditObj: audit_trail_insert_input!) {
insert_audit_trail_one(object: $auditObj) {
id
jobid
billid
bodyshopid
created
operation
useremail
}
}
`;

View File

@@ -91,6 +91,8 @@ export const QUERY_BODYSHOP = gql`
md_jobline_presets md_jobline_presets
cdk_dealerid cdk_dealerid
features features
attach_pdf_to_email
tt_allow_post_to_invoiced
employees { employees {
id id
active active
@@ -178,6 +180,8 @@ export const UPDATE_SHOP = gql`
jc_hourly_rates jc_hourly_rates
md_jobline_presets md_jobline_presets
cdk_dealerid cdk_dealerid
attach_pdf_to_email
tt_allow_post_to_invoiced
employees { employees {
id id
first_name first_name
@@ -204,6 +208,10 @@ export const QUERY_INTAKE_CHECKLIST = gql`
scheduled_delivery scheduled_delivery
intakechecklist intakechecklist
status status
owner {
allow_text_message
id
}
labhrs: joblines_aggregate( labhrs: joblines_aggregate(
where: { where: {
_and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }] _and: [{ mod_lbr_ty: { _neq: "LAR" } }, { removed: { _eq: false } }]

View File

@@ -23,19 +23,6 @@ export const GET_ALL_JOBLINES_BY_PK = gql`
notes notes
location location
tax_part tax_part
parts_order_lines {
id
parts_order {
id
order_number
order_date
user_email
vendor {
id
name
}
}
}
} }
} }
`; `;
@@ -228,7 +215,11 @@ export const generateJobLinesUpdatesForInvoicing = (joblines) => {
export const DELETE_JOB_LINE_BY_PK = gql` export const DELETE_JOB_LINE_BY_PK = gql`
mutation DELETE_JOB_LINE_BY_PK($joblineId: uuid!) { mutation DELETE_JOB_LINE_BY_PK($joblineId: uuid!) {
delete_joblines_by_pk(id: $joblineId) { update_joblines_by_pk(
pk_columns: { id: $joblineId }
_set: { removed: true }
) {
removed
id id
} }
} }

View File

@@ -559,6 +559,7 @@ export const GET_JOB_BY_PK = gql`
} }
payments { payments {
id id
jobid
amount amount
payer payer
created_at created_at
@@ -566,6 +567,8 @@ export const GET_JOB_BY_PK = gql`
transactionid transactionid
memo memo
date date
type
exportedat
} }
cccontracts { cccontracts {
id id
@@ -686,6 +689,8 @@ export const QUERY_JOB_CARD_DETAILS = gql`
v_make_desc v_make_desc
v_model_desc v_model_desc
v_color v_color
v_vin
plate_st
plate_no plate_no
vehicle { vehicle {
id id
@@ -1020,6 +1025,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
$search: String $search: String
$isConverted: Boolean $isConverted: Boolean
$notExported: Boolean $notExported: Boolean
$notInvoiced: Boolean
) { ) {
search_jobs( search_jobs(
args: { search: $search } args: { search: $search }
@@ -1028,6 +1034,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
_and: { _and: {
converted: { _eq: $isConverted } converted: { _eq: $isConverted }
date_exported: { _is_null: $notExported } date_exported: { _is_null: $notExported }
date_invoiced: { _is_null: $notInvoiced }
} }
} }
) { ) {
@@ -1040,6 +1047,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
v_make_desc v_make_desc
v_model_desc v_model_desc
v_model_yr v_model_yr
status
} }
} }
`; `;
@@ -1841,6 +1849,10 @@ export const QUERY_JOB_CHECKLISTS = gql`
scheduled_delivery scheduled_delivery
actual_delivery actual_delivery
production_vars production_vars
owner {
id
allow_text_message
}
bodyshop { bodyshop {
id id
intakechecklist intakechecklist

View File

@@ -5,6 +5,7 @@ export const INSERT_NEW_PARTS_ORDERS = gql`
insert_parts_orders(objects: $po) { insert_parts_orders(objects: $po) {
returning { returning {
id id
order_number
} }
} }
} }

View File

@@ -36,6 +36,7 @@ export const GLOBAL_SEARCH_QUERY = gql`
search_payments(args: { search: $search }) { search_payments(args: { search: $search }) {
id id
amount amount
paymentnum
job { job {
ro_number ro_number
id id

View File

@@ -81,48 +81,52 @@ export default class Home extends React.Component {
dataSource={Banner00DataSource} dataSource={Banner00DataSource}
isMobile={this.state.isMobile} isMobile={this.state.isMobile}
/>, />,
// <Content4 ...(process.env.NODE_ENV !== "production"
// id="Content4_0" ? [
// key="Content4_0" // <Content4
// dataSource={Content40DataSource} // id="Content4_0"
// isMobile={this.state.isMobile} // key="Content4_0"
// />, // dataSource={Content40DataSource}
<Content1 // isMobile={this.state.isMobile}
id="Content1_0" // />,
key="Content1_0" <Content1
dataSource={Content10DataSource} id="Content1_0"
isMobile={this.state.isMobile} key="Content1_0"
/>, dataSource={Content10DataSource}
<Content0 isMobile={this.state.isMobile}
id="Content0_0" />,
key="Content0_0" <Content0
dataSource={Content00DataSource} id="Content0_0"
isMobile={this.state.isMobile} key="Content0_0"
/>, dataSource={Content00DataSource}
<Pricing2 isMobile={this.state.isMobile}
id="Pricing2_0" />,
key="Pricing2_0" <Pricing2
dataSource={Pricing20DataSource} id="Pricing2_0"
isMobile={this.state.isMobile} key="Pricing2_0"
/>, dataSource={Pricing20DataSource}
// <Pricing1 isMobile={this.state.isMobile}
// id="Pricing1_1" />,
// key="Pricing1_1" // <Pricing1
// dataSource={Pricing11DataSource} // id="Pricing1_1"
// isMobile={this.state.isMobile} // key="Pricing1_1"
// />, // dataSource={Pricing11DataSource}
// <Content3 // isMobile={this.state.isMobile}
// id="Content3_0" // />,
// key="Content3_0" // <Content3
// dataSource={Content30DataSource} // id="Content3_0"
// isMobile={this.state.isMobile} // key="Content3_0"
// />, // dataSource={Content30DataSource}
// <Content12 // isMobile={this.state.isMobile}
// id="Content12_0" // />,
// key="Content12_0" // <Content12
// dataSource={Content120DataSource} // id="Content12_0"
// isMobile={this.state.isMobile} // key="Content12_0"
// />, // dataSource={Content120DataSource}
// isMobile={this.state.isMobile}
// />,
]
: []),
<Footer1 <Footer1
id="Footer1_0" id="Footer1_0"
key="Footer1_0" key="Footer1_0"

View File

@@ -12,7 +12,6 @@ import AlertComponent from "../../components/alert/alert.component";
import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries"; import { QUERY_EXPORT_LOG_PAGINATED } from "../../graphql/accounting.queries";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateTimeFormatter } from "../../utils/DateFormatter"; import { DateTimeFormatter } from "../../utils/DateFormatter";
import { alphaSort } from "../../utils/sorters";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -79,12 +78,10 @@ export function ExportLogsPageComponent({ bodyshop }) {
title: t("jobs.fields.ro_number"), title: t("jobs.fields.ro_number"),
dataIndex: "ro_number", dataIndex: "ro_number",
key: "ro_number", key: "ro_number",
sorter: (a, b) => alphaSort(a.ro_number, b.ro_number),
sortOrder: sortcolumn === "ro_number" && sortorder,
render: (text, record) => render: (text, record) =>
record.job && ( record.job && (
<Link to={"/manage/jobs/" + record.job && record.job.id}> <Link to={`/manage/jobs/${record.job.id}`}>
{(record.job && record.job.ro_number) || t("general.labels.na")} {(record.job && record.job.ro_number) || t("general.labels.na")}
</Link> </Link>
), ),

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client"; import { useQuery } from "@apollo/client";
import { Card, Col, Result, Row, Space } from "antd"; import { Card, Col, Result, Row, Space, Typography } from "antd";
import React, { useEffect } from "react"; import React, { useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -15,6 +15,8 @@ import JobAdminOwnerReassociate from "../../components/jobs-admin-owner-reassoci
import JobsAdminUnvoid from "../../components/jobs-admin-unvoid/jobs-admin-unvoid.component"; import JobsAdminUnvoid from "../../components/jobs-admin-unvoid/jobs-admin-unvoid.component";
import JobAdminVehicleReassociate from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component"; import JobAdminVehicleReassociate from "../../components/jobs-admin-vehicle-reassociate/jobs-admin-vehicle-reassociate.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component"; import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import JobsAdminStatus from "../../components/jobs-admin-change-status/jobs-admin-change.status.component";
import NotFound from "../../components/not-found/not-found.component"; import NotFound from "../../components/not-found/not-found.component";
import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component"; import RbacWrapper from "../../components/rbac-wrapper/rbac-wrapper.component";
import { GET_JOB_BY_PK } from "../../graphql/jobs.queries"; import { GET_JOB_BY_PK } from "../../graphql/jobs.queries";
@@ -82,6 +84,9 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
return ( return (
<RbacWrapper action="jobs:admin"> <RbacWrapper action="jobs:admin">
<Typography.Title level={4} style={{ color: "tomato" }}>
{t("jobs.labels.adminwarning")}
</Typography.Title>
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col {...colSpan}> <Col {...colSpan}>
<Card style={cardStyle}> <Card style={cardStyle}>
@@ -96,6 +101,7 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
<JobsAdminDeleteIntake job={data ? data.jobs_by_pk : {}} /> <JobsAdminDeleteIntake job={data ? data.jobs_by_pk : {}} />
<JobsAdminMarkReexport job={data ? data.jobs_by_pk : {}} /> <JobsAdminMarkReexport job={data ? data.jobs_by_pk : {}} />
<JobsAdminUnvoid job={data ? data.jobs_by_pk : {}} /> <JobsAdminUnvoid job={data ? data.jobs_by_pk : {}} />
<JobsAdminStatus job={data ? data.jobs_by_pk : {}} />
</Space> </Space>
</Card> </Card>
</Col> </Col>

View File

@@ -42,9 +42,26 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
setLoading(true); setLoading(true);
const result = await client.mutate({ const result = await client.mutate({
mutation: generateJobLinesUpdatesForInvoicing(values.joblines), mutation: generateJobLinesUpdatesForInvoicing(values.joblines),
});
if (result.errors) {
return; // Abandon the rest of the close.
}
const closeResult = await closeJob({
variables: {
jobId: job.id,
job: {
status: bodyshop.md_ro_statuses.default_invoiced || "",
date_invoiced: new Date(),
actual_in: values.actual_in,
actual_completion: values.actual_completion,
actual_delivery: values.actual_delivery,
},
},
refetchQueries: ["QUERY_JOB_CLOSE_DETAILS"], refetchQueries: ["QUERY_JOB_CLOSE_DETAILS"],
awaitRefetchQueries: true, awaitRefetchQueries: true,
}); });
if (!result.errors) { if (!result.errors) {
notification["success"]({ message: t("jobs.successes.save") }); notification["success"]({ message: t("jobs.successes.save") });
// form.resetFields(); // form.resetFields();
@@ -56,18 +73,6 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
}); });
return; // Abandon the rest of the close. return; // Abandon the rest of the close.
} }
form.resetFields();
form.resetFields();
const closeResult = await closeJob({
variables: {
jobId: job.id,
job: {
status: bodyshop.md_ro_statuses.default_invoiced || "",
date_invoiced: new Date(),
},
},
});
if (!closeResult.errors) { if (!closeResult.errors) {
setLoading(false); setLoading(false);
@@ -84,6 +89,8 @@ export function JobsCloseComponent({ job, bodyshop, jobRO }) {
}), }),
}); });
} }
form.resetFields();
form.resetFields();
setLoading(false); setLoading(false);
}; };

View File

@@ -5,6 +5,7 @@ import Icon, {
FileImageFilled, FileImageFilled,
PrinterFilled, PrinterFilled,
ToolFilled, ToolFilled,
HistoryOutlined,
} from "@ant-design/icons"; } from "@ant-design/icons";
import { import {
Button, Button,
@@ -45,6 +46,9 @@ import ScheduleJobModalContainer from "../../components/schedule-job-modal/sched
import { selectJobReadOnly } from "../../redux/application/application.selectors"; import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import JobAuditTrail from "../../components/job-audit-trail/job-audit-trail.component";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { insertAuditTrail } from "../../redux/application/application.actions";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
@@ -53,6 +57,8 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) => setPrintCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "printCenter" })), dispatch(setModalContext({ context: context, modal: "printCenter" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
}); });
export function JobsDetailPage({ export function JobsDetailPage({
setPrintCenterContext, setPrintCenterContext,
@@ -60,6 +66,7 @@ export function JobsDetailPage({
job, job,
mutationUpdateJob, mutationUpdateJob,
handleSubmit, handleSubmit,
insertAuditTrail,
refetch, refetch,
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
@@ -81,6 +88,7 @@ export function JobsDetailPage({
const handleFinish = async (values) => { const handleFinish = async (values) => {
setLoading(true); setLoading(true);
const result = await mutationUpdateJob({ const result = await mutationUpdateJob({
variables: { variables: {
jobId: job.id, jobId: job.id,
@@ -105,6 +113,62 @@ export function JobsDetailPage({
notification["success"]({ notification["success"]({
message: t("jobs.successes.savetitle"), message: t("jobs.successes.savetitle"),
}); });
const changedAuditFields = form.getFieldsValue(
[
"scheduled_in",
"actual_in",
"scheduled_completion",
"actual_completion",
"scheduled_delivery",
"actual_delivery",
"date_invoiced",
"ins_co_nm",
"ded_amt",
"ded_status",
"date_exported",
"special_coverage_policy",
"ca_gst_registrant",
"ca_bc_pvrt",
"scheduled_in",
"rate_la1",
"rate_la2",
"rate_la3",
"rate_la4",
"rate_laa",
"rate_lab",
"rate_lad",
"rate_lae",
"rate_laf",
"rate_lag",
"rate_lam",
"rate_lar",
"rate_las",
"rate_lau",
"rate_ma2s",
"rate_ma2t",
"rate_ma3s",
"rate_mabl",
"rate_macs",
"rate_mapa",
"rate_mahw",
"rate_mash",
"rate_matd",
],
(meta) => meta && meta.touched
);
Object.keys(changedAuditFields).forEach((key) => {
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobfieldchange(
key,
changedAuditFields[key] instanceof moment
? moment(changedAuditFields[key]).format("MM/DD/YYYY hh:mm a")
: changedAuditFields[key]
),
});
});
await refetch(); await refetch();
form.setFieldsValue(transormJobToForm(job)); form.setFieldsValue(transormJobToForm(job));
form.resetFields(); form.resetFields();
@@ -279,6 +343,17 @@ export function JobsDetailPage({
> >
<JobNotesContainer jobId={job.id} /> <JobNotesContainer jobId={job.id} />
</Tabs.TabPane> </Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<HistoryOutlined />
{t("jobs.labels.audit")}
</span>
}
key="audit"
>
<JobAuditTrail jobId={job.id} />
</Tabs.TabPane>
</Tabs> </Tabs>
</Form> </Form>
</div> </div>

View File

@@ -53,3 +53,8 @@ export const setOnline = (isOnline) => ({
type: ApplicationActionTypes.SET_ONLINE_STATUS, type: ApplicationActionTypes.SET_ONLINE_STATUS,
payload: isOnline, payload: isOnline,
}); });
export const insertAuditTrail = ({ jobid, billid, operation }) => ({
type: ApplicationActionTypes.INSERT_AUDIT_TRAIL,
payload: { jobid, billid, operation },
});

View File

@@ -1,6 +1,7 @@
import moment from "moment"; import moment from "moment";
import { all, call, put, select, takeLatest } from "redux-saga/effects"; import { all, call, put, select, takeLatest } from "redux-saga/effects";
import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries"; import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries";
import { INSERT_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import client from "../../utils/GraphQLClient"; import client from "../../utils/GraphQLClient";
import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils"; import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils";
import { import {
@@ -125,6 +126,56 @@ export function* calculateScheduleLoad({ payload: end }) {
} }
} }
export function* applicationSagas() { export function* onInsertAuditTrail() {
yield all([call(onCalculateScheduleLoad)]); yield takeLatest(
ApplicationActionTypes.INSERT_AUDIT_TRAIL,
insertAuditTrailSaga
);
}
export function* insertAuditTrailSaga({
payload: { jobid, billid, operation },
}) {
const state = yield select();
const bodyshop = state.user.bodyshop;
const currentUser = state.user.currentUser;
console.log(
"Inserting audit trail for",
bodyshop.shopname,
currentUser.email,
jobid,
billid,
operation
);
const variables = {
auditObj: {
bodyshopid: bodyshop.id,
jobid,
billid,
operation,
useremail: currentUser.email,
},
};
yield client.mutate({
mutation: INSERT_AUDIT_TRAIL,
variables,
update(cache, { data }) {
cache.modify({
fields: {
audit_trail(existingAuditTrail, { readField }) {
const newAuditTrail = cache.writeQuery({
data: data.insert_audit_trail_one,
query: INSERT_AUDIT_TRAIL,
variables,
});
return [...existingAuditTrail, newAuditTrail];
},
},
});
},
});
}
export function* applicationSagas() {
yield all([call(onCalculateScheduleLoad), call(onInsertAuditTrail)]);
} }

View File

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

View File

@@ -39,7 +39,7 @@ export function* openChatByPhone({ payload }) {
data: { conversations }, data: { conversations },
} = yield client.query({ } = yield client.query({
query: CONVERSATION_ID_BY_PHONE, query: CONVERSATION_ID_BY_PHONE,
variables: { phone: phone(phone_num)[0] }, variables: { phone: phone(phone_num).phoneNumber },
fetchPolicy: "network-only", fetchPolicy: "network-only",
}); });
@@ -53,7 +53,7 @@ export function* openChatByPhone({ payload }) {
variables: { variables: {
conversation: [ conversation: [
{ {
phone_num: phone(phone_num)[0], phone_num: phone(phone_num).phoneNumber,
bodyshopid: bodyshop.id, bodyshopid: bodyshop.id,
job_conversations: jobid ? { data: { jobid: jobid } } : null, job_conversations: jobid ? { data: { jobid: jobid } } : null,
}, },

View File

@@ -28,6 +28,7 @@ import {
validatePasswordResetSuccess, validatePasswordResetSuccess,
} from "./user.actions"; } from "./user.actions";
import UserActionTypes from "./user.types"; import UserActionTypes from "./user.types";
import * as Sentry from "@sentry/browser";
export function* onEmailSignInStart() { export function* onEmailSignInStart() {
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail); yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
@@ -100,8 +101,12 @@ export function* onUpdateUserDetails() {
} }
export function* updateUserDetails(userDetails) { export function* updateUserDetails(userDetails) {
try { try {
yield updateCurrentUser(userDetails.payload); const updatedDetails = yield updateCurrentUser(userDetails.payload);
yield put(updateUserDetailsSuccess(userDetails.payload)); console.log(
"🚀 ~ file: user.sagas.js ~ line 104 ~ updatedDetails",
updatedDetails
);
yield put(updateUserDetailsSuccess(updatedDetails));
notification.open({ notification.open({
type: "success", type: "success",
message: i18next.t("profile.successes.updated"), message: i18next.t("profile.successes.updated"),
@@ -174,6 +179,11 @@ export function* signInSuccessSaga({ payload }) {
]); ]);
console.log("Setting $crisp segments", ["user"]); console.log("Setting $crisp segments", ["user"]);
window.$crisp.push(["set", "session:segments", [["user"]]]); window.$crisp.push(["set", "session:segments", [["user"]]]);
Sentry.setUser({
email: payload.email,
username: payload.displayName || payload.email,
});
} catch (error) { } catch (error) {
console.log("Error updating Crisp settings.", error); console.log("Error updating Crisp settings.", error);
} }

View File

@@ -37,6 +37,7 @@
"fields": { "fields": {
"alt_transport": "Alt. Trans.", "alt_transport": "Alt. Trans.",
"color": "Appointment Color", "color": "Appointment Color",
"note": "Appt. Note",
"time": "Appointment Time", "time": "Appointment Time",
"title": "Title" "title": "Title"
}, },
@@ -52,6 +53,7 @@
"nocompletingjobs": "No jobs scheduled for completion.", "nocompletingjobs": "No jobs scheduled for completion.",
"nodateselected": "No date has been selected.", "nodateselected": "No date has been selected.",
"priorappointments": "Previous Appointments", "priorappointments": "Previous Appointments",
"reminder": "This is {{shopname}} reminding you about an appointment on {{date}} at {{time}}. Please let us know if you are not able to make the appointment. We look forward to seeing you soon. ",
"scheduledfor": "Scheduled appointment for: ", "scheduledfor": "Scheduled appointment for: ",
"smartscheduling": "Smart Scheduling", "smartscheduling": "Smart Scheduling",
"suggesteddates": "Suggested Dates" "suggesteddates": "Suggested Dates"
@@ -82,6 +84,24 @@
"values": "Values" "values": "Values"
} }
}, },
"audit_trail": {
"messages": {
"billposted": "Bill with invoice number {{invoice_number}} posted.",
"billupdated": "Bill with invoice number {{invoice_number}} updated.",
"jobassignmentchange": "Employee {{name}} assigned to {{operation}}",
"jobassignmentremoved": "Employee assignment removed for {{operation}}",
"jobchecklist": "Checklist type \"{{type}}\" completed. In production set to {{inproduction}}. Status set to {{status}}.",
"jobconverted": "Job converted and assigned number {{ro_number}}.",
"jobfieldchanged": "Job field $t(jobs.fields.{{field}}) changed to {{value}}.",
"jobimported": "Job imported.",
"jobinproductionchange": "Job production status set to {{inproduction}}",
"jobmodifylbradj": "Labor adjustments modified.",
"jobspartsorder": "Parts order {{order_number}} added to job.",
"jobspartsreturn": "Parts return {{order_number}} added to job.",
"jobstatuschange": "Job status changed to {{status}}.",
"jobsupplement": "Job supplement imported."
}
},
"billlines": { "billlines": {
"actions": { "actions": {
"newline": "New Line" "newline": "New Line"
@@ -199,6 +219,7 @@
"label": "Label" "label": "Label"
}, },
"appt_length": "Default Appointment Length", "appt_length": "Default Appointment Length",
"attach_pdf_to_email": "Attach PDF copy to sent emails?",
"bill_federal_tax_rate": "Bills - Federal Tax Rate %", "bill_federal_tax_rate": "Bills - Federal Tax Rate %",
"bill_local_tax_rate": "Bill - Provincial/State Tax Rate %", "bill_local_tax_rate": "Bill - Provincial/State Tax Rate %",
"bill_state_tax_rate": "Bill - Provincial/State Tax Rate %", "bill_state_tax_rate": "Bill - Provincial/State Tax Rate %",
@@ -425,6 +446,7 @@
"production_statuses": "Production Statuses" "production_statuses": "Production Statuses"
}, },
"target_touchtime": "Target Touch Time", "target_touchtime": "Target Touch Time",
"tt_allow_post_to_invoiced": "Allow Time Tickets to be posted to Invoiced & Exported Jobs",
"use_fippa": "Use FIPPA for Names on Generated Documents?", "use_fippa": "Use FIPPA for Names on Generated Documents?",
"website": "Website", "website": "Website",
"zip_post": "Zip/Postal Code" "zip_post": "Zip/Postal Code"
@@ -490,6 +512,7 @@
}, },
"labels": { "labels": {
"addtoproduction": "Add Job to Production?", "addtoproduction": "Add Job to Production?",
"allow_text_message": "Permission to Text?",
"checklist": "Checklist", "checklist": "Checklist",
"printpack": "Job Intake Print Pack", "printpack": "Job Intake Print Pack",
"removefromproduction": "Remove job from production?" "removefromproduction": "Remove job from production?"
@@ -742,6 +765,7 @@
"attachments": "Attachments", "attachments": "Attachments",
"documents": "Documents", "documents": "Documents",
"generatingemail": "Generating email...", "generatingemail": "Generating email...",
"pdfcopywillbeattached": "A PDF copy of this email will be attached when it is sent.",
"preview": "Email Preview" "preview": "Email Preview"
}, },
"successes": { "successes": {
@@ -855,6 +879,7 @@
"message": "Message", "message": "Message",
"monday": "Monday", "monday": "Monday",
"na": "N/A", "na": "N/A",
"newpassword": "New Password",
"no": "No", "no": "No",
"nointernet": "It looks like you're not connected to the internet.", "nointernet": "It looks like you're not connected to the internet.",
"nointernet_sub": "Please check your connection and try again. ", "nointernet_sub": "Please check your connection and try again. ",
@@ -875,6 +900,7 @@
"sendagain": "Send Again", "sendagain": "Send Again",
"sendby": "Send By", "sendby": "Send By",
"signin": "Sign In", "signin": "Sign In",
"sms": "SMS",
"sub_status": { "sub_status": {
"expired": "The subscription for this shop has expired. Please contact technical support to reactivate the subscription. " "expired": "The subscription for this shop has expired. Please contact technical support to reactivate the subscription. "
}, },
@@ -1034,6 +1060,9 @@
"intake": "Intake", "intake": "Intake",
"manualnew": "Create New Job Manually", "manualnew": "Create New Job Manually",
"mark": "Mark", "mark": "Mark",
"markasexported": "Mark as Exported",
"markpstexempt": "Mark Job PST Exempt",
"markpstexemptconfirm": "Are you sure you want to do this? To undo this, you must manually update all PST rates.",
"postbills": "Post Bills", "postbills": "Post Bills",
"printCenter": "Print Center", "printCenter": "Print Center",
"recalculate": "Recalculate", "recalculate": "Recalculate",
@@ -1042,6 +1071,7 @@
"schedule": "Schedule", "schedule": "Schedule",
"sendcsi": "Send CSI", "sendcsi": "Send CSI",
"sync": "Sync", "sync": "Sync",
"uninvoice": "Uninvoice",
"unvoid": "Unvoid Job", "unvoid": "Unvoid Job",
"viewchecklist": "View Checklists", "viewchecklist": "View Checklists",
"viewdetail": "View Details" "viewdetail": "View Details"
@@ -1223,6 +1253,7 @@
"servicecar": "Service Car", "servicecar": "Service Car",
"servicing_dealer": "Servicing Dealer", "servicing_dealer": "Servicing Dealer",
"servicing_dealer_contact": "Servicing Dealer Contact", "servicing_dealer_contact": "Servicing Dealer Contact",
"special_coverage_policy": "Special Coverage Policy",
"specialcoveragepolicy": "Special Coverage Policy", "specialcoveragepolicy": "Special Coverage Policy",
"state_tax_rate": "Provincial/State Tax Rate", "state_tax_rate": "Provincial/State Tax Rate",
"status": "Job Status", "status": "Job Status",
@@ -1260,6 +1291,7 @@
"additionaltotal": "Additional Total", "additionaltotal": "Additional Total",
"adjustmentrate": "Adjustment Rate", "adjustmentrate": "Adjustment Rate",
"adjustments": "Adjustments", "adjustments": "Adjustments",
"adminwarning": "Use the functionality on this page at your own risk. You are responsible for any and all changes to your data.",
"allocations": "Allocations", "allocations": "Allocations",
"alreadyclosed": "This job has already been closed.", "alreadyclosed": "This job has already been closed.",
"appointmentconfirmation": "Send confirmation to customer?", "appointmentconfirmation": "Send confirmation to customer?",
@@ -1312,7 +1344,8 @@
"waived": "Waived" "waived": "Waived"
}, },
"deleteconfirm": "Are you sure you want to delete this job? This cannot be undone. ", "deleteconfirm": "Are you sure you want to delete this job? This cannot be undone. ",
"deleteintake": "Delete Intake", "deletedelivery": "Delete Delivery Checklist",
"deleteintake": "Delete Intake Checklist",
"deliverchecklist": "Deliver Checklist", "deliverchecklist": "Deliver Checklist",
"difference": "Difference", "difference": "Difference",
"diskscan": "Scan Disk for Estimates", "diskscan": "Scan Disk for Estimates",
@@ -1441,11 +1474,11 @@
"name": "ImEX Online", "name": "ImEX Online",
"status": "System Status" "status": "System Status"
}, },
"slogan": "The future of shop management systems. " "slogan": "A whole new kind of shop management system."
}, },
"hero": { "hero": {
"button": "Learn More", "button": "Coming Soon",
"title": "Bringing the future to the collision repair process." "title": "A whole new kind of shop management system."
}, },
"labels": { "labels": {
"features": "Features", "features": "Features",
@@ -1805,6 +1838,7 @@
"depreciation": "Depreciation", "depreciation": "Depreciation",
"other": "Other", "other": "Other",
"ponumber": "PO Number", "ponumber": "PO Number",
"refnumber": "Reference Number",
"sendtype": "Send by", "sendtype": "Send by",
"state": "Province/State", "state": "Province/State",
"zip": "Postal Code/Zip" "zip": "Postal Code/Zip"
@@ -1830,6 +1864,8 @@
"invoice_total_payable": "Invoice (Total Payable)", "invoice_total_payable": "Invoice (Total Payable)",
"job_costing_ro": "Job Costing", "job_costing_ro": "Job Costing",
"job_notes": "Job Notes", "job_notes": "Job Notes",
"key_tag": "Key Tag",
"paint_grid": "Paint Grid",
"parts_label_single": "Parts Label - Single", "parts_label_single": "Parts Label - Single",
"parts_list": "Parts List", "parts_list": "Parts List",
"parts_order": "Parts Order Confirmation", "parts_order": "Parts Order Confirmation",
@@ -1844,6 +1880,7 @@
"qc_sheet": "Quality Control Sheet", "qc_sheet": "Quality Control Sheet",
"ro_totals": "RO Totals", "ro_totals": "RO Totals",
"ro_with_description": "RO Summary with Descriptions", "ro_with_description": "RO Summary with Descriptions",
"stolen_recovery_checklist": "Stolen Recovery Checklist",
"supplement_request": "Supplement Request", "supplement_request": "Supplement Request",
"thank_you_ro": "Thank You Letter", "thank_you_ro": "Thank You Letter",
"thirdpartypayer": "Third Party Payer", "thirdpartypayer": "Third Party Payer",
@@ -1945,12 +1982,15 @@
"bills": "Bills", "bills": "Bills",
"exportlogs": "Export Logs", "exportlogs": "Export Logs",
"jobs": "Jobs", "jobs": "Jobs",
"parts_orders": "Parts Orders",
"payments": "Payments", "payments": "Payments",
"scoreboard": "Scoreboard",
"timetickets": "Timetickets" "timetickets": "Timetickets"
}, },
"vendor": "Vendor" "vendor": "Vendor"
}, },
"templates": { "templates": {
"anticipated_revenue": "Anticipated Revenue",
"attendance_detail": "Attendance (All Employees)", "attendance_detail": "Attendance (All Employees)",
"attendance_employee": "Employee Attendance", "attendance_employee": "Employee Attendance",
"attendance_summary": "Attendance Summary (All Employees)", "attendance_summary": "Attendance Summary (All Employees)",
@@ -1960,30 +2000,39 @@
"export_payables": "Export Log - Payables", "export_payables": "Export Log - Payables",
"export_payments": "Export Log - Payments", "export_payments": "Export Log - Payments",
"export_receivables": "Export Log - Receivables", "export_receivables": "Export Log - Receivables",
"gsr_by_csr": "Gross Sales by CSR",
"gsr_by_delivery_date": "Gross Sales by Delivery Date", "gsr_by_delivery_date": "Gross Sales by Delivery Date",
"gsr_by_estimator": "Gross Sales by Estimator", "gsr_by_estimator": "Gross Sales by Estimator",
"gsr_by_exported_date": "Gross Sales by Export Date", "gsr_by_exported_date": "Gross Sales by Export Date",
"gsr_by_ins_co": "Gross Sales by Insurance Company'", "gsr_by_ins_co": "Gross Sales by Insurance Company",
"gsr_by_make": "Gross Sales by Vehicle Make", "gsr_by_make": "Gross Sales by Vehicle Make",
"gsr_by_referral": "Gross Sales by Referral Source", "gsr_by_referral": "Gross Sales by Referral Source",
"gsr_by_ro": "Gross Sales by RO", "gsr_by_ro": "Gross Sales by RO",
"gsr_labor_only": "Gross Sales - Labor Only", "gsr_labor_only": "Gross Sales - Labor Only",
"hours_sold_detail_closed": "Hours Sold Detail - Closed", "hours_sold_detail_closed": "Hours Sold Detail - Closed",
"hours_sold_detail_closed_csr": "Hours Sold Detail - Closed by CSR",
"hours_sold_detail_closed_ins_co": "Hours Sold Detail - Closed by Source", "hours_sold_detail_closed_ins_co": "Hours Sold Detail - Closed by Source",
"hours_sold_detail_open": "Hours Sold Detail - Open", "hours_sold_detail_open": "Hours Sold Detail - Open",
"hours_sold_detail_open_csr": "Hours Sold Detail - Open by CSR",
"hours_sold_detail_open_ins_co": "Hours Sold Detail - Open by Source", "hours_sold_detail_open_ins_co": "Hours Sold Detail - Open by Source",
"hours_sold_summary_closed": "Hours Sold Summary - Closed", "hours_sold_summary_closed": "Hours Sold Summary - Closed",
"hours_sold_summary_closed_csr": "Hours Sold Summary - Closed by CSR",
"hours_sold_summary_closed_ins_co": "Hours Sold Summary - Closed by Source", "hours_sold_summary_closed_ins_co": "Hours Sold Summary - Closed by Source",
"hours_sold_summary_open": "Hours Sold Summary - Open", "hours_sold_summary_open": "Hours Sold Summary - Open",
"hours_sold_summary_open_csr": "Hours Sold Summary - Open CSR",
"hours_sold_summary_open_ins_co": "Hours Sold Summary - Open by Source", "hours_sold_summary_open_ins_co": "Hours Sold Summary - Open by Source",
"job_costing_ro_csr": "Job Costing by CSR",
"job_costing_ro_date_detail": "Job Costing by RO - Detail", "job_costing_ro_date_detail": "Job Costing by RO - Detail",
"job_costing_ro_date_summary": "Job Costing by RO - Summary", "job_costing_ro_date_summary": "Job Costing by RO - Summary",
"job_costing_ro_estimator": "Job Costing by Estimator", "job_costing_ro_estimator": "Job Costing by Estimator",
"job_costing_ro_ins_co": "Job Costing by RO Source", "job_costing_ro_ins_co": "Job Costing by RO Source",
"lag_time": "Lag Time",
"open_orders": "Open Orders by Date", "open_orders": "Open Orders by Date",
"open_orders_csr": "Open Orders by CSR",
"open_orders_estimator": "Open Orders by Estimator", "open_orders_estimator": "Open Orders by Estimator",
"open_orders_ins_co": "Open Orders by Insurance Company", "open_orders_ins_co": "Open Orders by Insurance Company",
"parts_backorder": "Backordered Parts", "parts_backorder": "Backordered Parts",
"parts_not_recieved": "Parts Not Received",
"payments_by_date": "Payments by Date", "payments_by_date": "Payments by Date",
"payments_by_date_type": "Payments by Date and Type", "payments_by_date_type": "Payments by Date and Type",
"production_by_csr": "Production by CSR", "production_by_csr": "Production by CSR",
@@ -2000,6 +2049,8 @@
"purchases_grouped_by_vendor_detailed": "Purchases Grouped by Vendor - Detailed", "purchases_grouped_by_vendor_detailed": "Purchases Grouped by Vendor - Detailed",
"purchases_grouped_by_vendor_summary": "Purchases Grouped by Vendor - Summary", "purchases_grouped_by_vendor_summary": "Purchases Grouped by Vendor - Summary",
"schedule": "Appointment Schedule", "schedule": "Appointment Schedule",
"scoreboard_detail": "Scoreboard Detail",
"scoreboard_summary": "Scoreboard Summary",
"supplement_ratio_ins_co": "Supplement Ratio by Source", "supplement_ratio_ins_co": "Supplement Ratio by Source",
"thank_you_date": "Thank You Letters", "thank_you_date": "Thank You Letters",
"timetickets": "Time Tickets", "timetickets": "Time Tickets",
@@ -2206,6 +2257,7 @@
}, },
"user": { "user": {
"actions": { "actions": {
"changepassword": "Change Password",
"signout": "Sign Out", "signout": "Sign Out",
"updateprofile": "Update Profile" "updateprofile": "Update Profile"
}, },
@@ -2220,6 +2272,9 @@
}, },
"labels": { "labels": {
"actions": "Actions" "actions": "Actions"
},
"successess": {
"passwordchanged": "Password changed successfully. "
} }
}, },
"vehicles": { "vehicles": {
@@ -2282,7 +2337,7 @@
"city": "City", "city": "City",
"cost_center": "Cost Center", "cost_center": "Cost Center",
"country": "Country", "country": "Country",
"discount": "Discount %", "discount": "Discount % (as decimal)",
"display_name": "Display Name", "display_name": "Display Name",
"due_date": "Payment Due Date", "due_date": "Payment Due Date",
"email": "Contact Email", "email": "Contact Email",

View File

@@ -37,6 +37,7 @@
"fields": { "fields": {
"alt_transport": "", "alt_transport": "",
"color": "", "color": "",
"note": "",
"time": "", "time": "",
"title": "Título" "title": "Título"
}, },
@@ -52,6 +53,7 @@
"nocompletingjobs": "", "nocompletingjobs": "",
"nodateselected": "No se ha seleccionado ninguna fecha.", "nodateselected": "No se ha seleccionado ninguna fecha.",
"priorappointments": "Nombramientos previos", "priorappointments": "Nombramientos previos",
"reminder": "",
"scheduledfor": "Cita programada para:", "scheduledfor": "Cita programada para:",
"smartscheduling": "", "smartscheduling": "",
"suggesteddates": "" "suggesteddates": ""
@@ -82,6 +84,24 @@
"values": "" "values": ""
} }
}, },
"audit_trail": {
"messages": {
"billposted": "",
"billupdated": "",
"jobassignmentchange": "",
"jobassignmentremoved": "",
"jobchecklist": "",
"jobconverted": "",
"jobfieldchanged": "",
"jobimported": "",
"jobinproductionchange": "",
"jobmodifylbradj": "",
"jobspartsorder": "",
"jobspartsreturn": "",
"jobstatuschange": "",
"jobsupplement": ""
}
},
"billlines": { "billlines": {
"actions": { "actions": {
"newline": "" "newline": ""
@@ -199,6 +219,7 @@
"label": "" "label": ""
}, },
"appt_length": "", "appt_length": "",
"attach_pdf_to_email": "",
"bill_federal_tax_rate": "", "bill_federal_tax_rate": "",
"bill_local_tax_rate": "", "bill_local_tax_rate": "",
"bill_state_tax_rate": "", "bill_state_tax_rate": "",
@@ -425,6 +446,7 @@
"production_statuses": "" "production_statuses": ""
}, },
"target_touchtime": "", "target_touchtime": "",
"tt_allow_post_to_invoiced": "",
"use_fippa": "", "use_fippa": "",
"website": "", "website": "",
"zip_post": "" "zip_post": ""
@@ -490,6 +512,7 @@
}, },
"labels": { "labels": {
"addtoproduction": "", "addtoproduction": "",
"allow_text_message": "",
"checklist": "", "checklist": "",
"printpack": "", "printpack": "",
"removefromproduction": "" "removefromproduction": ""
@@ -742,6 +765,7 @@
"attachments": "", "attachments": "",
"documents": "", "documents": "",
"generatingemail": "", "generatingemail": "",
"pdfcopywillbeattached": "",
"preview": "" "preview": ""
}, },
"successes": { "successes": {
@@ -855,6 +879,7 @@
"message": "", "message": "",
"monday": "", "monday": "",
"na": "N / A", "na": "N / A",
"newpassword": "",
"no": "", "no": "",
"nointernet": "", "nointernet": "",
"nointernet_sub": "", "nointernet_sub": "",
@@ -875,6 +900,7 @@
"sendagain": "", "sendagain": "",
"sendby": "", "sendby": "",
"signin": "", "signin": "",
"sms": "",
"sub_status": { "sub_status": {
"expired": "" "expired": ""
}, },
@@ -1034,6 +1060,9 @@
"intake": "", "intake": "",
"manualnew": "", "manualnew": "",
"mark": "", "mark": "",
"markasexported": "",
"markpstexempt": "",
"markpstexemptconfirm": "",
"postbills": "Contabilizar facturas", "postbills": "Contabilizar facturas",
"printCenter": "Centro de impresión", "printCenter": "Centro de impresión",
"recalculate": "", "recalculate": "",
@@ -1042,6 +1071,7 @@
"schedule": "Programar", "schedule": "Programar",
"sendcsi": "", "sendcsi": "",
"sync": "", "sync": "",
"uninvoice": "",
"unvoid": "", "unvoid": "",
"viewchecklist": "", "viewchecklist": "",
"viewdetail": "" "viewdetail": ""
@@ -1223,6 +1253,7 @@
"servicecar": "Auto de servicio", "servicecar": "Auto de servicio",
"servicing_dealer": "Distribuidor de servicio", "servicing_dealer": "Distribuidor de servicio",
"servicing_dealer_contact": "Servicio Contacto con el concesionario", "servicing_dealer_contact": "Servicio Contacto con el concesionario",
"special_coverage_policy": "Política de cobertura especial",
"specialcoveragepolicy": "Política de cobertura especial", "specialcoveragepolicy": "Política de cobertura especial",
"state_tax_rate": "", "state_tax_rate": "",
"status": "Estado del trabajo", "status": "Estado del trabajo",
@@ -1260,6 +1291,7 @@
"additionaltotal": "", "additionaltotal": "",
"adjustmentrate": "", "adjustmentrate": "",
"adjustments": "", "adjustments": "",
"adminwarning": "",
"allocations": "", "allocations": "",
"alreadyclosed": "", "alreadyclosed": "",
"appointmentconfirmation": "¿Enviar confirmación al cliente?", "appointmentconfirmation": "¿Enviar confirmación al cliente?",
@@ -1312,6 +1344,7 @@
"waived": "" "waived": ""
}, },
"deleteconfirm": "", "deleteconfirm": "",
"deletedelivery": "",
"deleteintake": "", "deleteintake": "",
"deliverchecklist": "", "deliverchecklist": "",
"difference": "", "difference": "",
@@ -1805,6 +1838,7 @@
"depreciation": "", "depreciation": "",
"other": "", "other": "",
"ponumber": "", "ponumber": "",
"refnumber": "",
"sendtype": "", "sendtype": "",
"state": "", "state": "",
"zip": "" "zip": ""
@@ -1830,6 +1864,8 @@
"invoice_total_payable": "", "invoice_total_payable": "",
"job_costing_ro": "", "job_costing_ro": "",
"job_notes": "", "job_notes": "",
"key_tag": "",
"paint_grid": "",
"parts_label_single": "", "parts_label_single": "",
"parts_list": "", "parts_list": "",
"parts_order": "", "parts_order": "",
@@ -1844,6 +1880,7 @@
"qc_sheet": "", "qc_sheet": "",
"ro_totals": "", "ro_totals": "",
"ro_with_description": "", "ro_with_description": "",
"stolen_recovery_checklist": "",
"supplement_request": "", "supplement_request": "",
"thank_you_ro": "", "thank_you_ro": "",
"thirdpartypayer": "", "thirdpartypayer": "",
@@ -1945,12 +1982,15 @@
"bills": "", "bills": "",
"exportlogs": "", "exportlogs": "",
"jobs": "", "jobs": "",
"parts_orders": "",
"payments": "", "payments": "",
"scoreboard": "",
"timetickets": "" "timetickets": ""
}, },
"vendor": "" "vendor": ""
}, },
"templates": { "templates": {
"anticipated_revenue": "",
"attendance_detail": "", "attendance_detail": "",
"attendance_employee": "", "attendance_employee": "",
"attendance_summary": "", "attendance_summary": "",
@@ -1960,6 +2000,7 @@
"export_payables": "", "export_payables": "",
"export_payments": "", "export_payments": "",
"export_receivables": "", "export_receivables": "",
"gsr_by_csr": "",
"gsr_by_delivery_date": "", "gsr_by_delivery_date": "",
"gsr_by_estimator": "", "gsr_by_estimator": "",
"gsr_by_exported_date": "", "gsr_by_exported_date": "",
@@ -1969,21 +2010,29 @@
"gsr_by_ro": "", "gsr_by_ro": "",
"gsr_labor_only": "", "gsr_labor_only": "",
"hours_sold_detail_closed": "", "hours_sold_detail_closed": "",
"hours_sold_detail_closed_csr": "",
"hours_sold_detail_closed_ins_co": "", "hours_sold_detail_closed_ins_co": "",
"hours_sold_detail_open": "", "hours_sold_detail_open": "",
"hours_sold_detail_open_csr": "",
"hours_sold_detail_open_ins_co": "", "hours_sold_detail_open_ins_co": "",
"hours_sold_summary_closed": "", "hours_sold_summary_closed": "",
"hours_sold_summary_closed_csr": "",
"hours_sold_summary_closed_ins_co": "", "hours_sold_summary_closed_ins_co": "",
"hours_sold_summary_open": "", "hours_sold_summary_open": "",
"hours_sold_summary_open_csr": "",
"hours_sold_summary_open_ins_co": "", "hours_sold_summary_open_ins_co": "",
"job_costing_ro_csr": "",
"job_costing_ro_date_detail": "", "job_costing_ro_date_detail": "",
"job_costing_ro_date_summary": "", "job_costing_ro_date_summary": "",
"job_costing_ro_estimator": "", "job_costing_ro_estimator": "",
"job_costing_ro_ins_co": "", "job_costing_ro_ins_co": "",
"lag_time": "",
"open_orders": "", "open_orders": "",
"open_orders_csr": "",
"open_orders_estimator": "", "open_orders_estimator": "",
"open_orders_ins_co": "", "open_orders_ins_co": "",
"parts_backorder": "", "parts_backorder": "",
"parts_not_recieved": "",
"payments_by_date": "", "payments_by_date": "",
"payments_by_date_type": "", "payments_by_date_type": "",
"production_by_csr": "", "production_by_csr": "",
@@ -2000,6 +2049,8 @@
"purchases_grouped_by_vendor_detailed": "", "purchases_grouped_by_vendor_detailed": "",
"purchases_grouped_by_vendor_summary": "", "purchases_grouped_by_vendor_summary": "",
"schedule": "", "schedule": "",
"scoreboard_detail": "",
"scoreboard_summary": "",
"supplement_ratio_ins_co": "", "supplement_ratio_ins_co": "",
"thank_you_date": "", "thank_you_date": "",
"timetickets": "", "timetickets": "",
@@ -2206,6 +2257,7 @@
}, },
"user": { "user": {
"actions": { "actions": {
"changepassword": "",
"signout": "desconectar", "signout": "desconectar",
"updateprofile": "Actualización del perfil" "updateprofile": "Actualización del perfil"
}, },
@@ -2220,6 +2272,9 @@
}, },
"labels": { "labels": {
"actions": "" "actions": ""
},
"successess": {
"passwordchanged": ""
} }
}, },
"vehicles": { "vehicles": {

View File

@@ -37,6 +37,7 @@
"fields": { "fields": {
"alt_transport": "", "alt_transport": "",
"color": "", "color": "",
"note": "",
"time": "", "time": "",
"title": "Titre" "title": "Titre"
}, },
@@ -52,6 +53,7 @@
"nocompletingjobs": "", "nocompletingjobs": "",
"nodateselected": "Aucune date n'a été sélectionnée.", "nodateselected": "Aucune date n'a été sélectionnée.",
"priorappointments": "Rendez-vous précédents", "priorappointments": "Rendez-vous précédents",
"reminder": "",
"scheduledfor": "Rendez-vous prévu pour:", "scheduledfor": "Rendez-vous prévu pour:",
"smartscheduling": "", "smartscheduling": "",
"suggesteddates": "" "suggesteddates": ""
@@ -82,6 +84,24 @@
"values": "" "values": ""
} }
}, },
"audit_trail": {
"messages": {
"billposted": "",
"billupdated": "",
"jobassignmentchange": "",
"jobassignmentremoved": "",
"jobchecklist": "",
"jobconverted": "",
"jobfieldchanged": "",
"jobimported": "",
"jobinproductionchange": "",
"jobmodifylbradj": "",
"jobspartsorder": "",
"jobspartsreturn": "",
"jobstatuschange": "",
"jobsupplement": ""
}
},
"billlines": { "billlines": {
"actions": { "actions": {
"newline": "" "newline": ""
@@ -199,6 +219,7 @@
"label": "" "label": ""
}, },
"appt_length": "", "appt_length": "",
"attach_pdf_to_email": "",
"bill_federal_tax_rate": "", "bill_federal_tax_rate": "",
"bill_local_tax_rate": "", "bill_local_tax_rate": "",
"bill_state_tax_rate": "", "bill_state_tax_rate": "",
@@ -425,6 +446,7 @@
"production_statuses": "" "production_statuses": ""
}, },
"target_touchtime": "", "target_touchtime": "",
"tt_allow_post_to_invoiced": "",
"use_fippa": "", "use_fippa": "",
"website": "", "website": "",
"zip_post": "" "zip_post": ""
@@ -490,6 +512,7 @@
}, },
"labels": { "labels": {
"addtoproduction": "", "addtoproduction": "",
"allow_text_message": "",
"checklist": "", "checklist": "",
"printpack": "", "printpack": "",
"removefromproduction": "" "removefromproduction": ""
@@ -742,6 +765,7 @@
"attachments": "", "attachments": "",
"documents": "", "documents": "",
"generatingemail": "", "generatingemail": "",
"pdfcopywillbeattached": "",
"preview": "" "preview": ""
}, },
"successes": { "successes": {
@@ -855,6 +879,7 @@
"message": "", "message": "",
"monday": "", "monday": "",
"na": "N / A", "na": "N / A",
"newpassword": "",
"no": "", "no": "",
"nointernet": "", "nointernet": "",
"nointernet_sub": "", "nointernet_sub": "",
@@ -875,6 +900,7 @@
"sendagain": "", "sendagain": "",
"sendby": "", "sendby": "",
"signin": "", "signin": "",
"sms": "",
"sub_status": { "sub_status": {
"expired": "" "expired": ""
}, },
@@ -1034,6 +1060,9 @@
"intake": "", "intake": "",
"manualnew": "", "manualnew": "",
"mark": "", "mark": "",
"markasexported": "",
"markpstexempt": "",
"markpstexemptconfirm": "",
"postbills": "Poster des factures", "postbills": "Poster des factures",
"printCenter": "Centre d'impression", "printCenter": "Centre d'impression",
"recalculate": "", "recalculate": "",
@@ -1042,6 +1071,7 @@
"schedule": "Programme", "schedule": "Programme",
"sendcsi": "", "sendcsi": "",
"sync": "", "sync": "",
"uninvoice": "",
"unvoid": "", "unvoid": "",
"viewchecklist": "", "viewchecklist": "",
"viewdetail": "" "viewdetail": ""
@@ -1223,6 +1253,7 @@
"servicecar": "Voiture de service", "servicecar": "Voiture de service",
"servicing_dealer": "Concessionnaire", "servicing_dealer": "Concessionnaire",
"servicing_dealer_contact": "Contacter le concessionnaire", "servicing_dealer_contact": "Contacter le concessionnaire",
"special_coverage_policy": "Politique de couverture spéciale",
"specialcoveragepolicy": "Politique de couverture spéciale", "specialcoveragepolicy": "Politique de couverture spéciale",
"state_tax_rate": "", "state_tax_rate": "",
"status": "Statut de l'emploi", "status": "Statut de l'emploi",
@@ -1260,6 +1291,7 @@
"additionaltotal": "", "additionaltotal": "",
"adjustmentrate": "", "adjustmentrate": "",
"adjustments": "", "adjustments": "",
"adminwarning": "",
"allocations": "", "allocations": "",
"alreadyclosed": "", "alreadyclosed": "",
"appointmentconfirmation": "Envoyer une confirmation au client?", "appointmentconfirmation": "Envoyer une confirmation au client?",
@@ -1312,6 +1344,7 @@
"waived": "" "waived": ""
}, },
"deleteconfirm": "", "deleteconfirm": "",
"deletedelivery": "",
"deleteintake": "", "deleteintake": "",
"deliverchecklist": "", "deliverchecklist": "",
"difference": "", "difference": "",
@@ -1805,6 +1838,7 @@
"depreciation": "", "depreciation": "",
"other": "", "other": "",
"ponumber": "", "ponumber": "",
"refnumber": "",
"sendtype": "", "sendtype": "",
"state": "", "state": "",
"zip": "" "zip": ""
@@ -1830,6 +1864,8 @@
"invoice_total_payable": "", "invoice_total_payable": "",
"job_costing_ro": "", "job_costing_ro": "",
"job_notes": "", "job_notes": "",
"key_tag": "",
"paint_grid": "",
"parts_label_single": "", "parts_label_single": "",
"parts_list": "", "parts_list": "",
"parts_order": "", "parts_order": "",
@@ -1844,6 +1880,7 @@
"qc_sheet": "", "qc_sheet": "",
"ro_totals": "", "ro_totals": "",
"ro_with_description": "", "ro_with_description": "",
"stolen_recovery_checklist": "",
"supplement_request": "", "supplement_request": "",
"thank_you_ro": "", "thank_you_ro": "",
"thirdpartypayer": "", "thirdpartypayer": "",
@@ -1945,12 +1982,15 @@
"bills": "", "bills": "",
"exportlogs": "", "exportlogs": "",
"jobs": "", "jobs": "",
"parts_orders": "",
"payments": "", "payments": "",
"scoreboard": "",
"timetickets": "" "timetickets": ""
}, },
"vendor": "" "vendor": ""
}, },
"templates": { "templates": {
"anticipated_revenue": "",
"attendance_detail": "", "attendance_detail": "",
"attendance_employee": "", "attendance_employee": "",
"attendance_summary": "", "attendance_summary": "",
@@ -1960,6 +2000,7 @@
"export_payables": "", "export_payables": "",
"export_payments": "", "export_payments": "",
"export_receivables": "", "export_receivables": "",
"gsr_by_csr": "",
"gsr_by_delivery_date": "", "gsr_by_delivery_date": "",
"gsr_by_estimator": "", "gsr_by_estimator": "",
"gsr_by_exported_date": "", "gsr_by_exported_date": "",
@@ -1969,21 +2010,29 @@
"gsr_by_ro": "", "gsr_by_ro": "",
"gsr_labor_only": "", "gsr_labor_only": "",
"hours_sold_detail_closed": "", "hours_sold_detail_closed": "",
"hours_sold_detail_closed_csr": "",
"hours_sold_detail_closed_ins_co": "", "hours_sold_detail_closed_ins_co": "",
"hours_sold_detail_open": "", "hours_sold_detail_open": "",
"hours_sold_detail_open_csr": "",
"hours_sold_detail_open_ins_co": "", "hours_sold_detail_open_ins_co": "",
"hours_sold_summary_closed": "", "hours_sold_summary_closed": "",
"hours_sold_summary_closed_csr": "",
"hours_sold_summary_closed_ins_co": "", "hours_sold_summary_closed_ins_co": "",
"hours_sold_summary_open": "", "hours_sold_summary_open": "",
"hours_sold_summary_open_csr": "",
"hours_sold_summary_open_ins_co": "", "hours_sold_summary_open_ins_co": "",
"job_costing_ro_csr": "",
"job_costing_ro_date_detail": "", "job_costing_ro_date_detail": "",
"job_costing_ro_date_summary": "", "job_costing_ro_date_summary": "",
"job_costing_ro_estimator": "", "job_costing_ro_estimator": "",
"job_costing_ro_ins_co": "", "job_costing_ro_ins_co": "",
"lag_time": "",
"open_orders": "", "open_orders": "",
"open_orders_csr": "",
"open_orders_estimator": "", "open_orders_estimator": "",
"open_orders_ins_co": "", "open_orders_ins_co": "",
"parts_backorder": "", "parts_backorder": "",
"parts_not_recieved": "",
"payments_by_date": "", "payments_by_date": "",
"payments_by_date_type": "", "payments_by_date_type": "",
"production_by_csr": "", "production_by_csr": "",
@@ -2000,6 +2049,8 @@
"purchases_grouped_by_vendor_detailed": "", "purchases_grouped_by_vendor_detailed": "",
"purchases_grouped_by_vendor_summary": "", "purchases_grouped_by_vendor_summary": "",
"schedule": "", "schedule": "",
"scoreboard_detail": "",
"scoreboard_summary": "",
"supplement_ratio_ins_co": "", "supplement_ratio_ins_co": "",
"thank_you_date": "", "thank_you_date": "",
"timetickets": "", "timetickets": "",
@@ -2206,6 +2257,7 @@
}, },
"user": { "user": {
"actions": { "actions": {
"changepassword": "",
"signout": "Déconnexion", "signout": "Déconnexion",
"updateprofile": "Mettre à jour le profil" "updateprofile": "Mettre à jour le profil"
}, },
@@ -2220,6 +2272,9 @@
}, },
"labels": { "labels": {
"actions": "" "actions": ""
},
"successess": {
"passwordchanged": ""
} }
}, },
"vehicles": { "vehicles": {

View File

@@ -0,0 +1,31 @@
import i18n from "i18next";
const AuditTrailMapping = {
jobstatuschange: (status) =>
i18n.t("audit_trail.messages.jobstatuschange", { status }),
jobsupplement: () => i18n.t("audit_trail.messages.jobsupplement"),
jobimported: () => i18n.t("audit_trail.messages.jobimported"),
jobconverted: (ro_number) =>
i18n.t("audit_trail.messages.jobconverted", { ro_number }),
jobfieldchange: (field, value) =>
i18n.t("audit_trail.messages.jobfieldchanged", { field, value }),
jobspartsorder: (order_number) =>
i18n.t("audit_trail.messages.jobspartsorder", { order_number }),
jobspartsreturn: (order_number) =>
i18n.t("audit_trail.messages.jobspartsreturn", { order_number }),
jobmodifylbradj: () => i18n.t("audit_trail.messages.jobmodifylbradj", {}),
billposted: (invoice_number) =>
i18n.t("audit_trail.messages.billposted", { invoice_number }),
billupdated: (invoice_number) =>
i18n.t("audit_trail.messages.billupdated", { invoice_number }),
jobassignmentchange: (operation, name) =>
i18n.t("audit_trail.messages.jobassignmentchange", { operation, name }),
jobassignmentremoved: (operation) =>
i18n.t("audit_trail.messages.jobassignmentremoved", { operation }),
jobinproductionchange: (inproduction) =>
i18n.t("audit_trail.messages.jobinproductionchange", { inproduction }),
jobchecklist: (type, inproduction, status) =>
i18n.t("audit_trail.messages.jobchecklist", { type, inproduction, status }),
};
export default AuditTrailMapping;

View File

@@ -6,7 +6,6 @@ import { WebSocketLink } from "@apollo/client/link/ws";
import { getMainDefinition } from "@apollo/client/utilities"; import { getMainDefinition } from "@apollo/client/utilities";
//import { split } from "apollo-link"; //import { split } from "apollo-link";
import apolloLogger from "apollo-link-logger"; import apolloLogger from "apollo-link-logger";
import axios from "axios";
import { auth } from "../firebase/firebase.utils"; import { auth } from "../firebase/firebase.utils";
import errorLink from "../graphql/apollo-error-handling"; import errorLink from "../graphql/apollo-error-handling";
@@ -48,7 +47,7 @@ const roundTripLink = new ApolloLink((operation, forward) => {
}); });
const TrackExecutionTime = async (operationName, time) => { const TrackExecutionTime = async (operationName, time) => {
await axios.post("/ioevent", { operationName, time, dbevent: true }); //await axios.post("/ioevent", { operationName, time, dbevent: true });
}; };
const subscriptionMiddleware = { const subscriptionMiddleware = {

View File

@@ -8,6 +8,7 @@ import { setEmailOptions } from "../redux/email/email.actions";
import { store } from "../redux/store"; import { store } from "../redux/store";
import client from "../utils/GraphQLClient"; import client from "../utils/GraphQLClient";
import { TemplateList } from "./TemplateConstants"; import { TemplateList } from "./TemplateConstants";
import _ from "lodash";
const server = process.env.REACT_APP_REPORTS_SERVER_URL; const server = process.env.REACT_APP_REPORTS_SERVER_URL;
jsreport.serverUrl = server; jsreport.serverUrl = server;
@@ -39,8 +40,10 @@ export default async function RenderTemplate(
offset: moment().utcOffset(), offset: moment().utcOffset(),
}, },
}; };
try { try {
const render = await jsreport.renderAsync(reportRequest); const render = await jsreport.renderAsync(reportRequest);
if (!renderAsHtml) { if (!renderAsHtml) {
render.download( render.download(
(Templates[templateObject.name] && (Templates[templateObject.name] &&
@@ -48,8 +51,21 @@ export default async function RenderTemplate(
"" ""
); );
} else { } else {
let pdf;
if (bodyshop.attach_pdf_to_email) {
const pdfRequest = _.cloneDeep(reportRequest);
pdfRequest.template.recipe = "chrome-pdf";
const pdfRender = await jsreport.renderAsync(pdfRequest);
pdf = pdfRender.toDataURI();
}
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
resolve(render.toString()); resolve({
pdf,
filename:
Templates[templateObject.name] &&
Templates[templateObject.name].title,
html: render.toString(),
});
}); });
} }
} catch (error) { } catch (error) {

View File

@@ -69,6 +69,14 @@ export const TemplateList = (type, context) => {
disabled: false, disabled: false,
group: "pre", group: "pre",
}, },
stolen_recovery_checklist: {
title: i18n.t("printcenter.jobs.stolen_recovery_checklist"),
description: "All Jobs Notes",
subject: i18n.t("printcenter.jobs.stolen_recovery_checklist"),
key: "stolen_recovery_checklist",
disabled: false,
group: "pre",
},
vehicle_check_in: { vehicle_check_in: {
title: i18n.t("printcenter.jobs.vehicle_check_in"), title: i18n.t("printcenter.jobs.vehicle_check_in"),
description: "All Jobs Notes", description: "All Jobs Notes",
@@ -150,6 +158,22 @@ export const TemplateList = (type, context) => {
disabled: false, disabled: false,
group: "ro", group: "ro",
}, },
key_tag: {
title: i18n.t("printcenter.jobs.key_tag"),
description: "All Jobs Notes",
subject: i18n.t("printcenter.jobs.key_tag"),
key: "key_tag",
disabled: false,
group: "ro",
},
paint_grid: {
title: i18n.t("printcenter.jobs.paint_grid"),
description: "All Jobs Notes",
subject: i18n.t("printcenter.jobs.paint_grid"),
key: "paint_grid",
disabled: false,
group: "ro",
},
worksheet_by_line_number: { worksheet_by_line_number: {
title: i18n.t("printcenter.jobs.worksheet_by_line_number"), title: i18n.t("printcenter.jobs.worksheet_by_line_number"),
description: "All Jobs Notes", description: "All Jobs Notes",
@@ -728,6 +752,67 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_open"), field: i18n.t("jobs.fields.date_open"),
}, },
}, },
hours_sold_detail_closed_csr: {
title: i18n.t(
"reportcenter.templates.hours_sold_detail_closed_csr"
),
description: "",
subject: i18n.t(
"reportcenter.templates.hours_sold_detail_closed_csr"
),
key: "hours_sold_detail_closed_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
},
hours_sold_detail_open_csr: {
title: i18n.t("reportcenter.templates.hours_sold_detail_open_csr"),
description: "",
subject: i18n.t(
"reportcenter.templates.hours_sold_detail_open_csr"
),
key: "hours_sold_detail_open_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
},
hours_sold_summary_closed_csr: {
title: i18n.t(
"reportcenter.templates.hours_sold_summary_closed_csr"
),
description: "",
subject: i18n.t(
"reportcenter.templates.hours_sold_summary_closed_csr"
),
key: "hours_sold_summary_closed_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
},
hours_sold_summary_open_csr: {
title: i18n.t("reportcenter.templates.hours_sold_summary_open_csr"),
description: "",
subject: i18n.t(
"reportcenter.templates.hours_sold_summary_open_csr"
),
key: "hours_sold_summary_open_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
},
estimator_detail: { estimator_detail: {
title: i18n.t("reportcenter.templates.estimator_detail"), title: i18n.t("reportcenter.templates.estimator_detail"),
description: "", description: "",
@@ -790,6 +875,18 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_invoiced"), field: i18n.t("jobs.fields.date_invoiced"),
}, },
}, },
job_costing_ro_csr: {
title: i18n.t("reportcenter.templates.job_costing_ro_csr"),
description: "",
subject: i18n.t("reportcenter.templates.job_costing_ro_csr"),
key: "job_costing_ro_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
},
job_costing_ro_ins_co: { job_costing_ro_ins_co: {
title: i18n.t("reportcenter.templates.job_costing_ro_ins_co"), title: i18n.t("reportcenter.templates.job_costing_ro_ins_co"),
description: "", description: "",
@@ -840,6 +937,18 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_open"), field: i18n.t("jobs.fields.date_open"),
}, },
}, },
gsr_by_csr: {
title: i18n.t("reportcenter.templates.gsr_by_csr"),
description: "",
subject: i18n.t("reportcenter.templates.gsr_by_csr"),
key: "gsr_by_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
},
gsr_by_make: { gsr_by_make: {
title: i18n.t("reportcenter.templates.gsr_by_make"), title: i18n.t("reportcenter.templates.gsr_by_make"),
description: "", description: "",
@@ -949,6 +1058,18 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_open"), field: i18n.t("jobs.fields.date_open"),
}, },
}, },
open_orders_csr: {
title: i18n.t("reportcenter.templates.open_orders_csr"),
description: "",
subject: i18n.t("reportcenter.templates.open_orders_csr"),
key: "open_orders_csr",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open"),
},
},
open_orders_estimator: { open_orders_estimator: {
title: i18n.t("reportcenter.templates.open_orders_estimator"), title: i18n.t("reportcenter.templates.open_orders_estimator"),
description: "", description: "",
@@ -1069,6 +1190,66 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_open"), field: i18n.t("jobs.fields.date_open"),
}, },
}, },
lag_time: {
title: i18n.t("reportcenter.templates.lag_time"),
description: "",
subject: i18n.t("reportcenter.templates.lag_time"),
key: "lag_time",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_invoiced"),
},
},
parts_not_recieved: {
title: i18n.t("reportcenter.templates.parts_not_recieved"),
description: "",
subject: i18n.t("reportcenter.templates.parts_not_recieved"),
key: "parts_not_recieved",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.parts_orders"),
field: i18n.t("parts_orders.fields.order_date"),
},
},
scoreboard_detail: {
title: i18n.t("reportcenter.templates.scoreboard_detail"),
description: "",
subject: i18n.t("reportcenter.templates.scoreboard_detail"),
key: "scoreboard_detail",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.scoreboard"),
field: i18n.t("scoreboard.fields.date"),
},
},
scoreboard_summary: {
title: i18n.t("reportcenter.templates.scoreboard_summary"),
description: "",
subject: i18n.t("reportcenter.templates.scoreboard_summary"),
key: "scoreboard_summary",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.scoreboard"),
field: i18n.t("scoreboard.fields.date"),
},
},
anticipated_revenue: {
title: i18n.t("reportcenter.templates.anticipated_revenue"),
description: "",
subject: i18n.t("reportcenter.templates.anticipated_revenue"),
key: "anticipated_revenue",
//idtype: "vendor",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.scheduled_completion"), // Also date invoice.
},
},
} }
: {}), : {}),
...(!type || type === "courtesycarcontract" ...(!type || type === "courtesycarcontract"

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -0,0 +1,11 @@
- args:
cascade: false
read_only: false
sql: "CREATE OR REPLACE FUNCTION public.search_payments(search text)\n RETURNS
SETOF payments\n LANGUAGE plpgsql\n STABLE\nAS $function$\n\nBEGIN\n if search
= '' then\n return query select * from payments ;\n else \n return query
SELECT\n p.*\nFROM\n payments p, jobs j\nWHERE\np.jobid = j.id AND\n(\nsearch
<% p.paymentnum OR\nsearch <% j.ownr_fn OR\nsearch <% j.ownr_ln OR\nsearch <%
j.ownr_co_nm OR\nsearch <% j.ro_number OR\n search <% (p.payer) OR\n search
<% (p.transactionid) OR\n search <% (p.memo));\n end if;\n\n\tEND\n$function$;"
type: run_sql

View File

@@ -0,0 +1,6 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."vehicles" add constraint "vehicles_v_vin_shopid_key"
unique ("v_vin", "shopid");
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: alter table "public"."vehicles" drop constraint "vehicles_v_vin_shopid_key";
type: run_sql

View File

@@ -0,0 +1,5 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "attach_pdf_to_email";
type: run_sql

View File

@@ -0,0 +1,6 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "attach_pdf_to_email" boolean
NOT NULL DEFAULT False;
type: run_sql

View File

@@ -0,0 +1,87 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- accountingconfig
- address1
- address2
- appt_alt_transport
- appt_colors
- appt_length
- bill_tax_rates
- cdk_dealerid
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- enforce_referral
- features
- federal_tax_id
- id
- imexshopid
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- jc_hourly_rates
- jobsizelimit
- logo_img_path
- md_categories
- md_ccc_rates
- md_classes
- md_hour_split
- md_ins_cos
- md_jobline_presets
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- md_rbac
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- messagingservicesid
- phone
- prodtargethrs
- production_config
- region_config
- schedule_end_time
- schedule_start_time
- scoreboard_target
- shopname
- shoprates
- speedprint
- ssbuckets
- state
- state_tax_id
- stripe_acct_id
- sub_status
- target_touchtime
- template_header
- textid
- updated_at
- use_fippa
- website
- workingdays
- zip_post
computed_fields: []
filter:
associations:
user:
authid:
_eq: X-Hasura-User-Id
role: user
table:
name: bodyshops
schema: public
type: create_select_permission

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