Compare commits

...

132 Commits

Author SHA1 Message Date
Patrick Fic
edb58ebc81 IO-1330 Resolve postal code on qb export. 2021-08-24 16:39:42 -07:00
Patrick Fic
8d9507dce1 IO-539 Resolve tax import for SGI. 2021-08-24 16:32:05 -07:00
Patrick Fic
1c473c95a2 IO-1330 Update QB Information 2021-08-23 09:50:01 -07:00
Patrick Fic
44c17bd7a2 Remove tracker assist for CI. 2021-08-23 08:43:14 -07:00
Patrick Fic
5213b0b315 Merge branch 'test' into release/2021-08-27 2021-08-23 08:34:25 -07:00
Patrick Fic
0541167cc5 Addition of Open Replay Tracker 2021-08-20 14:38:33 -07:00
Patrick Fic
26e47ff203 Merged in release/2021-08-20 (pull request #183)
IO-1327 Resolve dinero error on bill posting

Approved-by: Patrick Fic
2021-08-20 15:07:38 +00:00
Patrick Fic
d2b2a5399d IO-1327 Resolve dinero error on bill posting 2021-08-20 08:07:01 -07:00
Patrick Fic
5a4d6d3e8c IO-1269 2021-08-19 17:31:32 -07:00
Patrick Fic
61a5e180f4 IO-1323 Add unit number to contracts list. 2021-08-19 14:43:38 -07:00
Patrick Fic
d5b8ea3ac5 IO-1317 2021-08-19 14:24:12 -07:00
Patrick Fic
4e87ef179b Resolve craco configuration. 2021-08-19 13:45:37 -07:00
Patrick Fic
3b7c31626d IO-539 Adjust Threshold Totals 2021-08-19 08:48:30 -07:00
Patrick Fic
a57e35354d Add sentry error logging. 2021-08-18 15:57:29 -07:00
Patrick Fic
a269fd3ad8 IO-1316 Add flat_rate to time tickets & resolve issues in job costing calculations. 2021-08-18 15:39:37 -07:00
Patrick Fic
86bee9ad0d IO-1321 Mark PO Quantity as required. 2021-08-18 15:38:54 -07:00
Patrick Fic
f8b57ca9bd IO-1297 Adjust Job Costing after Invoicing 2021-08-18 14:13:11 -07:00
Patrick Fic
5f7b780195 Merged in release/2021-08-20 (pull request #182)
release/2021-08-20

Approved-by: Patrick Fic
2021-08-18 20:29:06 +00:00
Patrick Fic
ba5400d04a IO-1307 Resolve selection issues on export tables. 2021-08-18 13:18:30 -07:00
Patrick Fic
0190e737c1 IO-1297 resolve decimal issue on time ticket list. 2021-08-18 12:58:26 -07:00
Patrick Fic
0d12ab36a9 Merged in release/2021-08-20 (pull request #181)
release/2021-08-20

Approved-by: Patrick Fic
2021-08-13 23:38:45 +00:00
Patrick Fic
8f52efbaca IO-1289 Resolve scoreboard UI issues. 2021-08-13 16:33:36 -07:00
Patrick Fic
d6dc5db185 IO-1296 Resolve missing translation. 2021-08-13 16:20:45 -07:00
Patrick Fic
58d1859640 IO-1306 Show Export Attempts on export screens. 2021-08-13 16:17:16 -07:00
Patrick Fic
dba648aea2 IO-1303 Adjust jobline status alignment. 2021-08-13 15:52:02 -07:00
Patrick Fic
12f6206f88 IO-1224 IO-1297 IO-1298 Adjust decimal place displays in various locations 2021-08-13 15:48:12 -07:00
Patrick Fic
7d9fd06b6d IO-233 CDK Get Makes & Taxes calculation. 2021-08-13 15:08:44 -07:00
Patrick Fic
b68de683b0 Merged in hotfix/2021-08-12 (pull request #179)
Merged in hotfix/2021-08-12 (pull request #178)

Approved-by: Patrick Fic
2021-08-12 21:30:32 +00:00
Patrick Fic
142df39cd2 Merged in hotfix/2021-08-12 (pull request #176)
Additional Checklist resolutions.

Approved-by: Patrick Fic
2021-08-12 20:21:34 +00:00
Patrick Fic
ef79ccc299 Additional Checklist resolutions. 2021-08-12 13:20:56 -07:00
Patrick Fic
6d2d96be5c Merged in hotfix/2021-08-12 (pull request #175)
Resolve delete checklist item.

Approved-by: Patrick Fic
2021-08-12 20:16:47 +00:00
Patrick Fic
8ff2a6e6c4 Resolve delete checklist item. 2021-08-12 13:16:26 -07:00
Patrick Fic
b793aa3394 Merged in hotfix/2021-08-12 (pull request #174)
hotfix/2021-08-12

Approved-by: Patrick Fic
2021-08-12 20:02:48 +00:00
Patrick Fic
be2cfb908a IO-1312 Resolve delivery checklist issue. 2021-08-12 13:01:20 -07:00
Patrick Fic
e34084b146 Merged in feature/2021-08-13 (pull request #173)
feature/2021-08-13

Approved-by: Patrick Fic
2021-08-12 16:53:13 +00:00
Patrick Fic
c1d7168260 Resolve CI issue. 2021-08-12 09:52:44 -07:00
Patrick Fic
b3a3709b72 Merged in feature/2021-08-13 (pull request #172)
IO-1286 Retry Supplement Importing fix.

Approved-by: Patrick Fic
2021-08-12 16:47:27 +00:00
Patrick Fic
dd59f3d026 IO-1286 Retry Supplement Importing fix. 2021-08-12 09:46:50 -07:00
Patrick Fic
ccb00ff391 Merged in feature/2021-08-13 (pull request #171)
feature/2021-08-13

Approved-by: Patrick Fic
2021-08-11 20:51:19 +00: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
77819b06fe Merged in hotfix/2021-08-11 (pull request #169)
hotfix/2021-08-11

Approved-by: Patrick Fic
2021-08-11 20:03:30 +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
b2362a85fa Merged in hotfix/2021-08-11 (pull request #167)
hotfix/2021-08-11

Approved-by: Patrick Fic
2021-08-11 19:58:52 +00:00
Patrick Fic
f45f351678 IO-1310 resolve production list issue. 2021-08-11 12:57:46 -07:00
Patrick Fic
311171fa0a Merged in feature/2021-08-13 (pull request #166)
Merge into CDK Branch

Approved-by: Patrick Fic
2021-08-11 18:22:20 +00:00
Patrick Fic
7e05f03f4d Resolve merge conflicts. 2021-08-11 11:21:49 -07:00
Patrick Fic
76826c1e80 Merged in feature/2021-08-13 (pull request #165)
Include sentry username

Approved-by: Patrick Fic
2021-08-11 18:16:43 +00:00
Patrick Fic
18618efdc0 Include sentry username 2021-08-11 11:16:22 -07:00
Patrick Fic
d1952dfc25 Merged in feature/2021-08-13 (pull request #164)
IO-1300 Resolve null line desc.

Approved-by: Patrick Fic
2021-08-11 18:10:01 +00:00
Patrick Fic
fe5f4f2727 IO-1300 Resolve null line desc. 2021-08-11 11:09:16 -07:00
Patrick Fic
fd7c907b8f IO-233 Beging get vehicle makes 2021-08-11 11:08:03 -07:00
Patrick Fic
0273255c2c Merged in feature/2021-08-13 (pull request #163)
Feature/2021 08 13
2021-08-11 15:09:36 +00: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
6b64499e24 IO-233 Resolv merge issues & move allocations to ws 2021-08-10 14:30:08 -07:00
Patrick Fic
e78e628bec Merge branch 'feature/cdk' into feature/2021-08-13 2021-08-10 13:58:58 -07:00
Patrick Fic
ed8eb51c2f IO-233 CDK Allocations Summary 2021-08-10 13:42:53 -07:00
Patrick Fic
124d68ef68 IO-233 CDK Shop Config 2021-08-10 08:40:38 -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
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
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
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
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
6f64cb71f2 Merged in feature/2021-07-16 (pull request #137)
feature/2021-07-16
2021-07-15 18:42:45 +00: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
7999895323 Merged in feature/2021-07-09 (pull request #135)
Feature/2021 07 09
2021-07-09 20:52:55 +00: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
f0d6c5e1b1 IO-233 CDK WIP 2021-07-06 09:31:01 -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
84b39f3d2b IO-233 WIP CDK 2021-07-02 12:53:18 -07:00
Patrick Fic
4ab0947cc8 IO-233 Add customer insert actions. 2021-06-30 16:04:01 -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
105ecd4221 IO-233 CDK 2021-06-30 13:41:00 -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
187 changed files with 54073 additions and 8010 deletions

View File

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

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,25 @@
// craco.config.js
const TerserPlugin = require("terser-webpack-plugin");
const CracoLessPlugin = require("craco-less");
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
module.exports = {
plugins: [
{
plugin: SentryWebpackPlugin,
options: {
// sentry-cli configuration
authToken:
"6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
org: "snapt-software",
project: "imexonline",
release: process.env.REACT_APP_GIT_SHA,
// webpack-specific configuration
include: ".",
ignore: ["node_modules", "webpack.config.js"],
},
},
{
plugin: CracoLessPlugin,
options: {
@@ -53,4 +69,5 @@ module.exports = {
},
}),
},
devtool: "source-map",
};

43861
client/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -8,8 +8,28 @@ import { useTranslation } from "react-i18next";
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
import client from "../utils/GraphQLClient";
import App from "./App";
import trackerGraphQL from "@openreplay/tracker-graphql";
//import trackerRedux from "@openreplay/tracker-redux";
import Tracker from "@openreplay/tracker";
//import trackerAssist from "@openreplay/tracker-assist";
moment.locale("en-US");
export const tracker = new Tracker({
projectKey: "FPjeYIbwJyvhrVVTTLHo",
ingestPoint: "https://replay.bodyshop.app/ingest",
// ...(process.env.NODE_ENV === null || process.env.NODE_ENV === "development"
// ? { __DISABLE_SECURE_MODE: true }
// : {}),
// beaconSize: 10485760,
onStart: ({ sessionID }) =>
console.log(
"******** OpenReplay tracker started with session: ",
sessionID
),
});
//tracker.use(trackerAssist({ confirmText: "Confimr hep" })); // check the list of available options below
export const recordGraphQL = tracker.use(trackerGraphQL());
tracker.start();
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
export default function AppContainer() {

View File

@@ -109,6 +109,17 @@ export default function AccountingPayablesTableComponent({ loading, bills }) {
<Checkbox disabled checked={record.is_credit_memo} />
),
},
{
title: t("exportlogs.labels.attempts"),
dataIndex: "attempts",
key: "attempts",
render: (text, record) => {
const success = record.exportlogs.filter((e) => e.successful).length;
const attempts = record.exportlogs.length;
return `${success}/${attempts}`;
},
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
@@ -121,6 +132,7 @@ export default function AccountingPayablesTableComponent({ loading, bills }) {
billId={record.id}
disabled={transInProgress || !!record.exported}
loadingCallback={setTransInProgress}
setSelectedBills={setSelectedBills}
/>
</div>
),

View File

@@ -108,7 +108,17 @@ export default function AccountingPayablesTableComponent({
<DateTimeFormatter>{record.exportedat}</DateTimeFormatter>
),
},
{
title: t("exportlogs.labels.attempts"),
dataIndex: "attempts",
key: "attempts",
render: (text, record) => {
const success = record.exportlogs.filter((e) => e.successful).length;
const attempts = record.exportlogs.length;
return `${success}/${attempts}`;
},
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
@@ -120,6 +130,7 @@ export default function AccountingPayablesTableComponent({
paymentId={record.id}
disabled={transInProgress || !!record.exportedat}
loadingCallback={setTransInProgress}
setSelectedPayments={setSelectedPayments}
/>
),
},

View File

@@ -114,17 +114,28 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) {
);
},
},
{
title: t("exportlogs.labels.attempts"),
dataIndex: "attempts",
key: "attempts",
render: (text, record) => {
const success = record.exportlogs.filter((e) => e.successful).length;
const attempts = record.exportlogs.length;
return `${success}/${attempts}`;
},
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
sorter: (a, b) => a.clm_total - b.clm_total,
render: (text, record) => (
<Space wrap>
<JobExportButton
jobId={record.id}
disabled={!!record.date_exported}
setSelectedJobs={setSelectedJobs}
/>
<Link to={`/manage/jobs/${record.id}/close`}>
<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 { createStructuredSelector } from "reselect";
import { setModalContext } from "../../redux/modals/modals.actions";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
@@ -33,6 +35,8 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({
setPartsOrderContext: (context) =>
dispatch(setModalContext({ context: context, modal: "partsOrder" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export default connect(
@@ -40,7 +44,10 @@ export default connect(
mapDispatchToProps
)(BillDetailEditcontainer);
export function BillDetailEditcontainer({ setPartsOrderContext }) {
export function BillDetailEditcontainer({
setPartsOrderContext,
insertAuditTrail,
}) {
const search = queryString.parse(useLocation().search);
const history = useHistory();
const { t } = useTranslation();
@@ -134,6 +141,12 @@ export function BillDetailEditcontainer({ setPartsOrderContext }) {
});
await Promise.all(updates);
insertAuditTrail({
jobid: bill.jobid,
billid: search.billid,
operation: AuditTrailMapping.billupdated(bill.invoice_number),
});
await refetch();
form.setFieldsValue(transformData(data));
form.resetFields();

View File

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

View File

@@ -72,9 +72,11 @@ export function BillEnterModalLinesComponent({
quantity: opt.part_qty || 1,
actual_price: opt.cost,
cost_center: opt.part_type
? responsibilityCenters.defaults.costs[
? responsibilityCenters.defaults &&
(responsibilityCenters.defaults.costs[
opt.part_type
] || null
] ||
null)
: null,
};
}

View File

@@ -1,13 +1,8 @@
import Dinero from "dinero.js";
export const CalculateBillTotal = (invoice) => {
const {
total,
billlines,
federal_tax_rate,
local_tax_rate,
state_tax_rate,
} = invoice;
const { total, billlines, federal_tax_rate, local_tax_rate, state_tax_rate } =
invoice;
//TODO Determine why this recalculates so many times.
let subtotal = Dinero({ amount: 0 });
@@ -20,8 +15,7 @@ export const CalculateBillTotal = (invoice) => {
billlines.forEach((i) => {
if (!!i) {
const itemTotal = Dinero({
amount:
Math.round(((i.actual_cost || 0) * 100 + Number.EPSILON) * 100) / 100,
amount: Math.round((i.actual_cost || 0) * 100),
}).multiply(i.quantity || 1);
subtotal = subtotal.add(itemTotal);

View File

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

View File

@@ -83,8 +83,10 @@ export function ContractsList({
render: (text, record) => (
<Link to={`/manage/courtesycars/${record.courtesycar.id}`}>{`${
record.courtesycar.year
} ${record.courtesycar.make} ${record.courtesycar.model} ${
record.courtesycar.plate ? `(${record.courtesycar.plate})` : ""
} ${record.courtesycar.make} ${record.courtesycar.model}${
record.courtesycar.plate ? ` (${record.courtesycar.plate})` : ""
}${
record.courtesycar.fleetnumber ? ` (${record.courtesycar.fleetnumber})` : ""
}`}</Link>
),
},

View File

@@ -34,7 +34,9 @@ export default function DashboardMonthlyRevenueGraph({ data, ...cardProps }) {
let dailySales;
if (!!jobsByDate[val]) {
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());
} else {
dailySales = Dinero();

View File

@@ -13,7 +13,14 @@ export default function DashboardProjectedMonthlySales({ data, ...cardProps }) {
const dollars =
data.projected_monthly_sales &&
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()
);
return (

View File

@@ -14,7 +14,8 @@ export default function DashboardTotalProductionDollars({
const dollars =
data.production_jobs &&
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()
);

View File

@@ -0,0 +1,83 @@
import { Button, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import Dinero from "dinero.js";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(DmsAllocationsSummary);
export function DmsAllocationsSummary({ socket, bodyshop, jobId }) {
const { t } = useTranslation();
const [allocationsSummary, setAllocationsSummary] = useState([]);
const columns = [
{
title: t("jobs.fields.dms.center"),
dataIndex: "center",
key: "center",
},
{
title: t("jobs.fields.dms.sale"),
dataIndex: "sale",
key: "sale",
render: (text, record) => Dinero(record.sale).toFormat(),
},
{
title: t("jobs.fields.dms.cost"),
dataIndex: "cost",
key: "cost",
render: (text, record) => Dinero(record.cost).toFormat(),
},
{
title: t("jobs.fields.dms.sale_dms_acctnumber"),
dataIndex: "sale_dms_acctnumber",
key: "sale_dms_acctnumber",
render: (text, record) =>
record.profitCenter && record.profitCenter.dms_acctnumber,
},
{
title: t("jobs.fields.dms.cost_dms_acctnumber"),
dataIndex: "cost_dms_acctnumber",
key: "cost_dms_acctnumber",
render: (text, record) =>
record.costCenter && record.costCenter.dms_acctnumber,
},
{
title: t("jobs.fields.dms.dms_wip_acctnumber"),
dataIndex: "dms_wip_acctnumber",
key: "dms_wip_acctnumber",
render: (text, record) =>
record.costCenter && record.costCenter.dms_wip_acctnumber,
},
];
return (
<Table
title={() => (
<Button
onClick={() => {
socket.emit("cdk-calculate-allocations", jobId, (ack) =>
setAllocationsSummary(ack)
);
}}
>
Get
</Button>
)}
pagination={{ position: "top", defaultPageSize: 50 }}
columns={columns}
rowKey="center"
dataSource={allocationsSummary}
/>
);
}

View File

@@ -0,0 +1,115 @@
import React, { useState } from "react";
import { Modal, Button, Table, Input } from "antd";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkMakes);
export function DmsCdkMakes({ bodyshop, form, socket }) {
const [makesList, setMakesList] = useState([]);
const [searchText, setSearchText] = useState("");
const [loading, setLoading] = useState(false);
const [visible, setVisible] = useState(false);
const [selectedModel, setSelectedModel] = useState(null);
const { t } = useTranslation();
const columns = [
{
title: t("jobs.fields.dms.makeFullName"),
dataIndex: "makeFullName",
key: "makeFullName",
},
{
title: t("jobs.fields.dms.modelFullName"),
dataIndex: "modelFullName",
key: "modelFullName",
},
{
title: t("jobs.fields.dms.makeCode"),
dataIndex: "makeCode",
key: "makeCode",
},
{
title: t("jobs.fields.dms.modelCode"),
dataIndex: "modelCode",
key: "modelCode",
},
];
const filteredMakes =
searchText !== "" && searchText
? makesList.filter(
(make) =>
searchText
.split(" ")
.some((v) =>
make.makeFullName.toLowerCase().includes(v.toLowerCase())
) ||
searchText
.split(" ")
.some((v) =>
make.modelFullName.toLowerCase().includes(v.toLowerCase())
)
)
: makesList;
return (
<div>
<Modal width={"90%"} visible={visible} onCancel={() => setVisible(false)}>
<Table
title={() => (
<Input.Search
onSearch={(val) => setSearchText(val)}
placeholder={t("general.labels.search")}
/>
)}
columns={columns}
loading={loading}
id="id"
dataSource={filteredMakes}
onRow={(record) => {
return {
onClick: setSelectedModel(record),
};
}}
rowSelection={{
onSelect: (record, selected, ...props) => {
console.log(
"🚀 ~ file: dms-cdk-makes.component.jsx ~ line 85 ~ record, selected, ...props",
record,
selected,
...props
);
setSelectedModel(record);
},
type: "radio",
selectedRowKeys: [selectedModel && selectedModel.id],
}}
/>
</Modal>
<Button
onClick={() => {
setVisible(true);
setLoading(true);
socket.emit("cdk-get-makes", bodyshop.cdk_dealerid, (makes) => {
console.log("Called back", makes);
setMakesList(makes);
setLoading(false);
});
}}
>
Get Makes
</Button>
</div>
);
}

View File

@@ -0,0 +1,77 @@
import { Button, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { socket } from "../../pages/dms/dms.container";
import PhoneFormatter from "../../utils/PhoneFormatter";
import { alphaSort } from "../../utils/sorters";
export default function DmsCustomerSelector() {
const { t } = useTranslation();
const [customerList, setcustomerList] = useState([]);
const [visible, setVisible] = useState(false);
const [selectedCustomer, setSelectedCustomer] = useState(null);
socket.on("cdk-select-customer", (customerList, callback) => {
setVisible(true);
setcustomerList(customerList);
});
const onOk = () => {
setVisible(false);
socket.emit("cdk-selected-customer", selectedCustomer);
};
const columns = [
{
title: t("dms.fields.name1"),
dataIndex: ["name1", "fullName"],
key: "name1",
sorter: (a, b) => alphaSort(a.name1?.fullName, b.name1?.fullName),
},
{
title: t("dms.fields.name2"),
dataIndex: ["name2", "fullName"],
key: "name2",
sorter: (a, b) => alphaSort(a.name2?.fullName, b.name2?.fullName),
},
{
title: t("dms.fields.phone"),
dataIndex: ["contactInfo", "mainTelephoneNumber", "value"],
key: "phone",
render: (record, value) => (
<PhoneFormatter>
{record.contactInfo?.mainTelephoneNumber?.value}
</PhoneFormatter>
),
},
{
title: t("dms.fields.address"),
//dataIndex: ["name2", "fullName"],
key: "address",
render: (record, value) =>
`${record.address?.addressLine[0]}, ${record.address?.city} ${record.address?.stateOrProvince} ${record.address?.postalCode}`,
},
];
if (!visible) return <></>;
return (
<Table
title={() => (
<div>
<Button onClick={onOk}>Select</Button>
</div>
)}
pagination={{ position: "top" }}
columns={columns}
rowKey={(record) => record.id.value}
dataSource={customerList}
//onChange={handleTableChange}
rowSelection={{
onSelect: (props) => {
setSelectedCustomer(props.id.value);
},
type: "radio",
selectedRowKeys: [selectedCustomer],
}}
/>
);
}

View File

@@ -0,0 +1,55 @@
import { Divider, Space, Tag, Timeline } from "antd";
import moment from "moment";
import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents);
export function DmsLogEvents({ socket, logs, bodyshop }) {
return (
<Timeline pending reverse={true}>
{logs.map((log, idx) => (
<Timeline.Item key={idx} color={LogLevelHierarchy(log.level)}>
<Space wrap align="start" style={{}}>
<Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag>
<span>{moment(log.timestamp).format("MM/DD/YYYY HH:MM:ss")}</span>
<Divider type="vertical" />
<span>{log.message}</span>
</Space>
</Timeline.Item>
))}
</Timeline>
);
}
function LogLevelHierarchy(level) {
switch (level) {
case "TRACE":
return "pink";
case "DEBUG":
return "orange";
case "INFO":
return "blue";
case "WARNING":
return "yellow";
case "ERROR":
return "red";
default:
return 0;
}
}

View File

@@ -0,0 +1,150 @@
import { DeleteFilled } from "@ant-design/icons";
import { Button, Form, Input } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm);
export function DmsPostForm({ bodyshop, socket, jobId }) {
const [form] = Form.useForm();
const { t } = useTranslation();
return (
<Form form={form} layout="vertical">
<LayoutFormRow>
<Form.Item
name="journal"
label={t("jobs.fields.dms.journal")}
initialValue={
bodyshop.cdk_configuration &&
bodyshop.cdk_configuration.default_journal
}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="dms_make"
label={t("jobs.fields.dms.dms_make")}
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
name="dms_make"
label={t("jobs.fields.dms.dms_make")}
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<DmsCdkMakes form={form} socket={socket} />
</LayoutFormRow>
<Form.List name={["payers"]}>
{(fields, { add, remove }) => {
return (
<div>
{fields.map((field, index) => (
<Form.Item key={field.key}>
<LayoutFormRow>
<Form.Item
label={t("jobs.fields.dms.payer.name")}
key={`${index}name`}
name={[field.name, "name"]}
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.account")}
key={`${index}account`}
name={[field.name, "account"]}
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.amount")}
key={`${index}amount`}
name={[field.name, "amount"]}
rules={[
{
required: true,
},
]}
>
<CurrencyInput />
</Form.Item>
<Form.Item
label={t("jobs.fields.dms.payer.controlnumber")}
key={`${index}controlnumber`}
name={[field.name, "controlnumber"]}
rules={[
{
required: true,
},
]}
>
<Input />
</Form.Item>
<DeleteFilled
onClick={() => {
remove(field.name);
}}
/>
</LayoutFormRow>
</Form.Item>
))}
<Form.Item>
<Button
type="dashed"
onClick={() => {
add();
}}
style={{ width: "100%" }}
>
{t("general.actions.add")}
</Button>
</Form.Item>
</div>
);
}}
</Form.List>
</Form>
);
}

View File

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

View File

@@ -161,7 +161,7 @@ export function Jobd3RdPartyModal({ bodyshop, jobId }) {
<Input />
</Form.Item>
<Form.Item
label={t("printcenter.jobs.3rdpartyfields.ponumber")}
label={t("printcenter.jobs.3rdpartyfields.refnumber")}
name="ponumber"
>
<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 { useTranslation } from "react-i18next";
import { connect } from "react-redux";
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 { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import PhoneFormatter from "../../utils/PhoneFormatter";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component";
import ScheduleAtChange from "./job-at-change.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) => ({
setScheduleContext: (context) =>
dispatch(setModalContext({ context: context, modal: "schedule" })),
openChatByPhone: (phone) => dispatch(openChatByPhone(phone)),
setMessage: (text) => dispatch(setMessage(text)),
});
export function ScheduleEventComponent({
bodyshop,
setMessage,
openChatByPhone,
event,
refetch,
handleCancel,
@@ -38,7 +64,7 @@ export function ScheduleEventComponent({
);
const popoverContent = (
<div>
<div style={{ maxWidth: "40vw" }}>
{!event.isintake ? (
<strong>{event.title}</strong>
) : (
@@ -75,17 +101,19 @@ export function ScheduleEventComponent({
{(event.job && event.job.ownr_ea) || ""}
</DataLabel>
<DataLabel label={t("jobs.fields.ownr_ph1")}>
<PhoneFormatter>
{(event.job && event.job.ownr_ph1) || ""}
</PhoneFormatter>
<ChatOpenButton
phone={event.job && event.job.ownr_ph1}
jobid={event.job.id}
/>
</DataLabel>
<DataLabel label={t("jobs.fields.alt_transport")}>
{(event.job && event.job.alt_transport) || ""}
<ScheduleAtChange job={event && event.job} />
</DataLabel>
<ScheduleEventNote event={event} />
</div>
) : null}
<Divider />
<Space wrap>
{event.job ? (
<Link to={`/manage/jobs/${event.job && event.job.id}`}>
@@ -106,23 +134,62 @@ export function ScheduleEventComponent({
{t("appointments.actions.preview")}
</Button>
) : null}
<Button
onClick={() => {
const Template = TemplateList("job").appointment_reminder;
GenerateDocument(
{
name: Template.key,
variables: { id: event.job.id },
},
{ to: event.job && event.job.ownr_ea, subject: Template.subject },
"e",
event.job && event.job.id
);
}}
disabled={event.arrived}
<Dropdown
overlay={
<Menu>
<Menu.Item
onClick={() => {
const Template = TemplateList("job").appointment_reminder;
GenerateDocument(
{
name: Template.key,
variables: { id: event.job.id },
},
{
to: event.job && event.job.ownr_ea,
subject: Template.subject,
},
"e",
event.job && event.job.id
);
}}
disabled={event.arrived}
>
{t("general.labels.email")}
</Menu.Item>
<Menu.Item
onClick={() => {
const p = parsePhoneNumber(event.job.ownr_ph1, "CA");
if (p && p.isValid()) {
openChatByPhone({
phone_num: p.formatInternational(),
jobid: event.job.id,
});
setMessage(
t("appointments.labels.reminder", {
shopname: bodyshop.shopname,
date: moment(event.start).format("MM/DD/YYYY"),
time: moment(event.start).format("HH:MM a"),
})
);
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>
<Button>{t("appointments.actions.sendreminder")}</Button>
</Dropdown>
<Button onClick={() => handleCancel(event.id)} disabled={event.arrived}>
{t("appointments.actions.cancel")}
</Button>
@@ -161,6 +228,7 @@ export function ScheduleEventComponent({
const RegularEvent = event.isintake ? (
<div style={{ display: "flex", flexWrap: "wrap" }}>
<Space>
{event.note && <AlertFilled className="production-alert" />}
<strong>{`${event.job.ro_number || t("general.labels.na")}`}</strong>
<span>{`${(event.job && event.job.ownr_fn) || ""} ${
(event.job && event.job.ownr_ln) || ""
@@ -202,4 +270,7 @@ export function ScheduleEventComponent({
</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 DateTimePicker from "../../../form-date-time-picker/form-date-time-picker.component";
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({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function JobChecklistForm({
insertAuditTrail,
formItems,
bodyshop,
currentUser,
@@ -37,6 +42,8 @@ export function JobChecklistForm({
const [intakeJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false);
const [markAptArrived] = useMutation(MARK_LATEST_APPOINTMENT_AS_ARRIVED);
const [updateOwner] = useMutation(UPDATE_OWNER);
const { jobId } = useParams();
const history = useHistory();
const search = queryString.parse(useLocation().search);
@@ -59,6 +66,12 @@ export function JobChecklistForm({
production_vars: {
...job.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" && {
@@ -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);
if (!!!result.errors) {
notification["success"]({ message: t("checklist.successes.completed") });
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 {
notification["error"]({
message: t("checklist.errors.complete", {
@@ -135,6 +177,7 @@ export function JobChecklistForm({
initialValues={{
...(type === "intake" && {
addToProduction: true,
allow_text_message: job.owner && job.owner.allow_text_message,
scheduled_completion:
(job && job.scheduled_completion) ||
moment().businessAdd(
@@ -170,6 +213,14 @@ export function JobChecklistForm({
>
<Switch disabled={readOnly} />
</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
name="scheduled_completion"
label={t("jobs.fields.scheduled_completion")}

View File

@@ -37,8 +37,8 @@ export function JobEmployeeAssignments({
});
const [visibility, setVisibility] = useState(false);
const onChange = (e) => {
setAssignment({ ...assignment, employeeid: e });
const onChange = (value, option) => {
setAssignment({ ...assignment, employeeid: value, name: option.name });
};
const popContent = (
@@ -56,7 +56,11 @@ export function JobEmployeeAssignments({
}
>
{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}`}
</Select.Option>
))}

View File

@@ -6,14 +6,34 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB_ASSIGNMENTS } from "../../graphql/jobs.queries";
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 [updateJob] = useMutation(UPDATE_JOB_ASSIGNMENTS);
const [loading, setLoading] = useState(false);
const handleAdd = async (assignment) => {
setLoading(true);
const { operation, employeeid } = assignment;
const { operation, employeeid, name } = assignment;
logImEXEvent("job_assign_employee", { operation });
let empAssignment = determineFieldName(operation);
@@ -23,6 +43,11 @@ export default function JobEmployeeAssignmentsContainer({ job, refetch }) {
});
if (refetch) refetch();
insertAuditTrail({
jobid: job.id,
operation: AuditTrailMapping.jobassignmentchange(operation, name),
});
if (!!result.errors) {
notification["error"]({
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);
};

View File

@@ -72,7 +72,7 @@ export function JobLineStatusPopup({ bodyshop, jobline, disabled }) {
);
return (
<div
style={{ width: "100%", minHeight: "2rem", cursor: "pointer" }}
style={{ width: "100%", minHeight: "1rem", cursor: "pointer" }}
onClick={() => !disabled && setEditing(true)}
>
{jobline.status}

View File

@@ -15,6 +15,7 @@ const JobSearchSelect = (
{
disabled,
convertedOnly = false,
notInvoiced = false,
notExported = true,
clm_no = false,
...restProps
@@ -30,6 +31,7 @@ const JobSearchSelect = (
variables: {
...(convertedOnly ? { isConverted: true } : {}),
...(notExported ? { notExported: true } : {}),
...(notInvoiced ? { notInvoiced: true } : {}),
},
}
: {}),

View File

@@ -6,8 +6,8 @@ import { useTranslation } from "react-i18next";
export default function JobAdminDeleteIntake({ job }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [updateJob] = useMutation(gql`
mutation UPDATE_JOB($jobId: uuid!) {
const [deleteIntake] = useMutation(gql`
mutation DELETE_INTAKE($jobId: uuid!) {
update_jobs_by_pk(
pk_columns: { id: $jobId }
_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) => {
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 },
});
@@ -34,12 +64,16 @@ export default function JobAdminDeleteIntake({ job }) {
});
}
setLoading(false);
//Get the owner details, populate it all back into the job.
};
return (
<Button loading={loading} onClick={handleDelete}>
{t("jobs.labels.deleteintake")}
</Button>
<>
<Button loading={loading} onClick={handleDelete}>
{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 { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import moment from "moment";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -21,8 +22,8 @@ export default connect(
export function JobAdminMarkReexport({ bodyshop, job }) {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [updateJob] = useMutation(gql`
mutation UPDATE_JOB($jobId: uuid!) {
const [markJobForReexport] = useMutation(gql`
mutation MARK_JOB_FOR_REEXPORT($jobId: uuid!) {
update_jobs_by_pk(
pk_columns: { id: $jobId }
_set: { date_exported: null
@@ -30,14 +31,84 @@ export function JobAdminMarkReexport({ bodyshop, job }) {
}
) {
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);
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 },
});
@@ -51,16 +122,31 @@ export function JobAdminMarkReexport({ bodyshop, job }) {
});
}
setLoading(false);
//Get the owner details, populate it all back into the job.
};
return (
<Button
loading={loading}
disabled={!job.date_exported}
onClick={handleUpdate}
>
{t("jobs.labels.markforreexport")}
</Button>
<>
<Button
loading={loading}
disabled={!job.date_exported}
onClick={handleMarkForExport}
>
{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

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

View File

@@ -8,7 +8,6 @@ import {
import { Col, notification, Row } from "antd";
import Axios from "axios";
import Dinero from "dinero.js";
import _ from "lodash";
import moment from "moment";
import queryString from "query-string";
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_NOTE } from "../../graphql/notes.queries";
import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import AlertComponent from "../alert/alert.component";
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
@@ -42,8 +43,15 @@ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
currentUser: selectCurrentUser,
});
export function JobsAvailableContainer({ bodyshop, currentUser }) {
const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function JobsAvailableContainer({
bodyshop,
currentUser,
insertAuditTrail,
}) {
const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, {
fetchPolicy: "network-only",
});
@@ -66,7 +74,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
const client = useApolloClient();
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 importOptions = importOptionsState[0];
@@ -79,13 +87,9 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
setOwnerModalVisible(false);
setInsertLoading(true);
if (
!(
estData.data &&
estData.data.available_jobs_by_pk &&
estData.data.available_jobs_by_pk.est_data
)
) {
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
if (!(estData && estData.est_data)) {
//We don't have the right data. Error!
setInsertLoading(false);
notification["error"]({
@@ -93,32 +97,47 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
});
return;
}
//IO-539 Check for Parts Rate on PAL for SGI use case.
if (
estData.est_data.parts_tax_rates &&
estData.est_data.parts_tax_rates.PAL &&
(estData.est_data.parts_tax_rates.PAL.prt_tax_rt === null ||
estData.est_data.parts_tax_rates.PAL.prt_tax_rt === 0)
) {
console.log("checking");
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for used parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}%.`
);
if (res) {
estData.est_data.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAL.prt_tax_in = true;
}
}
const newTotals = (
await Axios.post("/job/totals", {
job: {
...estData.data.available_jobs_by_pk.est_data,
joblines: estData.data.available_jobs_by_pk.est_data.joblines.data,
...estData.est_data,
joblines: estData.est_data.joblines.data,
},
})
).data;
let existingVehicles;
if (
estData.data.available_jobs_by_pk.est_data.vehicle &&
estData.data.available_jobs_by_pk.est_data.vin
) {
if (estData.est_data.vehicle && estData.est_data.vin) {
//There's vehicle data, need to double check the VIN.
existingVehicles = await client.query({
query: SEARCH_VEHICLE_BY_VIN,
variables: {
vin: estData.data.available_jobs_by_pk.est_data.vehicle.data.v_vin,
vin: estData.est_data.vehicle.data.v_vin,
},
});
}
const newJob = {
...estData.data.available_jobs_by_pk.est_data,
...estData.est_data,
clm_total: Dinero(newTotals.totals.total_repairs).toFormat("0.00"),
owner_owing: Dinero(newTotals.totals.custPayable.total).toFormat("0.00"),
job_totals: newTotals,
@@ -157,8 +176,13 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
});
//Job has been inserted. Clean up the available jobs record.
insertAuditTrail({
jobid: r.data.insert_jobs.returning[0].id,
operation: AuditTrailMapping.jobimported(),
});
deleteJob({
variables: { id: estData.data.available_jobs_by_pk.id },
variables: { id: estData.id },
}).then((r) => {
refetch();
setInsertLoading(false);
@@ -181,32 +205,47 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
setJobModalVisible(false);
setInsertLoading(true);
if (
!(
estData.data &&
estData.data.available_jobs_by_pk &&
estData.data.available_jobs_by_pk.est_data
)
) {
const estData = estDataRaw.data.available_jobs_by_pk;
if (!(estData && estData.est_data)) {
//We don't have the right data. Error!
setInsertLoading(false);
notification["error"]({
message: t("jobs.errors.creating", { error: "No job data present." }),
});
} else {
//IO-539 Check for Parts Rate on PAL for SGI use case.
if (
estData.est_data.parts_tax_rates &&
estData.est_data.parts_tax_rates.PAL &&
(estData.est_data.parts_tax_rates.PAL.prt_tax_rt === null ||
estData.est_data.parts_tax_rates.PAL.prt_tax_rt === 0)
) {
console.log("checking");
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for used parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}%.`
);
if (res) {
estData.est_data.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAL.prt_tax_in = true;
}
}
//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.vehicle;
if (importOptions.overrideHeaders) {
delete supp.ins_co_nm;
if (!importOptions.overrideHeaders) {
HeaderFields.forEach((item) => delete supp[item]);
}
let suppDelta = await GetSupplementDelta(
client,
selectedJob,
estData.data.available_jobs_by_pk.est_data.joblines.data
supp.joblines.data
);
delete supp.joblines;
@@ -265,7 +304,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
//Job has been inserted. Clean up the available jobs record.
deleteJob({
variables: { id: estData.data.available_jobs_by_pk.id },
variables: { id: estData.id },
}).then((r) => {
refetch();
setInsertLoading(false);
@@ -283,17 +322,21 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
],
},
});
insertAuditTrail({
jobid: selectedJob,
operation: AuditTrailMapping.jobsupplement(),
});
}
};
const owner =
estData.data &&
estData.data.available_jobs_by_pk &&
estData.data.available_jobs_by_pk.est_data &&
estData.data.available_jobs_by_pk.est_data.owner &&
estData.data.available_jobs_by_pk.est_data.owner.data &&
!estData.data.available_jobs_by_pk.issupplement
? estData.data.available_jobs_by_pk.est_data.owner.data
estDataRaw.data &&
estDataRaw.data.available_jobs_by_pk &&
estDataRaw.data.available_jobs_by_pk.est_data &&
estDataRaw.data.available_jobs_by_pk.est_data.owner &&
estDataRaw.data.available_jobs_by_pk.est_data.owner.data &&
!estDataRaw.data.available_jobs_by_pk.issupplement
? estDataRaw.data.available_jobs_by_pk.est_data.owner.data
: null;
const onOwnerModalCancel = () => {
@@ -331,8 +374,8 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
message={t("jobs.labels.creating_new_job")}
>
<OwnerFindModalContainer
loading={estData.loading}
error={estData.error}
loading={estDataRaw.loading}
error={estDataRaw.error}
owner={owner}
selectedOwner={selectedOwner}
setSelectedOwner={setSelectedOwner}
@@ -341,8 +384,8 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
onCancel={onOwnerModalCancel}
/>
<JobsFindModalContainer
loading={estData.loading}
error={estData.error}
loading={estDataRaw.loading}
error={estDataRaw.error}
selectedJob={selectedJob}
setSelectedJob={setSelectedJob}
importOptionsState={importOptionsState}
@@ -368,4 +411,24 @@ export function JobsAvailableContainer({ bodyshop, currentUser }) {
</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);
}
function confirmDialog(msg) {
return new Promise(function (resolve, reject) {
let confirmed = window.confirm(msg);
return confirmed ? resolve(true) : reject(false);
});
}

View File

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

View File

@@ -1,4 +1,4 @@
import { Button } from "antd";
import { Button, Dropdown, Menu } from "antd";
import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -12,11 +12,8 @@ const mapStateToProps = createStructuredSelector({
export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
const { t } = useTranslation();
const handleAllocate = () => {
logImEXEvent("jobs_close_allocate_auto");
const { defaults } = bodyshop.md_responsibility_centers;
const handleAllocate = (defaults) => {
form.setFieldsValue({
joblines: joblines.map((jl) => {
const ret = _.cloneDeep(jl);
@@ -32,7 +29,7 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
}
//Verify that this is also manually updated in server/job-costing
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")) {
ret.profitcenter_part = defaults.profits["MASH"];
} else if (lineDesc.includes("paint/materials")) {
@@ -48,8 +45,36 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
});
};
return (
<Button onClick={handleAllocate} disabled={disabled}>
const handleAutoAllocateClick = () => {
logImEXEvent("jobs_close_allocate_auto");
const { defaults } = bodyshop.md_responsibility_centers;
handleAllocate(defaults);
};
const handleMenuClick = ({ item, key, keyPath, domEvent }) => {
logImEXEvent("jobs_close_allocate_auto_dms");
handleAllocate(
bodyshop.md_responsibility_centers.dms_defaults.find(
(x) => x.name === key
)
);
};
const overlay = bodyshop.cdk_dealerid && (
<Menu onClick={handleMenuClick}>
{bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => (
<Menu.Item key={mapping.name}>{mapping.name}</Menu.Item>
))}
</Menu>
);
return bodyshop.cdk_dealerid ? (
<Dropdown overlay={overlay}>
<Button disabled={disabled}>{t("jobs.actions.dmsautoallocate")}</Button>
</Dropdown>
) : (
<Button onClick={handleAutoAllocateClick} disabled={disabled}>
{t("jobs.actions.autoallocate")}
</Button>
);

View File

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

View File

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

View File

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

View File

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

View File

@@ -10,7 +10,7 @@ import { alphaSort } from "../../utils/sorters";
import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component";
import "./labor-allocations-table.styles.scss";
import { CalculateAllocationsTotals } from "./labor-allocations-table.utility";
import _ from "lodash";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
technician: selectTechnician,
@@ -113,7 +113,7 @@ export function LaborAllocationsTable({
color: record.difference >= 0 ? "green" : "red",
}}
>
{record.difference}
{_.round(record.difference, 1)}
</strong>
),
},

View File

@@ -106,6 +106,12 @@ export default function PartsOrderModalComponent({
label={t("parts_orders.fields.quantity")}
key={`${index}quantity`}
name={[field.name, "quantity"]}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber />
</Form.Item>

View File

@@ -9,6 +9,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB_LINE_STATUS } from "../../graphql/jobs-lines.queries";
import { INSERT_NEW_PARTS_ORDERS } from "../../graphql/parts-orders.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 {
setModalContext,
@@ -19,6 +20,7 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import AlertComponent from "../alert/alert.component";
@@ -36,6 +38,8 @@ const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("partsOrder")),
setBillEnterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "billEnter" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function PartsOrderModalContainer({
@@ -45,6 +49,7 @@ export function PartsOrderModalContainer({
bodyshop,
setEmailOptions,
setBillEnterContext,
insertAuditTrail,
}) {
const { t } = useTranslation();
@@ -109,6 +114,18 @@ export function PartsOrderModalContainer({
: 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) {
notification["error"]({
message: t("parts_orders.errors.creating"),

View File

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

View File

@@ -24,6 +24,7 @@ export function PaymentExportButton({
paymentId,
disabled,
loadingCallback,
setSelectedPayments,
}) {
const { t } = useTranslation();
const [updatePayment] = useMutation(UPDATE_PAYMENTS);
@@ -141,6 +142,12 @@ export function PaymentExportButton({
}),
});
}
if (setSelectedPayments) {
setSelectedPayments((selectedBills) => {
return selectedBills.filter((i) => i !== paymentId);
});
}
}
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 ProductionBoardFilters from "../production-board-filters/production-board-filters.component";
import { selectTechnician } from "../../redux/tech/tech.selectors";
import { insertAuditTrail } from "../../redux/application/application.actions";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
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({
columns: [{ id: "Loading...", title: "Loading...", cards: [] }],
});
@@ -104,6 +117,11 @@ export function ProductionBoardKanbanComponent({ data, bodyshop, technician }) {
newChildCardNewParent
),
});
insertAuditTrail({
jobid: card.id,
operation: AuditTrailMapping.jobstatuschange(destination.toColumnId),
});
if (update.errors) {
notification["error"]({
message: t("production.errors.boardupdate", {
@@ -130,4 +148,7 @@ export function ProductionBoardKanbanComponent({ data, bodyshop, technician }) {
</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 { logImEXEvent } from "../../firebase/firebase.utils";
import { UPDATE_JOB } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
const iconStyle = { marginLeft: ".3rem" };
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
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 [updateJob] = useMutation(UPDATE_JOB);
const [loading, setLoading] = useState(false);
const handleAdd = async (assignment) => {
setLoading(true);
const { operation, employeeid } = assignment;
const { operation, employeeid, name } = assignment;
logImEXEvent("job_assign_employee", { operation });
let empAssignment = determineFieldName(operation);
@@ -44,6 +52,11 @@ export function ProductionListEmpAssignment({ bodyshop, record, type }) {
awaitRefetchQueries: true,
});
insertAuditTrail({
jobid: record.id,
operation: AuditTrailMapping.jobassignmentchange(empAssignment, name),
});
if (!!result.errors) {
notification["error"]({
message: t("jobs.errors.assigning", {
@@ -64,6 +77,11 @@ export function ProductionListEmpAssignment({ bodyshop, record, type }) {
awaitRefetchQueries: true,
});
insertAuditTrail({
jobid: record.id,
operation: AuditTrailMapping.jobassignmentremoved(empAssignment),
});
if (!!result.errors) {
notification["error"]({
message: t("jobs.errors.assigning", {
@@ -80,8 +98,8 @@ export function ProductionListEmpAssignment({ bodyshop, record, type }) {
});
const [visibility, setVisibility] = useState(false);
const onChange = (e) => {
setAssignment({ ...assignment, employeeid: e });
const onChange = (e, option) => {
setAssignment({ ...assignment, employeeid: e, name: option.name });
};
const popContent = (
@@ -99,7 +117,11 @@ export function ProductionListEmpAssignment({ bodyshop, record, type }) {
}
>
{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}`}
</Select.Option>
))}

View File

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

View File

@@ -25,6 +25,7 @@ export function ProductionListTable({
currentUser,
state,
setColumns,
setState,
}) {
const { t } = useTranslation();
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(
(a) => a.useremail === currentUser.email
@@ -77,6 +82,8 @@ export function ProductionListTable({
};
})
);
setState(bodyshop.production_config[0].columns.tableState);
};
return (

View File

@@ -41,9 +41,9 @@ export function ProductionListTable({
const [state, setState] = useState(
(bodyshop.production_config &&
bodyshop.production_config.find((p) => p.name === defaultView)
?.tableState) ||
bodyshop.production_config[0]?.tableState || {
bodyshop.production_config.find((p) => p.name === defaultView)?.columns
.tableState) ||
bodyshop.production_config[0]?.columns.tableState || {
sortedInfo: {},
filteredInfo: { text: "" },
}
@@ -173,6 +173,7 @@ export function ProductionListTable({
<ProductionListTableViewSelect
state={state}
setState={setState}
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 { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { updateUserDetails } from "../../redux/user/user.actions";
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({
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 (
<div>
<Form
onFinish={handleFinish}
autoComplete={"no"}
initialValues={currentUser}
layout="vertical"
>
<Form.Item
label={t("user.fields.displayname")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name="displayName"
>
<Input />
</Form.Item>
<Form.Item label={t("user.fields.photourl")} name="photoURL">
<Input />
</Form.Item>
<LayoutFormRow>
<Form.Item
label={t("user.fields.displayname")}
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
name="displayName"
>
<Input />
</Form.Item>
<Form.Item label={t("user.fields.photourl")} name="photoURL">
<Input />
</Form.Item>
</LayoutFormRow>
<Button type="primary" key="submit" htmlType="submit">
{t("user.actions.updateprofile")}
</Button>
</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>
);
});

View File

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

View File

@@ -31,7 +31,7 @@ export function ReportCenterModalContainer({
onCancel={() => toggleModalVisible()}
cancelButtonProps={{ style: { display: "none" } }}
destroyOnClose
width="60%"
width="80%"
>
<ReportCenterModalComponent />
</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 moment from "moment";
import React, { useState } from "react";
@@ -91,31 +101,34 @@ export function ScheduleJobModalComponent({
<DateTimePicker onlyFuture />
</Form.Item>
</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}>
{t("appointments.actions.calculate")}
</Button>
{smartOptions.length > 0 && (
<div>{t("appointments.labels.suggesteddates")}</div>
)}
<div
className="imex-flex-row imex-flex-row__flex-space-around"
style={{ flexWrap: "wrap" }}
>
{smartOptions.map((d, idx) => (
<Button
className="imex-flex-row__margin"
key={idx}
onClick={() => {
form.setFieldsValue({ start: new moment(d).add(8, "hours") });
handleDateBlur();
}}
>
<DateFormatter>{d}</DateFormatter>
</Button>
))}
</div>
</Card>
{smartOptions.map((d, idx) => (
<Button
className="imex-flex-row__margin"
key={idx}
onClick={() => {
form.setFieldsValue({ start: new moment(d).add(8, "hours") });
handleDateBlur();
}}
>
<DateFormatter>{d}</DateFormatter>
</Button>
))}
</Space>
<LayoutFormRow grow>
<Form.Item
name="notifyCustomer"
@@ -124,12 +137,9 @@ export function ScheduleJobModalComponent({
>
<Switch />
</Form.Item>
<Form.Item shouldUpdate>
{() => (
<Form.Item name="email" label={t("jobs.fields.ownr_ea")}>
<EmailInput disabled={!form.getFieldValue("notifyCustomer")} />
</Form.Item>
)}
<Form.Item name="email" label={t("jobs.fields.ownr_ea")}>
<EmailInput disabled={!form.getFieldValue("notifyCustomer")} />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow grow>
@@ -158,6 +168,9 @@ export function ScheduleJobModalComponent({
))}
</Select>
</Form.Item>
<Form.Item name={"note"} label={t("appointments.fields.note")}>
<Input />
</Form.Item>
</LayoutFormRow>
{t("appointments.labels.history")}
<ScheduleExistingAppointmentsList

View File

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

View File

@@ -17,7 +17,7 @@ import {
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
import _ from "lodash";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
@@ -52,17 +52,22 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
const theValue = {
date: moment(val).format("D dd"),
paintHrs: dayhrs.painthrs,
bodyHrs: dayhrs.bodyhrs,
accTargetHrs: Utils.AsOfDateTargetHours(
bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget,
val
paintHrs: _.round(dayhrs.painthrs, 1),
bodyHrs: _.round(dayhrs.bodyhrs, 1),
accTargetHrs: _.round(
Utils.AsOfDateTargetHours(
bodyshop.scoreboard_target.dailyBodyTarget +
bodyshop.scoreboard_target.dailyPaintTarget,
val
),
1
),
accHrs:
accHrs: _.round(
acc.length > 0
? acc[acc.length - 1].accHrs + dayhrs.painthrs + dayhrs.bodyhrs
: dayhrs.painthrs + dayhrs.bodyhrs,
1
),
};
return [...acc, theValue];

View File

@@ -1,6 +1,9 @@
import { Button, Card, Tabs } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import ShopInfoGeneral from "./shop-info.general.component";
import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component";
import ShopInfoLaborRates from "./shop-info.laborrates.component";
@@ -11,7 +14,15 @@ import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
import ShopInfoSchedulingComponent from "./shop-info.scheduling.component";
import ShopInfoSpeedPrint from "./shop-info.speedprint.component";
export default function ShopInfoComponent({ form, saveLoading }) {
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent);
export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
const { t } = useTranslation();
return (
<Card
@@ -53,6 +64,7 @@ export default function ShopInfoComponent({ form, saveLoading }) {
>
<ShopInfoResponsibilityCenterComponent form={form} />
</Tabs.TabPane>
<Tabs.TabPane key="checklists" tab={t("bodyshop.labels.checklists")}>
<ShopInfoIntakeChecklistComponent form={form} />
</Tabs.TabPane>

View File

@@ -165,6 +165,20 @@ export default function ShopInfoGeneral({ form }) {
);
}}
</Form.Item>
<Form.Item
label={t("bodyshop.labels.printlater")}
valuePropName="checked"
name={["accountingconfig", "printlater"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.emaillater")}
valuePropName="checked"
name={["accountingconfig", "emaillater"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.inhousevendorid")}
name={"inhousevendorid"}
@@ -444,6 +458,13 @@ export default function ShopInfoGeneral({ form }) {
>
<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 grow header={t("bodyshop.labels.messagingpresets")}>
<Form.List name={["md_messaging_presets"]}>

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

View File

@@ -1,9 +1,10 @@
import { Card, Space, Table } from "antd";
import { EditFilled } from "@ant-design/icons";
import { Card, Space, Table } from "antd";
import moment from "moment";
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {
selectAuthLevel,
@@ -16,8 +17,6 @@ import RbacWrapper, {
HasRbacAccess,
} from "../rbac-wrapper/rbac-wrapper.component";
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
@@ -105,7 +104,8 @@ export function TimeTicketList({
title: t("jobs.fields.ro_number"),
dataIndex: "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:
state.sortedInfo.columnKey === "ro_number" && state.sortedInfo.order,
render: (text, record) =>
@@ -267,8 +267,12 @@ export function TimeTicketList({
<Table.Summary.Cell />
<Table.Summary.Cell />
<Table.Summary.Cell />
<Table.Summary.Cell>{totals.productivehrs}</Table.Summary.Cell>
<Table.Summary.Cell>{totals.actualhrs}</Table.Summary.Cell>
<Table.Summary.Cell>
{totals.productivehrs.toFixed(1)}
</Table.Summary.Cell>
<Table.Summary.Cell>
{totals.actualhrs.toFixed(1)}
</Table.Summary.Cell>
<Table.Summary.Cell>
{totals.actualhrs === 0 || !totals.actualhrs
? "∞"

View File

@@ -1,5 +1,5 @@
import { useQuery } from "@apollo/client";
import { Form, Input, InputNumber, Select } from "antd";
import { Form, Input, InputNumber, Select, Switch } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -48,7 +48,9 @@ export function TimeTicketModalComponent({
{emps &&
emps.rates.map((item) => (
<Select.Option key={item.cost_center}>
{item.cost_center}
{item.cost_center === "timetickets.labels.shift"
? t(item.cost_center)
: item.cost_center}
</Select.Option>
))}
</Select>
@@ -82,7 +84,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>
@@ -108,7 +113,17 @@ export function TimeTicketModalComponent({
},
]}
>
<EmployeeSearchSelect options={employeeAutoCompleteOptions} />
<EmployeeSearchSelect
options={employeeAutoCompleteOptions}
onSelect={(value) => {
console.log(value);
const emps =
employeeAutoCompleteOptions &&
employeeAutoCompleteOptions.filter((e) => e.id === value)[0];
console.log(emps);
form.setFieldsValue({ flat_rate: emps && emps.flat_rate });
}}
/>
</Form.Item>
<Form.Item
shouldUpdate={(prev, cur) => prev.employeeid !== cur.employeeid}
@@ -135,6 +150,14 @@ export function TimeTicketModalComponent({
);
}}
</Form.Item>
<Form.Item
name="flat_rate"
label={t("timetickets.fields.flat_rate")}
valuePropName="checked"
>
<Switch />
</Form.Item>
</LayoutFormRow>
<LayoutFormRow>

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;
try {
messaging = firebase.messaging();

View File

@@ -24,6 +24,10 @@ export const QUERY_JOBS_FOR_EXPORT = gql`
clm_total
clm_no
ins_co_nm
exportlogs {
id
successful
}
}
}
`;
@@ -37,6 +41,10 @@ export const QUERY_BILLS_FOR_EXPORT = gql`
invoice_number
is_credit_memo
total
exportlogs {
id
successful
}
job {
id
ro_number
@@ -73,6 +81,10 @@ export const QUERY_PAYMENTS_FOR_EXPORT = gql`
transactionid
paymentnum
date
exportlogs {
id
successful
}
}
}
`;

View File

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

View File

@@ -1,18 +1,31 @@
import { gql } from "@apollo/client";
export const QUERY_AUDIT_TRAIL = gql`
query QUERY_AUDIT_TRAIL($id: uuid!) {
audit_trail(where: { recordid: { _eq: $id } }) {
query QUERY_AUDIT_TRAIL($jobid: uuid!) {
audit_trail(
where: { jobid: { _eq: $jobid } }
order_by: { created: desc }
) {
useremail
tabname
schemaname
recordid
jobid
operation
old_val
new_val
id
created
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

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

View File

@@ -58,6 +58,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
jobid
employeeid
memo
flat_rate
employee {
id
first_name

View File

@@ -318,6 +318,7 @@ export const QUERY_JOB_COSTING_DETAILS = gql`
cost_center
actualhrs
productivehrs
flat_rate
}
}
}
@@ -1025,6 +1026,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
$search: String
$isConverted: Boolean
$notExported: Boolean
$notInvoiced: Boolean
) {
search_jobs(
args: { search: $search }
@@ -1033,6 +1035,7 @@ export const SEARCH_JOBS_FOR_AUTOCOMPLETE = gql`
_and: {
converted: { _eq: $isConverted }
date_exported: { _is_null: $notExported }
date_invoiced: { _is_null: $notInvoiced }
}
}
) {
@@ -1847,6 +1850,10 @@ export const QUERY_JOB_CHECKLISTS = gql`
scheduled_delivery
actual_delivery
production_vars
owner {
id
allow_text_message
}
bodyshop {
id
intakechecklist
@@ -1869,3 +1876,100 @@ export const FIND_JOBS_BY_CLAIM = gql`
}
}
`;
export const QUERY_JOB_EXPORT_DMS = gql`
query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
jobs_by_pk(id: $id) {
ro_number
invoice_allocation
ins_co_id
id
ded_amt
ded_status
depreciation_taxes
other_amount_payable
towing_payable
storage_payable
adjustment_bottom_line
federal_tax_rate
state_tax_rate
local_tax_rate
tax_tow_rt
tax_str_rt
tax_paint_mat_rt
tax_sub_rt
tax_lbr_rt
tax_levies_rt
parts_tax_rates
job_totals
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_mahw
rate_mapa
rate_mash
rate_matd
status
date_exported
date_invoiced
voided
scheduled_completion
actual_completion
scheduled_delivery
actual_delivery
scheduled_in
actual_in
bills {
id
federal_tax_rate
local_tax_rate
state_tax_rate
is_credit_memo
billlines {
actual_cost
cost_center
id
quantity
}
}
joblines(where: { removed: { _eq: false } }) {
id
removed
tax_part
line_desc
prt_dsmk_p
prt_dsmk_m
part_type
oem_partno
db_price
act_price
part_qty
mod_lbr_ty
db_hrs
mod_lb_hrs
lbr_op
lbr_amt
op_code_desc
profitcenter_labor
profitcenter_part
prt_dsmk_p
}
}
}
`;

View File

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

View File

@@ -2,7 +2,10 @@ import { gql } from "@apollo/client";
export const SUBSCRIPTION_SCOREBOARD = gql`
subscription SUBSCRIPTION_SCOREBOARD($start: date!, $end: date!) {
scoreboard(where: { _and: { date: { _gte: $start, _lte: $end } } }) {
scoreboard(
where: { _and: { date: { _gte: $start, _lte: $end } } }
order_by: { date: asc }
) {
id
painthrs
bodyhrs

View File

@@ -14,6 +14,7 @@ export const QUERY_TICKETS_BY_JOBID = gql`
id
memo
jobid
flat_rate
employee {
employee_number
first_name
@@ -42,6 +43,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE = gql`
productivehrs
memo
jobid
flat_rate
job {
id
ro_number
@@ -72,6 +74,7 @@ export const INSERT_NEW_TIME_TICKET = gql`
ciecacode
date
memo
flat_rate
}
}
}
@@ -98,6 +101,7 @@ export const UPDATE_TIME_TICKET = gql`
updated_at
jobid
date
flat_rate
memo
}
}
@@ -121,6 +125,7 @@ export const QUERY_ACTIVE_TIME_TICKETS = gql`
clockon
memo
cost_center
flat_rate
jobid
job {
id

View File

@@ -1,16 +1,25 @@
import { Result, Timeline, Space, Tag, Divider, Button } from "antd";
//import { useQuery } from "@apollo/client";
import { Button, Col, Result, Row, Select, Space } from "antd";
import queryString from "query-string";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import SocketIO from "socket.io-client";
//import AlertComponent from "../../components/alert/alert.component";
import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms-allocations-summary.component";
import DmsCustomerSelector from "../../components/dms-customer-selector/dms-customer-selector.component";
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
import DmsPostForm from "../../components/dms-post-form/dms-post-form.component";
//import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { auth } from "../../firebase/firebase.utils";
//import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries";
import {
setBreadcrumbs,
setSelectedHeader,
} from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { useTranslation } from "react-i18next";
import SocketIO from "socket.io-client";
import { auth } from "../../firebase/firebase.utils";
import moment from "moment";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -38,7 +47,15 @@ export const socket = SocketIO(
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const [logLevel, setLogLevel] = useState("TRACE");
const [logs, setLogs] = useState([]);
const search = queryString.parse(useLocation().search);
const { jobId } = search;
// const { loading, error } = useQuery(QUERY_JOB_EXPORT_DMS, {
// variables: { id: jobId },
// skip: true, //!jobId,
// });
useEffect(() => {
document.title = t("titles.dms");
@@ -55,6 +72,19 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
socket.on("connected", () => {
console.log("Connected again.");
});
socket.on("reconnect", () => {
console.log("Connected again.");
setLogs((logs) => {
return [
...logs,
{
timestamp: new Date(),
level: "WARNING",
message: "Reconnected to CDK Export Service",
},
];
});
});
socket.on("log-event", (payload) => {
setLogs((logs) => {
@@ -63,74 +93,68 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
});
socket.connect();
socket.emit("set-log-level", "TRACE");
socket.emit("set-log-level", logLevel);
return () => {
socket.removeAllListeners();
socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!bodyshop.cdk_dealerid) return <Result status="404" />;
if (!jobId || !bodyshop.cdk_dealerid) return <Result status="404" />;
const dmsType = determineDmsType(bodyshop);
// if (loading) return <LoadingSpinner />;
// if (error) return <AlertComponent message={error.message} type="error" />;
return (
<div>
<Button
onClick={() => {
socket.emit(
`${dmsType}-export-job`,
"752a4f5f-22ab-414b-b182-98d4e62227ef"
);
}}
>
Export
</Button>
<Space>
<Button
onClick={() => {
socket.emit(
`${dmsType}-export-job`,
"752a4f5f-22ab-414b-b182-98d4e62227ef"
);
}}
>
Export
</Button>
<Select
placeholder="Log Level"
value={logLevel}
onChange={(value) => {
setLogLevel(value);
socket.emit("set-log-level", value);
}}
>
<Select.Option key="TRACE">TRACE</Select.Option>
<Select.Option key="DEBUG">DEBUG</Select.Option>
<Select.Option key="INFO">INFO</Select.Option>
<Select.Option key="WARNING">WARNING</Select.Option>
<Select.Option key="ERROR">ERROR</Select.Option>
</Select>
<Button onClick={() => setLogs([])}>Clear Logs</Button>
</Space>
<Row gutter={32}>
<Col span={18}>
<DmsAllocationsSummary socket={socket} jobId={jobId} />
<DmsPostForm socket={socket} jobId={jobId} />
</Col>
<Col span={6}>
<div style={{ maxHeight: "500px", overflowY: "auto" }}>
<DmsLogEvents socket={socket} logs={logs} />
</div>
</Col>
</Row>
<Button
onClick={() => {
setLogs([]);
socket.disconnect();
socket.connect();
}}
>
reconnect
</Button>
<Timeline pending={socket.connected && "Processing..."} reverse={true}>
{logs.map((log, idx) => (
<Timeline.Item key={idx} color={LogLevelHierarchy(log.level)}>
<Space wrap align="start" style={{}}>
<Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag>
<span>{moment(log.timestamp).format("MM/DD/YYYY HH:MM:ss")}</span>
<Divider type="vertical" />
<span>{log.message}</span>
</Space>
</Timeline.Item>
))}
</Timeline>
<DmsCustomerSelector />
</div>
);
}
function LogLevelHierarchy(level) {
switch (level) {
case "TRACE":
return "pink";
case "DEBUG":
return "orange";
case "INFO":
return "blue";
case "WARNING":
return "yellow";
case "ERROR":
return "red";
default:
return 0;
}
}
const determineDmsType = (bodyshop) => {
if (bodyshop.cdk_dealerid) return "cdk";
else {

View File

@@ -1,5 +1,5 @@
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 { useTranslation } from "react-i18next";
import { connect } from "react-redux";
@@ -84,6 +84,9 @@ export function JobsCloseContainer({ setBreadcrumbs, setSelectedHeader }) {
return (
<RbacWrapper action="jobs:admin">
<Typography.Title level={4} style={{ color: "tomato" }}>
{t("jobs.labels.adminwarning")}
</Typography.Title>
<Row gutter={[16, 16]}>
<Col {...colSpan}>
<Card style={cardStyle}>

View File

@@ -5,6 +5,7 @@ import Icon, {
FileImageFilled,
PrinterFilled,
ToolFilled,
HistoryOutlined,
} from "@ant-design/icons";
import {
Button,
@@ -45,6 +46,9 @@ import ScheduleJobModalContainer from "../../components/schedule-job-modal/sched
import { selectJobReadOnly } from "../../redux/application/application.selectors";
import { setModalContext } from "../../redux/modals/modals.actions";
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({
bodyshop: selectBodyshop,
@@ -53,6 +57,8 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({
setPrintCenterContext: (context) =>
dispatch(setModalContext({ context: context, modal: "printCenter" })),
insertAuditTrail: ({ jobid, operation }) =>
dispatch(insertAuditTrail({ jobid, operation })),
});
export function JobsDetailPage({
setPrintCenterContext,
@@ -60,6 +66,7 @@ export function JobsDetailPage({
job,
mutationUpdateJob,
handleSubmit,
insertAuditTrail,
refetch,
}) {
const { t } = useTranslation();
@@ -81,6 +88,7 @@ export function JobsDetailPage({
const handleFinish = async (values) => {
setLoading(true);
const result = await mutationUpdateJob({
variables: {
jobId: job.id,
@@ -105,6 +113,62 @@ export function JobsDetailPage({
notification["success"]({
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();
form.setFieldsValue(transormJobToForm(job));
form.resetFields();
@@ -279,6 +343,17 @@ export function JobsDetailPage({
>
<JobNotesContainer jobId={job.id} />
</Tabs.TabPane>
<Tabs.TabPane
tab={
<span>
<HistoryOutlined />
{t("jobs.labels.audit")}
</span>
}
key="audit"
>
<JobAuditTrail jobId={job.id} />
</Tabs.TabPane>
</Tabs>
</Form>
</div>

View File

@@ -53,3 +53,8 @@ export const setOnline = (isOnline) => ({
type: ApplicationActionTypes.SET_ONLINE_STATUS,
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 { all, call, put, select, takeLatest } from "redux-saga/effects";
import { QUERY_SCHEDULE_LOAD_DATA } from "../../graphql/appointments.queries";
import { INSERT_AUDIT_TRAIL } from "../../graphql/audit_trail.queries";
import client from "../../utils/GraphQLClient";
import { CalculateLoad, CheckJobBucket } from "../../utils/SSSUtils";
import {
@@ -125,6 +126,56 @@ export function* calculateScheduleLoad({ payload: end }) {
}
}
export function* applicationSagas() {
yield all([call(onCalculateScheduleLoad)]);
export function* onInsertAuditTrail() {
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_PARTNER_VERSION: "SET_PARTNER_VERSION",
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
};
export default ApplicationActionTypes;

View File

@@ -1,6 +0,0 @@
import DmsActions from "./dms.types";
export const endLoading = (options) => ({
// type: DmsActions.END_LOADING,
payload: options,
});

View File

@@ -1,20 +0,0 @@
import DmsActionTypes from "./dms.types";
const INITIAL_STATE = {
eventLog: [],
};
const dmsReducer = (state = INITIAL_STATE, action) => {
switch (action.type) {
// case ApplicationActionTypes.SET_SELECTED_HEADER:
// return {
// ...state,
// selectedHeader: action.payload,
// };
default:
return state;
}
};
export default dmsReducer;

View File

@@ -1,14 +0,0 @@
import { all, call } from "redux-saga/effects";
//import DmsActionTypes from "./dms.types";
export function* onCalculateScheduleLoad() {
// yield takeLatest(
// DmsActionTypes.CALCULATE_SCHEDULE_LOAD,
// calculateScheduleLoad
// );
}
export function* calculateScheduleLoad({ payload: end }) {}
export function* dmsSagas() {
yield all([call()]);
}

View File

@@ -1,8 +0,0 @@
import { createSelector } from "reselect";
const selectDms = (state) => state.dms;
export const selectEventLog = createSelector(
[selectDms],
(dms) => dms.eventLog
);

View File

@@ -1,4 +0,0 @@
const DmsActionTypes = {
ADD_EVENT: "ADD_EVENT",
};
export default DmsActionTypes;

View File

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

View File

@@ -27,7 +27,7 @@ const middlewares = [
if (process.env.NODE_ENV === "development") {
middlewares.push(createLogger({ collapsed: true, diff: true }));
}
//middlewares.push(Tracker.use(trackerRedux()));
const composeEnhancers =
typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({

View File

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

View File

@@ -37,6 +37,7 @@
"fields": {
"alt_transport": "Alt. Trans.",
"color": "Appointment Color",
"note": "Appt. Note",
"time": "Appointment Time",
"title": "Title"
},
@@ -52,6 +53,7 @@
"nocompletingjobs": "No jobs scheduled for completion.",
"nodateselected": "No date has been selected.",
"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: ",
"smartscheduling": "Smart Scheduling",
"suggesteddates": "Suggested Dates"
@@ -82,6 +84,24 @@
"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": {
"actions": {
"newline": "New Line"
@@ -211,6 +231,12 @@
"deliver": {
"templates": "Delivery Templates"
},
"dms": {
"default_journal": "Default Journal",
"dms_acctnumber": "DMS Account #",
"dms_wip_acctnumber": "DMS W.I.P. Account #",
"mappingname": "DMS Mapping Name"
},
"email": "General Shop Email",
"enforce_class": "Enforce Class on Conversion?",
"enforce_referral": "Enforce Referrals",
@@ -426,6 +452,7 @@
"production_statuses": "Production Statuses"
},
"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?",
"website": "Website",
"zip_post": "Zip/Postal Code"
@@ -446,6 +473,11 @@
"defaultcostsmapping": "Default Costs Mapping",
"defaultprofitsmapping": "Default Profits Mapping",
"deliverchecklist": "Delivery Checklist",
"dms": {
"cdk_dealerid": "CDK Dealer ID",
"title": "DMS"
},
"emaillater": "Email Later",
"employees": "Employees",
"insurancecos": "Insurance Companies",
"intakechecklist": "Intake Checklist",
@@ -457,6 +489,7 @@
"notespresets": "Notes Presets",
"orderstatuses": "Order Statuses",
"partslocations": "Parts Locations",
"printlater": "Print Later",
"rbac": "Role Based Access Control",
"responsibilitycenters": {
"costs": "Cost Centers",
@@ -491,6 +524,7 @@
},
"labels": {
"addtoproduction": "Add Job to Production?",
"allow_text_message": "Permission to Text?",
"checklist": "Checklist",
"printpack": "Job Intake Print Pack",
"removefromproduction": "Remove job from production?"
@@ -743,6 +777,7 @@
"attachments": "Attachments",
"documents": "Documents",
"generatingemail": "Generating email...",
"pdfcopywillbeattached": "A PDF copy of this email will be attached when it is sent.",
"preview": "Email Preview"
},
"successes": {
@@ -792,6 +827,9 @@
"exportlogs": {
"fields": {
"createdat": "Created At"
},
"labels": {
"attempts": "Export Attempts"
}
},
"general": {
@@ -856,6 +894,7 @@
"message": "Message",
"monday": "Monday",
"na": "N/A",
"newpassword": "New Password",
"no": "No",
"nointernet": "It looks like you're not connected to the internet.",
"nointernet_sub": "Please check your connection and try again. ",
@@ -876,6 +915,7 @@
"sendagain": "Send Again",
"sendby": "Send By",
"signin": "Sign In",
"sms": "SMS",
"sub_status": {
"expired": "The subscription for this shop has expired. Please contact technical support to reactivate the subscription. "
},
@@ -1026,6 +1066,7 @@
"changestatus": "Change Status",
"convert": "Convert",
"deliver": "Deliver",
"dmsautoallocate": "DMS Auto Allocate",
"export": "Export",
"exportcustdata": "Export Customer Data",
"exportselected": "Export Selected",
@@ -1035,6 +1076,7 @@
"intake": "Intake",
"manualnew": "Create New Job Manually",
"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",
@@ -1045,6 +1087,7 @@
"schedule": "Schedule",
"sendcsi": "Send CSI",
"sync": "Sync",
"uninvoice": "Uninvoice",
"unvoid": "Unvoid Job",
"viewchecklist": "View Checklists",
"viewdetail": "View Details"
@@ -1103,6 +1146,14 @@
"ded_amt": "Deductible",
"ded_status": "Deductible Status",
"depreciation_taxes": "Depreciation/Taxes",
"dms": {
"center": "Center",
"cost": "Cost",
"cost_dms_acctnumber": "Cost DMS Acct #",
"dms_wip_acctnumber": "Cost WIP DMS Acct #",
"sale": "Sale",
"sale_dms_acctnumber": "Sale DMS Acct #"
},
"driveable": "Driveable",
"employee_body": "Body",
"employee_csr": "Customer Service Rep.",
@@ -1226,6 +1277,7 @@
"servicecar": "Service Car",
"servicing_dealer": "Servicing Dealer",
"servicing_dealer_contact": "Servicing Dealer Contact",
"special_coverage_policy": "Special Coverage Policy",
"specialcoveragepolicy": "Special Coverage Policy",
"state_tax_rate": "Provincial/State Tax Rate",
"status": "Job Status",
@@ -1263,6 +1315,7 @@
"additionaltotal": "Additional Total",
"adjustmentrate": "Adjustment Rate",
"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",
"alreadyclosed": "This job has already been closed.",
"appointmentconfirmation": "Send confirmation to customer?",
@@ -1315,7 +1368,8 @@
"waived": "Waived"
},
"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",
"difference": "Difference",
"diskscan": "Scan Disk for Estimates",
@@ -1808,6 +1862,7 @@
"depreciation": "Depreciation",
"other": "Other",
"ponumber": "PO Number",
"refnumber": "Reference Number",
"sendtype": "Send by",
"state": "Province/State",
"zip": "Postal Code/Zip"
@@ -1951,12 +2006,15 @@
"bills": "Bills",
"exportlogs": "Export Logs",
"jobs": "Jobs",
"parts_orders": "Parts Orders",
"payments": "Payments",
"scoreboard": "Scoreboard",
"timetickets": "Timetickets"
},
"vendor": "Vendor"
},
"templates": {
"anticipated_revenue": "Anticipated Revenue",
"attendance_detail": "Attendance (All Employees)",
"attendance_employee": "Employee Attendance",
"attendance_summary": "Attendance Summary (All Employees)",
@@ -1970,28 +2028,35 @@
"gsr_by_delivery_date": "Gross Sales by Delivery Date",
"gsr_by_estimator": "Gross Sales by Estimator",
"gsr_by_exported_date": "Gross Sales by Export Date",
"gsr_by_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_referral": "Gross Sales by Referral Source",
"gsr_by_ro": "Gross Sales by RO",
"gsr_labor_only": "Gross Sales - Labor Only",
"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_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_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_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",
"job_costing_ro_csr": "Job Costing by CSR",
"job_costing_ro_date_detail": "Job Costing by RO - Detail",
"job_costing_ro_date_summary": "Job Costing by RO - Summary",
"job_costing_ro_estimator": "Job Costing by Estimator",
"job_costing_ro_ins_co": "Job Costing by RO Source",
"lag_time": "Lag Time",
"open_orders": "Open Orders by Date",
"open_orders_csr": "Open Orders by CSR",
"open_orders_estimator": "Open Orders by Estimator",
"open_orders_ins_co": "Open Orders by Insurance Company",
"parts_backorder": "Backordered Parts",
"parts_not_recieved": "Parts Not Received",
"payments_by_date": "Payments by Date",
"payments_by_date_type": "Payments by Date and Type",
"production_by_csr": "Production by CSR",
@@ -2008,6 +2073,8 @@
"purchases_grouped_by_vendor_detailed": "Purchases Grouped by Vendor - Detailed",
"purchases_grouped_by_vendor_summary": "Purchases Grouped by Vendor - Summary",
"schedule": "Appointment Schedule",
"scoreboard_detail": "Scoreboard Detail",
"scoreboard_summary": "Scoreboard Summary",
"supplement_ratio_ins_co": "Supplement Ratio by Source",
"thank_you_date": "Thank You Letters",
"timetickets": "Time Tickets",
@@ -2047,6 +2114,11 @@
"updated": "Scoreboard updated."
}
},
"scoredboard": {
"successes": {
"updated": "Scoreboard entry updated."
}
},
"tech": {
"fields": {
"employeeid": "Employee ID",
@@ -2090,6 +2162,7 @@
"date": "Ticket Date",
"efficiency": "Efficiency",
"employee": "Employee",
"flat_rate": "Flat Rate?",
"memo": "Memo",
"productivehrs": "Productive Hours",
"ro_number": "Job to Post Against"
@@ -2214,6 +2287,7 @@
},
"user": {
"actions": {
"changepassword": "Change Password",
"signout": "Sign Out",
"updateprofile": "Update Profile"
},
@@ -2228,6 +2302,9 @@
},
"labels": {
"actions": "Actions"
},
"successess": {
"passwordchanged": "Password changed successfully. "
}
},
"vehicles": {

View File

@@ -37,6 +37,7 @@
"fields": {
"alt_transport": "",
"color": "",
"note": "",
"time": "",
"title": "Título"
},
@@ -52,6 +53,7 @@
"nocompletingjobs": "",
"nodateselected": "No se ha seleccionado ninguna fecha.",
"priorappointments": "Nombramientos previos",
"reminder": "",
"scheduledfor": "Cita programada para:",
"smartscheduling": "",
"suggesteddates": ""
@@ -82,6 +84,24 @@
"values": ""
}
},
"audit_trail": {
"messages": {
"billposted": "",
"billupdated": "",
"jobassignmentchange": "",
"jobassignmentremoved": "",
"jobchecklist": "",
"jobconverted": "",
"jobfieldchanged": "",
"jobimported": "",
"jobinproductionchange": "",
"jobmodifylbradj": "",
"jobspartsorder": "",
"jobspartsreturn": "",
"jobstatuschange": "",
"jobsupplement": ""
}
},
"billlines": {
"actions": {
"newline": ""
@@ -211,6 +231,12 @@
"deliver": {
"templates": ""
},
"dms": {
"default_journal": "",
"dms_acctnumber": "",
"dms_wip_acctnumber": "",
"mappingname": ""
},
"email": "",
"enforce_class": "",
"enforce_referral": "",
@@ -426,6 +452,7 @@
"production_statuses": ""
},
"target_touchtime": "",
"tt_allow_post_to_invoiced": "",
"use_fippa": "",
"website": "",
"zip_post": ""
@@ -446,6 +473,11 @@
"defaultcostsmapping": "",
"defaultprofitsmapping": "",
"deliverchecklist": "",
"dms": {
"cdk_dealerid": "",
"title": ""
},
"emaillater": "",
"employees": "",
"insurancecos": "",
"intakechecklist": "",
@@ -457,6 +489,7 @@
"notespresets": "",
"orderstatuses": "",
"partslocations": "",
"printlater": "",
"rbac": "",
"responsibilitycenters": {
"costs": "",
@@ -491,6 +524,7 @@
},
"labels": {
"addtoproduction": "",
"allow_text_message": "",
"checklist": "",
"printpack": "",
"removefromproduction": ""
@@ -743,6 +777,7 @@
"attachments": "",
"documents": "",
"generatingemail": "",
"pdfcopywillbeattached": "",
"preview": ""
},
"successes": {
@@ -792,6 +827,9 @@
"exportlogs": {
"fields": {
"createdat": ""
},
"labels": {
"attempts": ""
}
},
"general": {
@@ -856,6 +894,7 @@
"message": "",
"monday": "",
"na": "N / A",
"newpassword": "",
"no": "",
"nointernet": "",
"nointernet_sub": "",
@@ -876,6 +915,7 @@
"sendagain": "",
"sendby": "",
"signin": "",
"sms": "",
"sub_status": {
"expired": ""
},
@@ -1026,6 +1066,7 @@
"changestatus": "Cambiar Estado",
"convert": "Convertir",
"deliver": "",
"dmsautoallocate": "",
"export": "",
"exportcustdata": "",
"exportselected": "",
@@ -1035,6 +1076,7 @@
"intake": "",
"manualnew": "",
"mark": "",
"markasexported": "",
"markpstexempt": "",
"markpstexemptconfirm": "",
"postbills": "Contabilizar facturas",
@@ -1045,6 +1087,7 @@
"schedule": "Programar",
"sendcsi": "",
"sync": "",
"uninvoice": "",
"unvoid": "",
"viewchecklist": "",
"viewdetail": ""
@@ -1103,6 +1146,14 @@
"ded_amt": "Deducible",
"ded_status": "Estado deducible",
"depreciation_taxes": "Depreciación / Impuestos",
"dms": {
"center": "",
"cost": "",
"cost_dms_acctnumber": "",
"dms_wip_acctnumber": "",
"sale": "",
"sale_dms_acctnumber": ""
},
"driveable": "",
"employee_body": "",
"employee_csr": "Representante de servicio al cliente.",
@@ -1226,6 +1277,7 @@
"servicecar": "Auto de servicio",
"servicing_dealer": "Distribuidor de servicio",
"servicing_dealer_contact": "Servicio Contacto con el concesionario",
"special_coverage_policy": "Política de cobertura especial",
"specialcoveragepolicy": "Política de cobertura especial",
"state_tax_rate": "",
"status": "Estado del trabajo",
@@ -1263,6 +1315,7 @@
"additionaltotal": "",
"adjustmentrate": "",
"adjustments": "",
"adminwarning": "",
"allocations": "",
"alreadyclosed": "",
"appointmentconfirmation": "¿Enviar confirmación al cliente?",
@@ -1315,6 +1368,7 @@
"waived": ""
},
"deleteconfirm": "",
"deletedelivery": "",
"deleteintake": "",
"deliverchecklist": "",
"difference": "",
@@ -1808,6 +1862,7 @@
"depreciation": "",
"other": "",
"ponumber": "",
"refnumber": "",
"sendtype": "",
"state": "",
"zip": ""
@@ -1951,12 +2006,15 @@
"bills": "",
"exportlogs": "",
"jobs": "",
"parts_orders": "",
"payments": "",
"scoreboard": "",
"timetickets": ""
},
"vendor": ""
},
"templates": {
"anticipated_revenue": "",
"attendance_detail": "",
"attendance_employee": "",
"attendance_summary": "",
@@ -1976,22 +2034,29 @@
"gsr_by_ro": "",
"gsr_labor_only": "",
"hours_sold_detail_closed": "",
"hours_sold_detail_closed_csr": "",
"hours_sold_detail_closed_ins_co": "",
"hours_sold_detail_open": "",
"hours_sold_detail_open_csr": "",
"hours_sold_detail_open_ins_co": "",
"hours_sold_summary_closed": "",
"hours_sold_summary_closed_csr": "",
"hours_sold_summary_closed_ins_co": "",
"hours_sold_summary_open": "",
"hours_sold_summary_open_csr": "",
"hours_sold_summary_open_ins_co": "",
"job_costing_ro_csr": "",
"job_costing_ro_date_detail": "",
"job_costing_ro_date_summary": "",
"job_costing_ro_estimator": "",
"job_costing_ro_ins_co": "",
"lag_time": "",
"open_orders": "",
"open_orders_csr": "",
"open_orders_estimator": "",
"open_orders_ins_co": "",
"parts_backorder": "",
"parts_not_recieved": "",
"payments_by_date": "",
"payments_by_date_type": "",
"production_by_csr": "",
@@ -2008,6 +2073,8 @@
"purchases_grouped_by_vendor_detailed": "",
"purchases_grouped_by_vendor_summary": "",
"schedule": "",
"scoreboard_detail": "",
"scoreboard_summary": "",
"supplement_ratio_ins_co": "",
"thank_you_date": "",
"timetickets": "",
@@ -2047,6 +2114,11 @@
"updated": ""
}
},
"scoredboard": {
"successes": {
"updated": ""
}
},
"tech": {
"fields": {
"employeeid": "",
@@ -2090,6 +2162,7 @@
"date": "",
"efficiency": "",
"employee": "",
"flat_rate": "",
"memo": "",
"productivehrs": "",
"ro_number": ""
@@ -2214,6 +2287,7 @@
},
"user": {
"actions": {
"changepassword": "",
"signout": "desconectar",
"updateprofile": "Actualización del perfil"
},
@@ -2228,6 +2302,9 @@
},
"labels": {
"actions": ""
},
"successess": {
"passwordchanged": ""
}
},
"vehicles": {

View File

@@ -37,6 +37,7 @@
"fields": {
"alt_transport": "",
"color": "",
"note": "",
"time": "",
"title": "Titre"
},
@@ -52,6 +53,7 @@
"nocompletingjobs": "",
"nodateselected": "Aucune date n'a été sélectionnée.",
"priorappointments": "Rendez-vous précédents",
"reminder": "",
"scheduledfor": "Rendez-vous prévu pour:",
"smartscheduling": "",
"suggesteddates": ""
@@ -82,6 +84,24 @@
"values": ""
}
},
"audit_trail": {
"messages": {
"billposted": "",
"billupdated": "",
"jobassignmentchange": "",
"jobassignmentremoved": "",
"jobchecklist": "",
"jobconverted": "",
"jobfieldchanged": "",
"jobimported": "",
"jobinproductionchange": "",
"jobmodifylbradj": "",
"jobspartsorder": "",
"jobspartsreturn": "",
"jobstatuschange": "",
"jobsupplement": ""
}
},
"billlines": {
"actions": {
"newline": ""
@@ -211,6 +231,12 @@
"deliver": {
"templates": ""
},
"dms": {
"default_journal": "",
"dms_acctnumber": "",
"dms_wip_acctnumber": "",
"mappingname": ""
},
"email": "",
"enforce_class": "",
"enforce_referral": "",
@@ -426,6 +452,7 @@
"production_statuses": ""
},
"target_touchtime": "",
"tt_allow_post_to_invoiced": "",
"use_fippa": "",
"website": "",
"zip_post": ""
@@ -446,6 +473,11 @@
"defaultcostsmapping": "",
"defaultprofitsmapping": "",
"deliverchecklist": "",
"dms": {
"cdk_dealerid": "",
"title": ""
},
"emaillater": "",
"employees": "",
"insurancecos": "",
"intakechecklist": "",
@@ -457,6 +489,7 @@
"notespresets": "",
"orderstatuses": "",
"partslocations": "",
"printlater": "",
"rbac": "",
"responsibilitycenters": {
"costs": "",
@@ -491,6 +524,7 @@
},
"labels": {
"addtoproduction": "",
"allow_text_message": "",
"checklist": "",
"printpack": "",
"removefromproduction": ""
@@ -743,6 +777,7 @@
"attachments": "",
"documents": "",
"generatingemail": "",
"pdfcopywillbeattached": "",
"preview": ""
},
"successes": {
@@ -792,6 +827,9 @@
"exportlogs": {
"fields": {
"createdat": ""
},
"labels": {
"attempts": ""
}
},
"general": {
@@ -856,6 +894,7 @@
"message": "",
"monday": "",
"na": "N / A",
"newpassword": "",
"no": "",
"nointernet": "",
"nointernet_sub": "",
@@ -876,6 +915,7 @@
"sendagain": "",
"sendby": "",
"signin": "",
"sms": "",
"sub_status": {
"expired": ""
},
@@ -1026,6 +1066,7 @@
"changestatus": "Changer le statut",
"convert": "Convertir",
"deliver": "",
"dmsautoallocate": "",
"export": "",
"exportcustdata": "",
"exportselected": "",
@@ -1035,6 +1076,7 @@
"intake": "",
"manualnew": "",
"mark": "",
"markasexported": "",
"markpstexempt": "",
"markpstexemptconfirm": "",
"postbills": "Poster des factures",
@@ -1045,6 +1087,7 @@
"schedule": "Programme",
"sendcsi": "",
"sync": "",
"uninvoice": "",
"unvoid": "",
"viewchecklist": "",
"viewdetail": ""
@@ -1103,6 +1146,14 @@
"ded_amt": "Déductible",
"ded_status": "Statut de franchise",
"depreciation_taxes": "Amortissement / taxes",
"dms": {
"center": "",
"cost": "",
"cost_dms_acctnumber": "",
"dms_wip_acctnumber": "",
"sale": "",
"sale_dms_acctnumber": ""
},
"driveable": "",
"employee_body": "",
"employee_csr": "représentant du service à la clientèle",
@@ -1226,6 +1277,7 @@
"servicecar": "Voiture de service",
"servicing_dealer": "Concessionnaire",
"servicing_dealer_contact": "Contacter le concessionnaire",
"special_coverage_policy": "Politique de couverture spéciale",
"specialcoveragepolicy": "Politique de couverture spéciale",
"state_tax_rate": "",
"status": "Statut de l'emploi",
@@ -1263,6 +1315,7 @@
"additionaltotal": "",
"adjustmentrate": "",
"adjustments": "",
"adminwarning": "",
"allocations": "",
"alreadyclosed": "",
"appointmentconfirmation": "Envoyer une confirmation au client?",
@@ -1315,6 +1368,7 @@
"waived": ""
},
"deleteconfirm": "",
"deletedelivery": "",
"deleteintake": "",
"deliverchecklist": "",
"difference": "",
@@ -1808,6 +1862,7 @@
"depreciation": "",
"other": "",
"ponumber": "",
"refnumber": "",
"sendtype": "",
"state": "",
"zip": ""
@@ -1951,12 +2006,15 @@
"bills": "",
"exportlogs": "",
"jobs": "",
"parts_orders": "",
"payments": "",
"scoreboard": "",
"timetickets": ""
},
"vendor": ""
},
"templates": {
"anticipated_revenue": "",
"attendance_detail": "",
"attendance_employee": "",
"attendance_summary": "",
@@ -1976,22 +2034,29 @@
"gsr_by_ro": "",
"gsr_labor_only": "",
"hours_sold_detail_closed": "",
"hours_sold_detail_closed_csr": "",
"hours_sold_detail_closed_ins_co": "",
"hours_sold_detail_open": "",
"hours_sold_detail_open_csr": "",
"hours_sold_detail_open_ins_co": "",
"hours_sold_summary_closed": "",
"hours_sold_summary_closed_csr": "",
"hours_sold_summary_closed_ins_co": "",
"hours_sold_summary_open": "",
"hours_sold_summary_open_csr": "",
"hours_sold_summary_open_ins_co": "",
"job_costing_ro_csr": "",
"job_costing_ro_date_detail": "",
"job_costing_ro_date_summary": "",
"job_costing_ro_estimator": "",
"job_costing_ro_ins_co": "",
"lag_time": "",
"open_orders": "",
"open_orders_csr": "",
"open_orders_estimator": "",
"open_orders_ins_co": "",
"parts_backorder": "",
"parts_not_recieved": "",
"payments_by_date": "",
"payments_by_date_type": "",
"production_by_csr": "",
@@ -2008,6 +2073,8 @@
"purchases_grouped_by_vendor_detailed": "",
"purchases_grouped_by_vendor_summary": "",
"schedule": "",
"scoreboard_detail": "",
"scoreboard_summary": "",
"supplement_ratio_ins_co": "",
"thank_you_date": "",
"timetickets": "",
@@ -2047,6 +2114,11 @@
"updated": ""
}
},
"scoredboard": {
"successes": {
"updated": ""
}
},
"tech": {
"fields": {
"employeeid": "",
@@ -2090,6 +2162,7 @@
"date": "",
"efficiency": "",
"employee": "",
"flat_rate": "",
"memo": "",
"productivehrs": "",
"ro_number": ""
@@ -2214,6 +2287,7 @@
},
"user": {
"actions": {
"changepassword": "",
"signout": "Déconnexion",
"updateprofile": "Mettre à jour le profil"
},
@@ -2228,6 +2302,9 @@
},
"labels": {
"actions": ""
},
"successess": {
"passwordchanged": ""
}
},
"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 { split } from "apollo-link";
import apolloLogger from "apollo-link-logger";
import axios from "axios";
import { auth } from "../firebase/firebase.utils";
import errorLink from "../graphql/apollo-error-handling";
@@ -48,7 +47,7 @@ const roundTripLink = new ApolloLink((operation, forward) => {
});
const TrackExecutionTime = async (operationName, time) => {
await axios.post("/ioevent", { operationName, time, dbevent: true });
//await axios.post("/ioevent", { operationName, time, dbevent: true });
};
const subscriptionMiddleware = {

View File

@@ -752,6 +752,67 @@ export const TemplateList = (type, context) => {
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: {
title: i18n.t("reportcenter.templates.estimator_detail"),
description: "",
@@ -814,6 +875,18 @@ export const TemplateList = (type, context) => {
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: {
title: i18n.t("reportcenter.templates.job_costing_ro_ins_co"),
description: "",
@@ -1117,6 +1190,66 @@ export const TemplateList = (type, context) => {
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"

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
- args:
permission:
allow_aggregations: false
columns:
- id
- new_val
- old_val
- operation
- schemaname
- tabname
- useremail
- created
- bodyshopid
- recordid
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: audit_trail
schema: public
type: create_select_permission

View File

@@ -0,0 +1,6 @@
- args:
role: user
table:
name: audit_trail
schema: public
type: drop_select_permission

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