Compare commits
76 Commits
release/20
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
774e4cdf94 | ||
|
|
65ade5cab8 | ||
|
|
bf21a073fb | ||
|
|
51f3b5927b | ||
|
|
42c779f368 | ||
|
|
0b2efa31b5 | ||
|
|
17baa8fcb2 | ||
|
|
fd53eb92e6 | ||
|
|
6521c0bfb8 | ||
|
|
50489dd682 | ||
|
|
220afa5add | ||
|
|
9d549b02fe | ||
|
|
c423e61ce8 | ||
|
|
65550c7bf4 | ||
|
|
cdb4da9e5f | ||
|
|
b59a5303c6 | ||
|
|
9a72abbed0 | ||
|
|
762a5ff01b | ||
|
|
2f6571e703 | ||
|
|
80c90f1819 | ||
|
|
4b0c2c60a2 | ||
|
|
ee7d7d2f6a | ||
|
|
0ad670013b | ||
|
|
064a66aa66 | ||
|
|
f46c19b152 | ||
|
|
ea2c583b26 | ||
|
|
7bd2a90141 | ||
|
|
632549dd9d | ||
|
|
70ef274821 | ||
|
|
7d9759fda4 | ||
|
|
0fd06d5e4e | ||
|
|
4995e44e06 | ||
|
|
96f292f61c | ||
|
|
c62f5e3911 | ||
|
|
9f1d184081 | ||
|
|
29e596eedb | ||
|
|
6e28afda67 | ||
|
|
eca7e9fba1 | ||
|
|
aa2ac2b296 | ||
|
|
ce57752b95 | ||
|
|
63163c6459 | ||
|
|
2712ee5c0b | ||
|
|
4179943df6 | ||
|
|
8bb8eee384 | ||
|
|
85d79a8d7f | ||
|
|
d38dd67c2f | ||
|
|
e73d082eab | ||
|
|
2637538d9a | ||
|
|
0eacf9a840 | ||
|
|
eb58274f90 | ||
|
|
aa410d6847 | ||
|
|
97f1be9d6f | ||
|
|
a7c9dde5e3 | ||
|
|
8ec5bc049b | ||
|
|
1f2bec06ef | ||
|
|
1cad57bec2 | ||
|
|
10f3c01677 | ||
|
|
fc68b669db | ||
|
|
6e22091b81 | ||
|
|
545db54c14 | ||
|
|
aa69fef9ba | ||
|
|
1e30642d28 | ||
|
|
c1dfba949e | ||
|
|
dfa9592755 | ||
|
|
8f2b1f0f78 | ||
|
|
cf91ec14c0 | ||
|
|
dfe0f99bea | ||
|
|
c76da54b93 | ||
|
|
186f6101ff | ||
|
|
e95cf0a026 | ||
|
|
3eaa3a0189 | ||
|
|
f6e2393a40 | ||
|
|
3e067e2103 | ||
|
|
334dd5fe94 | ||
|
|
7a515c35d2 | ||
|
|
1942103985 |
@@ -1,4 +1,4 @@
|
||||
<babeledit_project version="1.2" be_version="2.7.1">
|
||||
<babeledit_project be_version="2.7.1" version="1.2">
|
||||
<!--
|
||||
|
||||
BabelEdit project file
|
||||
@@ -6134,6 +6134,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>laa</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>lab</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -8229,6 +8250,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>qbo</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>rbac</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -16878,6 +16920,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>tax_part</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>total</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -19527,6 +19590,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>date_last_contacted</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>date_open</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -21327,6 +21411,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ownr_ph2</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>paa</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -22261,6 +22366,48 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>referral_source_extra</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>referral_source_other</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>referralsource</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -29268,6 +29415,32 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>errors</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>inserting</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>fields</name>
|
||||
<children>
|
||||
@@ -29402,6 +29575,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>notetoadd</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
@@ -29805,6 +29999,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ownr_ph2</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ownr_st</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -30333,6 +30548,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>comments</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>cost</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -32575,6 +32811,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>csi_invitation_action</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>diagnostic_authorization</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -32827,6 +33084,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>iou_form</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>job_costing_ro</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -33205,6 +33483,48 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>sgi_certificate_of_repairs</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>sgi_windshield_auth</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>stolen_recovery_checklist</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
@@ -4,37 +4,37 @@
|
||||
"private": true,
|
||||
"proxy": "http://localhost:5000",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.4.13",
|
||||
"@apollo/client": "^3.4.16",
|
||||
"@craco/craco": "^6.3.0",
|
||||
"@fingerprintjs/fingerprintjs": "^3.3.0",
|
||||
"@lourenci/react-kanban": "^2.1.0",
|
||||
"@openreplay/tracker": "^3.3.1",
|
||||
"@openreplay/tracker-assist": "^3.1.1",
|
||||
"@openreplay/tracker": "^3.4.4",
|
||||
"@openreplay/tracker-assist": "^3.4.3",
|
||||
"@openreplay/tracker-graphql": "^3.0.0",
|
||||
"@openreplay/tracker-redux": "^3.0.0",
|
||||
"@sentry/react": "^6.13.0",
|
||||
"@sentry/tracing": "^6.13.0",
|
||||
"@stripe/react-stripe-js": "^1.4.0",
|
||||
"@stripe/stripe-js": "^1.17.1",
|
||||
"@sentry/react": "^6.13.2",
|
||||
"@sentry/tracing": "^6.13.2",
|
||||
"@stripe/react-stripe-js": "^1.5.0",
|
||||
"@stripe/stripe-js": "^1.19.1",
|
||||
"@tanem/react-nprogress": "^3.0.79",
|
||||
"antd": "^4.16.13",
|
||||
"apollo-link-logger": "^2.0.0",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "^0.22.0",
|
||||
"craco-less": "^1.20.0",
|
||||
"dinero.js": "^1.9.0",
|
||||
"dotenv": "^10.0.0",
|
||||
"enquire-js": "^0.2.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"firebase": "^9.0.2",
|
||||
"graphql": "^15.5.3",
|
||||
"i18next": "^21.0.0",
|
||||
"firebase": "^9.1.1",
|
||||
"graphql": "^15.6.0",
|
||||
"i18next": "^21.2.4",
|
||||
"i18next-browser-languagedetector": "^6.1.2",
|
||||
"jsoneditor": "^9.5.4",
|
||||
"jsoneditor": "^9.5.6",
|
||||
"jsreport-browser-client-dist": "^1.3.0",
|
||||
"libphonenumber-js": "^1.9.34",
|
||||
"libphonenumber-js": "^1.9.36",
|
||||
"logrocket": "^2.0.0",
|
||||
"markerjs2": "^2.11.2",
|
||||
"markerjs2": "^2.13.0",
|
||||
"moment-business-days": "^1.2.0",
|
||||
"phone": "^3.1.8",
|
||||
"preval.macro": "^5.0.0",
|
||||
@@ -43,7 +43,7 @@
|
||||
"rc-queue-anim": "^2.0.0",
|
||||
"rc-scroll-anim": "^2.7.6",
|
||||
"react": "^17.0.1",
|
||||
"react-big-calendar": "^0.35.0",
|
||||
"react-big-calendar": "^0.36.1",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^17.0.1",
|
||||
@@ -51,7 +51,7 @@
|
||||
"react-grid-gallery": "^0.5.5",
|
||||
"react-grid-layout": "^1.3.0",
|
||||
"react-i18next": "^11.12.0",
|
||||
"react-icons": "^4.2.0",
|
||||
"react-icons": "^4.3.1",
|
||||
"react-number-format": "^4.7.3",
|
||||
"react-redux": "^7.2.5",
|
||||
"react-resizable": "^3.0.4",
|
||||
@@ -59,13 +59,13 @@
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-sublime-video": "^0.2.5",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"recharts": "^2.1.3",
|
||||
"recharts": "^2.1.4",
|
||||
"redux": "^4.1.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.1.3",
|
||||
"redux-state-sync": "^3.1.2",
|
||||
"reselect": "^4.0.0",
|
||||
"sass": "^1.41.1",
|
||||
"sass": "^1.42.1",
|
||||
"socket.io-client": "^4.2.0",
|
||||
"styled-components": "^5.3.1",
|
||||
"subscriptions-transport-ws": "^0.9.18",
|
||||
@@ -114,7 +114,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/webpack-plugin": "^1.17.1",
|
||||
"@sentry/webpack-plugin": "^1.17.2",
|
||||
"patch-package": "^6.4.7",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.2"
|
||||
|
||||
@@ -11,7 +11,7 @@ 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";
|
||||
//import trackerAssist from "@openreplay/tracker-assist";
|
||||
import { getCurrentUser } from "../firebase/firebase.utils";
|
||||
moment.locale("en-US");
|
||||
|
||||
@@ -24,14 +24,14 @@ export const tracker = new Tracker({
|
||||
// beaconSize: 10485760,
|
||||
onStart: async ({ sessionID }) => {
|
||||
const user = await getCurrentUser();
|
||||
tracker.setUserID(user.email);
|
||||
console.log("ORS SESSION ", sessionID, user.email);
|
||||
if (user) tracker.setUserID(user.email);
|
||||
console.log("ORS SESSION ", sessionID, user && user.email);
|
||||
},
|
||||
});
|
||||
|
||||
tracker.use(
|
||||
trackerAssist({ confirmText: "Technical support is about to assist you." })
|
||||
); // check the list of available options below
|
||||
// tracker.use(
|
||||
// trackerAssist({ confirmText: "Technical support is about to assist you." })
|
||||
// ); // 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");
|
||||
|
||||
@@ -124,3 +124,7 @@
|
||||
z-index: 2 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.react-kanban-column {
|
||||
background-color: #ddd !important;
|
||||
}
|
||||
|
||||
24
client/src/assets/C2QB_composite_English.svg
Normal file
|
After Width: | Height: | Size: 51 KiB |
24
client/src/assets/C2QB_transparent_English.svg
Normal file
|
After Width: | Height: | Size: 51 KiB |
4
client/src/assets/qbo/C2QB_green_btn_med_default.svg
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
5
client/src/assets/qbo/C2QB_green_btn_med_hover.svg
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
4
client/src/assets/qbo/C2QB_green_btn_short_default.svg
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
5
client/src/assets/qbo/C2QB_green_btn_short_hover.svg
Normal file
|
After Width: | Height: | Size: 6.5 KiB |
4
client/src/assets/qbo/C2QB_green_btn_tall_default.svg
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
5
client/src/assets/qbo/C2QB_green_btn_tall_hover.svg
Normal file
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
5
client/src/assets/qbo/C2QB_transparent_btn_med_hover.svg
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
|
After Width: | Height: | Size: 6.2 KiB |
@@ -9,8 +9,25 @@ import PayableExportAll from "../payable-export-all-button/payable-export-all-bu
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import queryString from "query-string";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
export default function AccountingPayablesTableComponent({ loading, bills }) {
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(AccountingPayablesTableComponent);
|
||||
|
||||
export function AccountingPayablesTableComponent({ bodyshop, loading, bills }) {
|
||||
const { t } = useTranslation();
|
||||
const [selectedBills, setSelectedBills] = useState([]);
|
||||
const [transInProgress, setTransInProgress] = useState(false);
|
||||
@@ -166,6 +183,9 @@ export default function AccountingPayablesTableComponent({ loading, bills }) {
|
||||
loadingCallback={setTransInProgress}
|
||||
completedCallback={setSelectedBills}
|
||||
/>
|
||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
||||
<QboAuthorizeComponent />
|
||||
)}
|
||||
<Input
|
||||
value={state.search}
|
||||
onChange={handleSearch}
|
||||
|
||||
@@ -11,6 +11,7 @@ import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-butto
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
@@ -206,6 +207,9 @@ export function AccountingReceivablesTableComponent({
|
||||
completedCallback={setSelectedJobs}
|
||||
/>
|
||||
)}
|
||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
||||
<QboAuthorizeComponent />
|
||||
)}
|
||||
<Input.Search
|
||||
value={state.search}
|
||||
onChange={handleSearch}
|
||||
|
||||
@@ -12,7 +12,18 @@ const BillLineSearchSelect = ({ options, disabled, ...restProps }, ref) => {
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
showSearch
|
||||
optionFilterProp="line_desc"
|
||||
// optionFilterProp="line_desc"
|
||||
filterOption={(inputValue, option) => {
|
||||
console.log(inputValue);
|
||||
return (
|
||||
(option.line_desc &&
|
||||
option.line_desc
|
||||
.toLowerCase()
|
||||
.includes(inputValue.toLowerCase())) ||
|
||||
(option.oem_partno &&
|
||||
option.oem_partno.toLowerCase().includes(inputValue.toLowerCase()))
|
||||
);
|
||||
}}
|
||||
notFoundContent={"Removed."}
|
||||
{...restProps}
|
||||
>
|
||||
@@ -29,6 +40,7 @@ const BillLineSearchSelect = ({ options, disabled, ...restProps }, ref) => {
|
||||
part_type={item.part_type}
|
||||
line_desc={item.line_desc}
|
||||
part_qty={item.part_qty}
|
||||
oem_partno={item.oem_partno}
|
||||
style={{
|
||||
...(item.removed ? { textDecoration: "line-through" } : {}),
|
||||
}}
|
||||
|
||||
@@ -62,13 +62,16 @@ export default function GlobalSearch() {
|
||||
}`,
|
||||
label: (
|
||||
<Link to={`/manage/owners/${owner.id}`}>
|
||||
<Space size="small" split={<Divider type="vertical" />}>
|
||||
<Space size="small" split={<Divider type="vertical" />} wrap>
|
||||
<span>{`${owner.ownr_fn || ""} ${owner.ownr_ln || ""} ${
|
||||
owner.ownr_co_nm || ""
|
||||
}`}</span>
|
||||
<PhoneNumberFormatter>
|
||||
{owner.ownr_ph1}
|
||||
</PhoneNumberFormatter>
|
||||
<PhoneNumberFormatter>
|
||||
{owner.ownr_ph2}
|
||||
</PhoneNumberFormatter>
|
||||
</Space>
|
||||
</Link>
|
||||
),
|
||||
|
||||
@@ -106,6 +106,12 @@ export function ScheduleEventComponent({
|
||||
jobid={event.job.id}
|
||||
/>
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.ownr_ph2")}>
|
||||
<ChatOpenButton
|
||||
phone={event.job && event.job.ownr_ph2}
|
||||
jobid={event.job.id}
|
||||
/>
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.alt_transport")}>
|
||||
{(event.job && event.job.alt_transport) || ""}
|
||||
<ScheduleAtChange job={event && event.job} />
|
||||
|
||||
@@ -167,7 +167,11 @@ export function JobChecklistForm({
|
||||
title={t("checklist.labels.checklist")}
|
||||
extra={
|
||||
!readOnly && (
|
||||
<Button loading={loading} onClick={() => form.submit()}>
|
||||
<Button
|
||||
loading={loading}
|
||||
type="primary"
|
||||
onClick={() => form.submit()}
|
||||
>
|
||||
{t("general.actions.submit")}
|
||||
</Button>
|
||||
)
|
||||
|
||||
@@ -112,60 +112,46 @@ export function JobDetailCards({ setPrintCenterContext }) {
|
||||
<Divider type="horizontal" />
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsInsuranceComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsInsuranceComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsTotalsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsTotalsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsDatesComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsDatesComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsPartsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsPartsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsNotesComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsNotesComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsDocumentsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsDocumentsComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
<Col {...span}>
|
||||
<Card.Grid style={{ width: "100%", height: "100%" }}>
|
||||
<JobDetailCardsDamageComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Card.Grid>
|
||||
<JobDetailCardsDamageComponent
|
||||
loading={loading}
|
||||
data={data ? data.jobs_by_pk : null}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
</Card>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Timeline } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
|
||||
export default function JobDetailCardsDatesComponent({ loading, data }) {
|
||||
@@ -26,80 +26,86 @@ export default function JobDetailCardsDatesComponent({ loading, data }) {
|
||||
) ? (
|
||||
<div>{t("jobs.errors.nodates")}</div>
|
||||
) : null}
|
||||
{data.date_last_contacted ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_last_contacted")}: </label>
|
||||
<DateTimeFormatter>{data.date_last_contacted}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
{data.date_open ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_open")}: </label>
|
||||
<DateFormatter>{data.date_open}</DateFormatter>
|
||||
<DateTimeFormatter>{data.date_open}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_estimated ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_estimated")}: </label>
|
||||
<DateFormatter>{data.date_estimated}</DateFormatter>
|
||||
<DateTimeFormatter>{data.date_estimated}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_scheduled ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_scheduled")}: </label>
|
||||
<DateFormatter>{data.date_scheduled}</DateFormatter>
|
||||
<DateTimeFormatter>{data.date_scheduled}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_in ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.scheduled_in")}: </label>
|
||||
<DateFormatter>{data.scheduled_in}</DateFormatter>
|
||||
<DateTimeFormatter>{data.scheduled_in}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.actual_in ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.actual_in")}: </label>
|
||||
<DateFormatter>{data.actual_in}</DateFormatter>
|
||||
<DateTimeFormatter>{data.actual_in}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_completion ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.scheduled_completion")}: </label>
|
||||
<DateFormatter>{data.scheduled_completion}</DateFormatter>
|
||||
<DateTimeFormatter>{data.scheduled_completion}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.actual_completion ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.actual_completion")}: </label>
|
||||
<DateFormatter>{data.actual_completion}</DateFormatter>
|
||||
<DateTimeFormatter>{data.actual_completion}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.scheduled_delivery ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.scheduled_delivery")}: </label>
|
||||
<DateFormatter>{data.scheduled_delivery}</DateFormatter>
|
||||
<DateTimeFormatter>{data.scheduled_delivery}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.actual_delivery ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.actual_delivery")}: </label>
|
||||
<DateFormatter>{data.actual_delivery}</DateFormatter>
|
||||
<DateTimeFormatter>{data.actual_delivery}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_invoiced ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_invoiced")}: </label>
|
||||
<DateFormatter>{data.date_invoiced}</DateFormatter>
|
||||
<DateTimeFormatter>{data.date_invoiced}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
|
||||
{data.date_exported ? (
|
||||
<Timeline.Item>
|
||||
<label>{t("jobs.fields.date_exported")}: </label>
|
||||
<DateFormatter>{data.date_exported}</DateFormatter>
|
||||
<DateTimeFormatter>{data.date_exported}</DateTimeFormatter>
|
||||
</Timeline.Item>
|
||||
) : null}
|
||||
</Timeline>
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { Carousel } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { GenerateThumbUrl } from "../jobs-documents-gallery/job-documents.utility";
|
||||
import CardTemplate from "./job-detail-cards.template.component";
|
||||
import { DetermineFileType } from "../documents-upload/documents-upload.utility";
|
||||
export default function JobDetailCardsDocumentsComponent({ loading, data }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -22,15 +22,7 @@ export default function JobDetailCardsDocumentsComponent({ loading, data }) {
|
||||
{data.documents.length > 0 ? (
|
||||
<Carousel autoplay>
|
||||
{data.documents.map((item) => (
|
||||
<img
|
||||
key={item.id}
|
||||
src={`${
|
||||
process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
}/${DetermineFileType(item.type)}/upload/${
|
||||
process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
|
||||
}/${item.key}`}
|
||||
alt={item.name}
|
||||
/>
|
||||
<img key={item.id} src={GenerateThumbUrl(item)} alt={item.name} />
|
||||
))}
|
||||
</Carousel>
|
||||
) : (
|
||||
|
||||
@@ -12,11 +12,13 @@ export default function JobDetailCardTemplate({
|
||||
if (extraLink) extra = { extra: <Link to={extraLink}>More</Link> };
|
||||
return (
|
||||
<Card
|
||||
size='small'
|
||||
className='job-card'
|
||||
size="small"
|
||||
className="job-card"
|
||||
title={title}
|
||||
loading={loading}
|
||||
{...extra}>
|
||||
style={{ height: "100%" }}
|
||||
{...extra}
|
||||
>
|
||||
{otherProps.children}
|
||||
</Card>
|
||||
);
|
||||
|
||||
@@ -36,6 +36,7 @@ import JobLinesBillRefernece from "../job-lines-bill-reference/job-lines-bill-re
|
||||
// import AllocationsBulkAssignmentContainer from "../allocations-bulk-assignment/allocations-bulk-assignment.container";
|
||||
// import AllocationsEmployeeLabelContainer from "../allocations-employee-label/allocations-employee-label.container";
|
||||
import PartsOrderModalContainer from "../parts-order-modal/parts-order-modal.container";
|
||||
import _ from "lodash";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -295,18 +296,17 @@ export function JobLinesComponent({
|
||||
onClick={async () => {
|
||||
await deleteJobLine({
|
||||
variables: { joblineId: record.id },
|
||||
// update(cache) {
|
||||
// cache.modify({
|
||||
// id: cache.identify(job),
|
||||
// fields: {
|
||||
// joblines(existingJobLines, { readField }) {
|
||||
// return existingJobLines.filter(
|
||||
// (jlRef) => record.id !== readField("id", jlRef)
|
||||
// );
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// },
|
||||
update(cache) {
|
||||
cache.modify({
|
||||
fields: {
|
||||
joblines(existingJobLines, { readField }) {
|
||||
return existingJobLines.filter(
|
||||
(jlRef) => record.id !== readField("id", jlRef)
|
||||
);
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
await axios.post("/job/totalsssu", {
|
||||
id: job.id,
|
||||
@@ -334,10 +334,12 @@ export function JobLinesComponent({
|
||||
const markedTypes = [e.key];
|
||||
if (e.key === "PAN") markedTypes.push("PAP");
|
||||
if (e.key === "PAS") markedTypes.push("PASL");
|
||||
setSelectedLines([
|
||||
...selectedLines,
|
||||
...jobLines.filter((item) => markedTypes.includes(item.part_type)),
|
||||
]);
|
||||
setSelectedLines(
|
||||
_.uniq([
|
||||
...selectedLines,
|
||||
...jobLines.filter((item) => markedTypes.includes(item.part_type)),
|
||||
])
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Form, Input, InputNumber, Modal, Select } from "antd";
|
||||
import { Form, Input, InputNumber, Modal, Select, Switch } from "antd";
|
||||
import React, { useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import InputCurrency from "../form-items-formatted/currency-form-item.component";
|
||||
@@ -184,9 +184,9 @@ export default function JobLinesUpsertModalComponent({
|
||||
>
|
||||
<InputNumber precision={0} min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("joblines.fields.db_price")} name="db_price">
|
||||
{/* <Form.Item label={t("joblines.fields.db_price")} name="db_price">
|
||||
<InputCurrency precision={2} min={0} />
|
||||
</Form.Item>
|
||||
</Form.Item> */}
|
||||
<Form.Item
|
||||
label={t("joblines.fields.act_price")}
|
||||
name="act_price"
|
||||
@@ -222,6 +222,14 @@ export default function JobLinesUpsertModalComponent({
|
||||
>
|
||||
<InputNumber precision={0} min={0} max={100} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("joblines.fields.tax_part")}
|
||||
name="tax_part"
|
||||
valuePropName="checked"
|
||||
initialValue={true}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
@@ -44,6 +44,7 @@ function JobLinesUpsertModalContainer({
|
||||
},
|
||||
],
|
||||
},
|
||||
refetchQueries: ["GET_LINE_TICKET_BY_PK"],
|
||||
});
|
||||
if (!r.errors) {
|
||||
await Axios.post("/job/totalsssu", {
|
||||
@@ -69,6 +70,7 @@ function JobLinesUpsertModalContainer({
|
||||
lineId: jobLineEditModal.context.id,
|
||||
line: values,
|
||||
},
|
||||
refetchQueries: ["GET_LINE_TICKET_BY_PK"],
|
||||
});
|
||||
if (!r.errors) {
|
||||
notification["success"]({
|
||||
|
||||
@@ -108,6 +108,9 @@ export default function JobReconciliationBillsTable({
|
||||
rowSelection={{
|
||||
onChange: handleOnRowClick,
|
||||
selectedRowKeys: selectedLines,
|
||||
getCheckboxProps: (record) => {
|
||||
return { disabled: record.deductedfromlbr };
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ export const reconcileByAssocLine = (
|
||||
const [selectedJobLines, setSelectedJobLines] = jobLineState;
|
||||
|
||||
const allJoblinesFromBills = billLines
|
||||
.filter((bl) => bl.joblineid && !(bl.jobline && bl.jobline.removed))
|
||||
.filter((bl) => bl.joblineid && bl.jobline && !bl.jobline.removed)
|
||||
.map((bl) => bl.joblineid);
|
||||
|
||||
const duplicatedJobLinesbyInvoiceId = _.filter(
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Form, InputNumber, notification, Popover } from "antd";
|
||||
import { useMutation, useLazyQuery } from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Form,
|
||||
InputNumber,
|
||||
notification,
|
||||
Popover,
|
||||
Space,
|
||||
} from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState } from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries";
|
||||
import {
|
||||
INSERT_SCOREBOARD_ENTRY,
|
||||
QUERY_SCOREBOARD_ENTRY,
|
||||
UPDATE_SCOREBOARD_ENTRY,
|
||||
} from "../../graphql/scoreboard.queries";
|
||||
import FormDatePicker from "../form-date-picker/form-date-picker.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
|
||||
export default function ScoreboardAddButton({
|
||||
job,
|
||||
@@ -14,17 +27,46 @@ export default function ScoreboardAddButton({
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [insertScoreboardEntry] = useMutation(INSERT_SCOREBOARD_ENTRY);
|
||||
const [updateScoreboardEntry] = useMutation(UPDATE_SCOREBOARD_ENTRY);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [visibility, setVisibility] = useState(false);
|
||||
const [callQuery, { loading: entryLoading, data: entryData }] = useLazyQuery(
|
||||
QUERY_SCOREBOARD_ENTRY
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (visibility) {
|
||||
callQuery({ variables: { jobid: job.id } });
|
||||
}
|
||||
}, [visibility, job.id, callQuery]);
|
||||
|
||||
useEffect(() => {
|
||||
console.log("UE", entryData);
|
||||
if (entryData && entryData.scoreboard && entryData.scoreboard[0]) {
|
||||
console.log("Setting FOrm");
|
||||
form.setFieldsValue(entryData.scoreboard[0]);
|
||||
}
|
||||
}, [entryData, form]);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
logImEXEvent("job_close_add_to_scoreboard");
|
||||
|
||||
setLoading(true);
|
||||
const result = await insertScoreboardEntry({
|
||||
variables: { sbInput: [{ jobid: job.id, ...values }] },
|
||||
});
|
||||
let result;
|
||||
|
||||
if (entryData && entryData.scoreboard && entryData.scoreboard[0]) {
|
||||
result = await updateScoreboardEntry({
|
||||
variables: {
|
||||
sbId: entryData.scoreboard[0].id,
|
||||
sbInput: values,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
result = await insertScoreboardEntry({
|
||||
variables: { sbInput: [{ jobid: job.id, ...values }] },
|
||||
});
|
||||
}
|
||||
|
||||
if (!!result.errors) {
|
||||
notification["error"]({
|
||||
@@ -44,53 +86,62 @@ export default function ScoreboardAddButton({
|
||||
const overlay = (
|
||||
<Card>
|
||||
<div>
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
initialValues={{}}
|
||||
>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.date")}
|
||||
name="date"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
{entryLoading ? (
|
||||
<LoadingSpinner />
|
||||
) : (
|
||||
<Form
|
||||
form={form}
|
||||
layout="vertical"
|
||||
onFinish={handleFinish}
|
||||
initialValues={{}}
|
||||
>
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.bodyhrs")}
|
||||
name="bodyhrs"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.painthrs")}
|
||||
name="painthrs"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.date")}
|
||||
name="date"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDatePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.bodyhrs")}
|
||||
name="bodyhrs"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={1} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("scoreboard.fields.painthrs")}
|
||||
name="painthrs"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber precision={1} />
|
||||
</Form.Item>
|
||||
|
||||
<Button type="primary" htmlType="submit">
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
</Form>
|
||||
<Space wrap>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
<Button onClick={() => setVisibility(false)}>
|
||||
{t("general.actions.cancel")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
@@ -99,7 +150,7 @@ export default function ScoreboardAddButton({
|
||||
setLoading(true);
|
||||
const v = job.joblines.reduce(
|
||||
(acc, val) => {
|
||||
if (val.mod_lbr_ty === "LAB")
|
||||
if (val.mod_lbr_ty !== "LAR")
|
||||
acc = { ...acc, bodyhrs: acc.bodyhrs + val.mod_lb_hrs };
|
||||
if (val.mod_lbr_ty === "LAR")
|
||||
acc = { ...acc, painthrs: acc.painthrs + val.mod_lb_hrs };
|
||||
|
||||
@@ -74,7 +74,12 @@ export default function JobsAdminDatesChange({ job }) {
|
||||
<Form.Item label={t("jobs.fields.actual_in")} name="actual_in">
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.date_last_contacted")}
|
||||
name="date_last_contacted"
|
||||
>
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.scheduled_completion")}
|
||||
name="scheduled_completion"
|
||||
|
||||
@@ -99,8 +99,8 @@ export function JobsAvailableContainer({
|
||||
return;
|
||||
}
|
||||
//IO-539 Check for Parts Rate on PAL for SGI use case.
|
||||
await CheckTaxRates(estData, bodyshop);
|
||||
|
||||
await CheckTaxRates(estData.est_data, bodyshop);
|
||||
console.log(estData);
|
||||
const newTotals = (
|
||||
await Axios.post("/job/totals", {
|
||||
job: {
|
||||
@@ -140,16 +140,17 @@ export function JobsAvailableContainer({
|
||||
: {}),
|
||||
};
|
||||
|
||||
if (selectedOwner) {
|
||||
newJob.ownerid = selectedOwner;
|
||||
delete newJob.owner;
|
||||
}
|
||||
if (newJob.vehicleid) {
|
||||
delete newJob.vehicle;
|
||||
}
|
||||
|
||||
insertNewJob({
|
||||
variables: {
|
||||
job: selectedOwner
|
||||
? Object.assign(
|
||||
{},
|
||||
newJob,
|
||||
{ owner: null },
|
||||
{ ownerid: selectedOwner }
|
||||
)
|
||||
: newJob,
|
||||
job: newJob,
|
||||
},
|
||||
})
|
||||
.then((r) => {
|
||||
@@ -199,11 +200,10 @@ export function JobsAvailableContainer({
|
||||
message: t("jobs.errors.creating", { error: "No job data present." }),
|
||||
});
|
||||
} else {
|
||||
//IO-539 Check for Parts Rate on PAL for SGI use case.
|
||||
await CheckTaxRates(estData, bodyshop);
|
||||
|
||||
//create upsert job
|
||||
let supp = replaceEmpty({ ...estData.est_data });
|
||||
//IO-539 Check for Parts Rate on PAL for SGI use case.
|
||||
await CheckTaxRates(supp, bodyshop);
|
||||
|
||||
delete supp.owner;
|
||||
delete supp.vehicle;
|
||||
@@ -391,101 +391,113 @@ function replaceEmpty(someObj, replaceValue = null) {
|
||||
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);
|
||||
}
|
||||
|
||||
async function CheckTaxRates(estData, bodyshop) {
|
||||
//LKQ Check
|
||||
if (
|
||||
!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
|
||||
!estData.parts_tax_rates?.PAL ||
|
||||
estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
|
||||
) {
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.est_data.parts_tax_rates.PAL) {
|
||||
estData.est_data.parts_tax_rates.PAL = {
|
||||
if (!estData.parts_tax_rates.PAL) {
|
||||
estData.parts_tax_rates.PAL = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAL",
|
||||
};
|
||||
}
|
||||
estData.est_data.parts_tax_rates.PAL.prt_tax_rt =
|
||||
estData.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;
|
||||
estData.parts_tax_rates.PAL.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
//PAC Check
|
||||
if (
|
||||
!estData.est_data.parts_tax_rates?.PAC ||
|
||||
estData.est_data.parts_tax_rates?.PAC?.prt_tax_rt === null ||
|
||||
estData.est_data.parts_tax_rates?.PAC?.prt_tax_rt === 0
|
||||
!estData.parts_tax_rates?.PAC ||
|
||||
estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
|
||||
) {
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.est_data.parts_tax_rates.PAC) {
|
||||
estData.est_data.parts_tax_rates.PAC = {
|
||||
if (!estData.parts_tax_rates.PAC) {
|
||||
estData.parts_tax_rates.PAC = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAC",
|
||||
};
|
||||
}
|
||||
estData.est_data.parts_tax_rates.PAC.prt_tax_rt =
|
||||
estData.parts_tax_rates.PAC.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.est_data.parts_tax_rates.PAC.prt_tax_in = true;
|
||||
estData.parts_tax_rates.PAC.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
//PAM Check
|
||||
if (
|
||||
!estData.est_data.parts_tax_rates?.PAM ||
|
||||
estData.est_data.parts_tax_rates?.PAM?.prt_tax_rt === null ||
|
||||
estData.est_data.parts_tax_rates?.PAM?.prt_tax_rt === 0
|
||||
!estData.parts_tax_rates?.PAM ||
|
||||
estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
|
||||
) {
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.est_data.parts_tax_rates.PAM) {
|
||||
estData.est_data.parts_tax_rates.PAM = {
|
||||
if (!estData.parts_tax_rates.PAM) {
|
||||
estData.parts_tax_rates.PAM = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAM",
|
||||
};
|
||||
}
|
||||
estData.est_data.parts_tax_rates.PAM.prt_tax_rt =
|
||||
estData.parts_tax_rates.PAM.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.est_data.parts_tax_rates.PAM.prt_tax_in = true;
|
||||
estData.parts_tax_rates.PAM.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
!estData.est_data.parts_tax_rates?.PAR ||
|
||||
estData.est_data.parts_tax_rates?.PAR?.prt_tax_rt === null ||
|
||||
estData.est_data.parts_tax_rates?.PAR?.prt_tax_rt === 0
|
||||
!estData.parts_tax_rates?.PAR ||
|
||||
estData.parts_tax_rates?.PAR?.prt_tax_rt === null ||
|
||||
estData.parts_tax_rates?.PAR?.prt_tax_rt === 0
|
||||
) {
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
|
||||
);
|
||||
if (res) {
|
||||
if (!estData.est_data.parts_tax_rates.PAR) {
|
||||
estData.est_data.parts_tax_rates.PAR = {
|
||||
if (!estData.parts_tax_rates.PAR) {
|
||||
estData.parts_tax_rates.PAR = {
|
||||
prt_discp: 0,
|
||||
prt_mktyp: true,
|
||||
prt_mkupp: 0,
|
||||
prt_type: "PAR",
|
||||
};
|
||||
}
|
||||
estData.est_data.parts_tax_rates.PAR.prt_tax_rt =
|
||||
estData.parts_tax_rates.PAR.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.est_data.parts_tax_rates.PAR.prt_tax_in = true;
|
||||
estData.parts_tax_rates.PAR.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
|
||||
//IO-1387 If a sublet line is NOT R&R, use the labor tax. If it is, use the sublet tax rate.
|
||||
//Currently limited to SK shops only.
|
||||
//if (bodyshop.region_config === "CA_SK") {
|
||||
estData.joblines.data.forEach((jl, index) => {
|
||||
if (
|
||||
(jl.part_type === "PASL" || jl.part_type === "PAS") &&
|
||||
jl.lbr_op !== "OP11"
|
||||
) {
|
||||
estData.joblines.data[index].tax_part = jl.lbr_tax;
|
||||
}
|
||||
});
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ export function JobsCloseExportButton({
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const handleQbxml = async () => {
|
||||
//Check if it's a CDK setup.
|
||||
if (bodyshop.cdk_dealerid) {
|
||||
history.push(`/manage/dms?jobId=${jobId}`);
|
||||
return;
|
||||
@@ -41,48 +42,58 @@ export function JobsCloseExportButton({
|
||||
logImEXEvent("jobs_close_export");
|
||||
|
||||
setLoading(true);
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{ jobIds: [jobId] },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("handle -> XML", QbXmlResponse);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
//Check if it's a QBO Setup.
|
||||
let PartnerResponse;
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
// "http://609feaeae986.ngrok.io/qb/",
|
||||
QbXmlResponse.data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting-partner"),
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
||||
withCredentials: true,
|
||||
jobIds: [jobId],
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
} else {
|
||||
//Default is QBD
|
||||
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{ jobIds: [jobId] },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("handle -> XML", QbXmlResponse);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
// "http://609feaeae986.ngrok.io/qb/",
|
||||
QbXmlResponse.data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting-partner"),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("PartnerResponse", PartnerResponse);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useMutation } from "@apollo/client";
|
||||
import {
|
||||
Button,
|
||||
Form,
|
||||
Input,
|
||||
notification,
|
||||
Popover,
|
||||
Select,
|
||||
@@ -112,24 +113,32 @@ export function JobsConvertButton({
|
||||
</Form.Item>
|
||||
)}
|
||||
{bodyshop.enforce_referral && (
|
||||
<Form.Item
|
||||
name={"referral_source"}
|
||||
label={t("jobs.fields.referralsource")}
|
||||
rules={[
|
||||
{
|
||||
required: bodyshop.enforce_referral,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
{bodyshop.md_referral_sources.map((s) => (
|
||||
<Select.Option key={s} value={s}>
|
||||
{s}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<>
|
||||
<Form.Item
|
||||
name={"referral_source"}
|
||||
label={t("jobs.fields.referralsource")}
|
||||
rules={[
|
||||
{
|
||||
required: bodyshop.enforce_referral,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
{bodyshop.md_referral_sources.map((s) => (
|
||||
<Select.Option key={s} value={s}>
|
||||
{s}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.referral_source_extra")}
|
||||
name="referral_source_extra"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</>
|
||||
)}
|
||||
<Form.Item
|
||||
label={t("jobs.fields.ca_gst_registrant")}
|
||||
|
||||
@@ -181,6 +181,12 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.referral_source_extra")}
|
||||
name="referral_source_extra"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
</Collapse.Panel>
|
||||
<Collapse.Panel
|
||||
|
||||
@@ -129,6 +129,19 @@ export default function JobsCreateOwnerInfoNewComponent() {
|
||||
>
|
||||
<FormItemPhone disabled={!state.owner.new} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_ph2")}
|
||||
name={["owner", "data", "ownr_ph2"]}
|
||||
rules={[
|
||||
({ getFieldValue }) =>
|
||||
PhoneItemFormatterValidation(
|
||||
getFieldValue,
|
||||
"owner.data.ownr_ph2"
|
||||
),
|
||||
]}
|
||||
>
|
||||
<FormItemPhone disabled={!state.owner.new} />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow>
|
||||
<Form.Item
|
||||
|
||||
@@ -84,6 +84,18 @@ export default function JobsCreateOwnerInfoSearchComponent({
|
||||
tableState.sortedInfo.columnKey === "ownr_ph1" &&
|
||||
tableState.sortedInfo.order,
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
render: (text, record) => (
|
||||
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
|
||||
),
|
||||
sorter: (a, b) => alphaSort(a.ownr_ph2, b.ownr_ph2),
|
||||
sortOrder:
|
||||
tableState.sortedInfo.columnKey === "ownr_ph2" &&
|
||||
tableState.sortedInfo.order,
|
||||
},
|
||||
];
|
||||
|
||||
const handleTableChange = (pagination, filters, sorter) => {
|
||||
|
||||
@@ -69,6 +69,12 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
</Tooltip>
|
||||
</FormRow>
|
||||
<FormRow header={t("jobs.forms.repairdates")}>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.date_last_contacted")}
|
||||
name="date_last_contacted"
|
||||
>
|
||||
<DateTimePicker />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.scheduled_completion")}
|
||||
name="scheduled_completion"
|
||||
@@ -93,7 +99,6 @@ export function JobsDetailDatesComponent({ jobRO, job, bodyshop }) {
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.scheduled_delivery")}
|
||||
name="scheduled_delivery"
|
||||
|
||||
@@ -114,6 +114,12 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.referral_source_extra")}
|
||||
name="referral_source_extra"
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.alt_transport")} name="alt_transport">
|
||||
<Select disabled={jobRO} allowClear>
|
||||
{bodyshop.appt_alt_transport.map((s) => (
|
||||
|
||||
@@ -104,7 +104,7 @@ export function JobsDetailHeaderCsi({
|
||||
replyTo: bodyshop.email,
|
||||
},
|
||||
template: {
|
||||
name: TemplateList("job").csi_invitation.key,
|
||||
name: TemplateList("job_special").csi_invitation_action.key,
|
||||
variables: {
|
||||
id: result.data.insert_csi.returning[0].id,
|
||||
},
|
||||
|
||||
@@ -141,6 +141,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
<DataLabel key="2" label={t("jobs.fields.ownr_ph1")}>
|
||||
<ChatOpenButton phone={job.ownr_ph1} jobid={job.id} />
|
||||
</DataLabel>
|
||||
<DataLabel key="22" label={t("jobs.fields.ownr_ph2")}>
|
||||
<ChatOpenButton phone={job.ownr_ph2} jobid={job.id} />
|
||||
</DataLabel>
|
||||
<DataLabel key="3" label={t("owners.fields.address")}>
|
||||
{`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${
|
||||
job.ownr_city || ""
|
||||
|
||||
@@ -38,7 +38,7 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
label={t("jobs.fields.depreciation_taxes")}
|
||||
name="depreciation_taxes"
|
||||
>
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
<Tooltip title={t("jobs.labels.ca_gst_all_if_null")}>
|
||||
<Form.Item
|
||||
@@ -47,6 +47,7 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
>
|
||||
<CurrencyInput
|
||||
disabled={jobRO}
|
||||
min={0}
|
||||
max={
|
||||
Math.round(
|
||||
(job.job_totals &&
|
||||
@@ -61,19 +62,19 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
label={t("jobs.fields.other_amount_payable")}
|
||||
name="other_amount_payable"
|
||||
>
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.towing_payable")}
|
||||
name="towing_payable"
|
||||
>
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.storage_payable")}
|
||||
name="storage_payable"
|
||||
>
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.adjustment_bottom_line")}
|
||||
@@ -83,7 +84,7 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
</Form.Item>
|
||||
<Space align="end">
|
||||
<Form.Item label={t("jobs.fields.ca_bc_pvrt")} name="ca_bc_pvrt">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
<CABCpvrtCalculator form={form} disabled={jobRO} />
|
||||
</Space>
|
||||
@@ -135,72 +136,72 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_laa")} name="rate_laa">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lab")} name="rate_lab">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lad")} name="rate_lad">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lae")} name="rate_lae">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lar")} name="rate_lar">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_las")} name="rate_las">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_laf")} name="rate_laf">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lam")} name="rate_lam">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lag")} name="rate_lag">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_la1")} name="rate_la1">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_la2")} name="rate_la2">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_la3")} name="rate_la3">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_la4")} name="rate_la4">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_lau")} name="rate_lau">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_mapa")} name="rate_mapa">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_mash")} name="rate_mash">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_mahw")} name="rate_mahw">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_ma2s")} name="rate_ma2s">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.rate_ma3s")} name="rate_ma3s">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
{
|
||||
// <Form.Item label={t("jobs.fields.rate_mabl")} name="rate_mabl">
|
||||
// <CurrencyInput disabled={jobRO} />
|
||||
// <CurrencyInput min={0}disabled={jobRO} />
|
||||
// </Form.Item>
|
||||
// <Form.Item label={t("jobs.fields.rate_macs")} name="rate_macs">
|
||||
// <CurrencyInput disabled={jobRO} />
|
||||
// <CurrencyInput min={0}disabled={jobRO} />
|
||||
// </Form.Item>
|
||||
}
|
||||
<Form.Item label={t("jobs.fields.rate_matd")} name="rate_matd">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
</FormRow>
|
||||
<JobsDetailRatesParts form={form} />
|
||||
|
||||
@@ -29,20 +29,6 @@ function JobsDocumentsComponent({
|
||||
setIndex(index);
|
||||
};
|
||||
|
||||
// useEffect(() => {
|
||||
// console.log("Added event listening for reteching.");
|
||||
// window.addEventListener("storage", (ev) => {
|
||||
// if (ev.key === "refetch" && ev.newValue === true) {
|
||||
// refetch && refetch();
|
||||
// localStorage.setItem("refetch", false);
|
||||
// }
|
||||
// });
|
||||
|
||||
// return () => {
|
||||
// window.removeEventListener("storage");
|
||||
// };
|
||||
// }, [refetch]);
|
||||
|
||||
useEffect(() => {
|
||||
let documents = data.reduce(
|
||||
(acc, value) => {
|
||||
|
||||
@@ -34,53 +34,61 @@ export function JobsExportAllButton({
|
||||
const [loading, setLoading] = useState(false);
|
||||
const handleQbxml = async () => {
|
||||
logImEXEvent("jobs_export_all");
|
||||
|
||||
setLoading(true);
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{ jobIds: jobIds },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let PartnerResponse;
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
// "http://609feaeae986.ngrok.io/qb/",
|
||||
QbXmlResponse.data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting-partner"),
|
||||
setLoading(true);
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
||||
withCredentials: true,
|
||||
jobIds: jobIds,
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{ jobIds: jobIds },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
// "http://609feaeae986.ngrok.io/qb/",
|
||||
QbXmlResponse.data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting-partner"),
|
||||
});
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
console.log("PartnerResponse", PartnerResponse);
|
||||
const groupedData = _.groupBy(PartnerResponse.data, "id");
|
||||
const groupedData = _.groupBy(
|
||||
PartnerResponse.data,
|
||||
bodyshop.accountingconfig.qbo ? "jobid" : "id"
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(groupedData).map(async (key) => {
|
||||
@@ -157,6 +165,7 @@ export function JobsExportAllButton({
|
||||
|
||||
if (!!completedCallback) completedCallback([]);
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
|
||||
@@ -69,6 +69,20 @@ export default function JobsFindModalComponent({
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
width: "12%",
|
||||
ellipsis: true,
|
||||
render: (text, record) => {
|
||||
return record.ownr_ph2 ? (
|
||||
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
|
||||
) : (
|
||||
t("general.labels.unknown")
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
dataIndex: "status",
|
||||
|
||||
@@ -73,6 +73,16 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
|
||||
<StartChatButton phone={record.ownr_ph1} jobid={record.id} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
width: "12%",
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<StartChatButton phone={record.ownr_ph2} jobid={record.id} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
dataIndex: "status",
|
||||
|
||||
@@ -145,6 +145,16 @@ export function JobsList({ bodyshop }) {
|
||||
<ChatOpenButton phone={record.ownr_ph1} jobid={record.id} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
responsive: ["md"],
|
||||
render: (text, record) => (
|
||||
<ChatOpenButton phone={record.ownr_ph2} jobid={record.id} />
|
||||
),
|
||||
},
|
||||
|
||||
{
|
||||
title: t("jobs.fields.status"),
|
||||
|
||||
@@ -81,6 +81,16 @@ export default function OwnerDetailFormComponent({ form, loading }) {
|
||||
>
|
||||
<FormItemPhone />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("owners.fields.ownr_ph2")}
|
||||
name="ownr_ph2"
|
||||
rules={[
|
||||
({ getFieldValue }) =>
|
||||
PhoneItemFormatterValidation(getFieldValue, "ownr_ph2"),
|
||||
]}
|
||||
>
|
||||
<FormItemPhone />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("owners.fields.preferred_contact")}
|
||||
name="preferred_contact"
|
||||
|
||||
@@ -51,6 +51,14 @@ export default function OwnerFindModalComponent({
|
||||
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
render: (text, record) => (
|
||||
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
const handleOnRowClick = (record) => {
|
||||
|
||||
@@ -16,6 +16,9 @@ export default function OwnerTagPopoverComponent({ job }) {
|
||||
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}>
|
||||
<PhoneFormatter>{job.ownr_ph1 || ""}</PhoneFormatter>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item key="22" label={t("jobs.fields.ownr_ph2")}>
|
||||
<PhoneFormatter>{job.ownr_ph2 || ""}</PhoneFormatter>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item key="3" label={t("owners.fields.address")}>
|
||||
{`${job.ownr_addr1 || ""} ${job.ownr_addr2 || ""} ${
|
||||
job.ownr_city || ""
|
||||
@@ -36,6 +39,12 @@ export default function OwnerTagPopoverComponent({ job }) {
|
||||
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph1")}>
|
||||
<PhoneFormatter>{job.owner.ownr_ph1 || ""}</PhoneFormatter>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item key="22" label={t("jobs.fields.ownr_ph2")}>
|
||||
<PhoneFormatter>{job.owner.ownr_ph2 || ""}</PhoneFormatter>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item key="2" label={t("jobs.fields.ownr_ph2")}>
|
||||
<PhoneFormatter>{job.owner.ownr_ph2 || ""}</PhoneFormatter>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item key="3" label={t("owners.fields.address")}>
|
||||
{`${job.owner.ownr_addr1 || ""} ${job.owner.ownr_addr2 || ""} ${
|
||||
job.owner.ownr_city || ""
|
||||
|
||||
@@ -47,6 +47,14 @@ export default function OwnersListComponent({
|
||||
return <PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
render: (text, record) => {
|
||||
return <PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("owners.fields.ownr_ea"),
|
||||
dataIndex: "ownr_ea",
|
||||
|
||||
@@ -27,6 +27,7 @@ import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import DataLabel from "../data-label/data-label.component";
|
||||
import PartsOrderBackorderEta from "../parts-order-backorder-eta/parts-order-backorder-eta.component";
|
||||
import PartsOrderLineBackorderButton from "../parts-order-line-backorder-button/parts-order-line-backorder-button.component";
|
||||
import PartsReceiveModalContainer from "../parts-receive-modal/parts-receive-modal.container";
|
||||
@@ -379,6 +380,9 @@ export function PartsOrderListTableComponent({
|
||||
rowKey="id"
|
||||
dataSource={record.parts_order_lines}
|
||||
/>
|
||||
<DataLabel label={t("parts_orders.fields.comments")}>
|
||||
<div style={{ whiteSpace: "pre" }}>{record.comments}</div>
|
||||
</DataLabel>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -73,7 +73,7 @@ export default function PartsOrderModalComponent({
|
||||
<Form.Item required={false} key={field.key}>
|
||||
<LayoutFormRow grow noDivider>
|
||||
<Form.Item
|
||||
span={8}
|
||||
//span={8}
|
||||
label={t("parts_orders.fields.line_desc")}
|
||||
key={`${index}line_desc`}
|
||||
name={[field.name, "line_desc"]}
|
||||
@@ -132,19 +132,21 @@ export default function PartsOrderModalComponent({
|
||||
</Form.Item>
|
||||
)}
|
||||
|
||||
<div>
|
||||
<DeleteFilled
|
||||
style={{ margin: "1rem" }}
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
<Space wrap align="center">
|
||||
<div>
|
||||
<DeleteFilled
|
||||
style={{ margin: "1rem" }}
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<FormListMoveArrows
|
||||
move={move}
|
||||
index={index}
|
||||
total={fields.length}
|
||||
/>
|
||||
</div>
|
||||
</Space>
|
||||
</LayoutFormRow>
|
||||
</Form.Item>
|
||||
))}
|
||||
@@ -152,6 +154,9 @@ export default function PartsOrderModalComponent({
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
<Form.Item name="comments" label={t("parts_orders.fields.comments")}>
|
||||
<Input.TextArea rows={3} />
|
||||
</Form.Item>
|
||||
<Radio.Group
|
||||
defaultValue={sendType}
|
||||
onChange={(e) => setSendType(e.target.value)}
|
||||
|
||||
@@ -260,7 +260,7 @@ export function PartsOrderModalContainer({
|
||||
onCancel={() => toggleModalVisible()}
|
||||
onOk={() => form.submit()}
|
||||
destroyOnClose
|
||||
width="50%"
|
||||
width="75%"
|
||||
forceRender
|
||||
>
|
||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||
|
||||
@@ -35,52 +35,61 @@ export function PayableExportAll({
|
||||
|
||||
const handleQbxml = async () => {
|
||||
logImEXEvent("accounting_payables_export_all");
|
||||
let PartnerResponse;
|
||||
|
||||
setLoading(true);
|
||||
if (!!loadingCallback) loadingCallback(true);
|
||||
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/payables",
|
||||
{ bills: billids },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("bills.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
||||
withCredentials: true,
|
||||
bills: billids,
|
||||
});
|
||||
if (loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/payables",
|
||||
{ bills: billids },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("bills.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
if (loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
let PartnerResponse;
|
||||
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
QbXmlResponse.data
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("bills.errors.exporting-partner"),
|
||||
});
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
QbXmlResponse.data
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("bills.errors.exporting-partner"),
|
||||
});
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("handleQbxml -> PartnerResponse", PartnerResponse);
|
||||
const groupedData = _.groupBy(PartnerResponse.data, "id");
|
||||
const groupedData = _.groupBy(
|
||||
PartnerResponse.data,
|
||||
bodyshop.accountingconfig.qbo ? "billid" : "id"
|
||||
);
|
||||
|
||||
const proms = [];
|
||||
Object.keys(groupedData).forEach((key) => {
|
||||
proms.push(
|
||||
|
||||
@@ -38,44 +38,53 @@ export function PayableExportButton({
|
||||
setLoading(true);
|
||||
if (!!loadingCallback) loadingCallback(true);
|
||||
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/payables",
|
||||
{ bills: [billId] },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("bills.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
if (loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
//Check if it's a QBO Setup.
|
||||
let PartnerResponse;
|
||||
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
QbXmlResponse.data
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("bills.errors.exporting-partner"),
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/payables`, {
|
||||
withCredentials: true,
|
||||
bills: [billId],
|
||||
});
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
} else {
|
||||
//Default is QBD
|
||||
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/payables",
|
||||
{ bills: [billId] },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("bills.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
if (loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
QbXmlResponse.data
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("bills.errors.exporting-partner"),
|
||||
});
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("handleQbxml -> PartnerResponse", PartnerResponse);
|
||||
@@ -123,7 +132,14 @@ export function PayableExportButton({
|
||||
});
|
||||
const billUpdateResponse = await updateBill({
|
||||
variables: {
|
||||
billIdList: successfulTransactions.map((st) => st.id),
|
||||
billIdList: successfulTransactions.map(
|
||||
(st) =>
|
||||
st[
|
||||
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
|
||||
? "billid"
|
||||
: "id"
|
||||
]
|
||||
),
|
||||
bill: {
|
||||
exported: true,
|
||||
exported_at: new Date(),
|
||||
|
||||
@@ -1,28 +1,35 @@
|
||||
import { Card, Col, Input, Row, Space, Typography } from "antd";
|
||||
import _ from "lodash";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectPrintCenter } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { TemplateList } from "../../utils/TemplateConstants";
|
||||
import Jobd3RdPartyModal from "../job-3rd-party-modal/job-3rd-party-modal.component";
|
||||
import PrintCenterItem from "../print-center-item/print-center-item.component";
|
||||
import PrintCenterSpeedPrint from "../print-center-speed-print/print-center-speed-print.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
printCenterModal: selectPrintCenter,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
|
||||
export function PrintCenterJobsComponent({ printCenterModal }) {
|
||||
export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
|
||||
const [search, setSearch] = useState("");
|
||||
const { id: jobId } = printCenterModal.context;
|
||||
const tempList = TemplateList("job", {});
|
||||
const { t } = useTranslation();
|
||||
const JobsReportsList = Object.keys(tempList).map((key) => {
|
||||
return tempList[key];
|
||||
});
|
||||
const JobsReportsList = Object.keys(tempList)
|
||||
.map((key) => {
|
||||
return tempList[key];
|
||||
})
|
||||
.filter(
|
||||
(temp) =>
|
||||
!temp.regions || (temp.regions && temp.regions[bodyshop.region_config])
|
||||
);
|
||||
|
||||
const filteredJobsReportsList =
|
||||
search !== ""
|
||||
|
||||
@@ -38,6 +38,7 @@ export function ProductionBoardFilters({
|
||||
}}
|
||||
/>
|
||||
<EmployeeSearchSelectComponent
|
||||
style={{ minWidth: "20rem" }}
|
||||
options={bodyshop.employees.filter((e) => e.active)}
|
||||
value={filter.employeeId}
|
||||
placeholder={t("production.labels.employeesearch")}
|
||||
|
||||
@@ -15,6 +15,7 @@ import ProductionListColumnPaintPriority from "./production-list-columns.paintpr
|
||||
import ProductionListColumnNote from "./production-list-columns.productionnote.component";
|
||||
import ProductionListColumnStatus from "./production-list-columns.status.component";
|
||||
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
||||
import ProductionListLastContacted from "./production-list-columns.lastcontacted.component";
|
||||
|
||||
const r = ({ technician, state }) => {
|
||||
return [
|
||||
@@ -97,6 +98,17 @@ const r = ({ technician, state }) => {
|
||||
<ProductionListDate record={record} field="scheduled_completion" />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.t("jobs.fields.date_last_contacted"),
|
||||
dataIndex: "date_last_contacted",
|
||||
key: "date_last_contacted",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => dateSort(a.date_last_contacted, b.date_last_contacted),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "date_last_contacted" &&
|
||||
state.sortedInfo.order,
|
||||
render: (text, record) => <ProductionListLastContacted record={record} />,
|
||||
},
|
||||
{
|
||||
title: i18n.t("jobs.fields.scheduled_delivery"),
|
||||
dataIndex: "scheduled_delivery",
|
||||
@@ -161,6 +173,15 @@ const r = ({ technician, state }) => {
|
||||
<PhoneFormatter>{record.ownr_ph1}</PhoneFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.t("jobs.fields.ownr_ph2"),
|
||||
dataIndex: "ownr_ph2",
|
||||
key: "ownr_ph2",
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<PhoneFormatter>{record.ownr_ph2}</PhoneFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: i18n.t("jobs.fields.specialcoveragepolicy"),
|
||||
dataIndex: "special_coverage_policy",
|
||||
|
||||
@@ -1,17 +1,20 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { DatePicker, Dropdown } from "antd";
|
||||
import { DatePicker, Dropdown, TimePicker, Button, Card } from "antd";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const OneCalendarDay = 60 * 60 * 24 * 1000;
|
||||
const Now = new Date();
|
||||
|
||||
export default function ProductionListDate({ record, field }) {
|
||||
export default function ProductionListDate({ record, field, time }) {
|
||||
const [updateAlert] = useMutation(UPDATE_JOB);
|
||||
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const handleChange = (date) => {
|
||||
logImEXEvent("product_toggle_date", { field });
|
||||
|
||||
@@ -25,27 +28,47 @@ export default function ProductionListDate({ record, field }) {
|
||||
},
|
||||
}).then(() => {
|
||||
if (record.refetch) record.refetch();
|
||||
if (!time) {
|
||||
setVisible(false);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dropdown
|
||||
trigger={["click"]}
|
||||
//trigger={["click"]}
|
||||
visible={visible}
|
||||
style={{
|
||||
height: "19px",
|
||||
}}
|
||||
overlay={
|
||||
<div onClick={(e) => e.stopPropagation()}>
|
||||
<Card
|
||||
style={{ padding: "1rem" }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<DatePicker
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
value={(record[field] && moment(record[field])) || null}
|
||||
onChange={handleChange}
|
||||
format="MM/DD/YYYY"
|
||||
/>
|
||||
</div>
|
||||
{time && (
|
||||
<TimePicker
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
value={(record[field] && moment(record[field])) || null}
|
||||
onChange={handleChange}
|
||||
format="hh:mm a"
|
||||
/>
|
||||
)}
|
||||
<Button onClick={() => setVisible(false)}>
|
||||
{t("general.actions.close")}
|
||||
</Button>
|
||||
</Card>
|
||||
}
|
||||
>
|
||||
<div
|
||||
onClick={() => setVisible(true)}
|
||||
style={{
|
||||
height: "19px",
|
||||
}}
|
||||
|
||||
@@ -0,0 +1,132 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Dropdown, Form, Input, notification, Space } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { UPDATE_JOB } from "../../graphql/jobs.queries";
|
||||
import { INSERT_NEW_NOTE } from "../../graphql/notes.queries";
|
||||
import { selectCurrentUser } from "../../redux/user/user.selectors";
|
||||
import { DateFormatter } from "../../utils/DateFormatter";
|
||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
currentUser: selectCurrentUser,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ProductionLastContacted);
|
||||
|
||||
export function ProductionLastContacted({ currentUser, record }) {
|
||||
const [updateAlert] = useMutation(UPDATE_JOB);
|
||||
const [insertNote] = useMutation(INSERT_NEW_NOTE);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const [form] = Form.useForm();
|
||||
const handleFinish = async ({ date_last_contacted, note }) => {
|
||||
logImEXEvent("production_last_contacted");
|
||||
|
||||
//e.stopPropagation();
|
||||
const res = await updateAlert({
|
||||
variables: {
|
||||
jobId: record.id,
|
||||
job: {
|
||||
date_last_contacted,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (res.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("jobs.errors.saving", {
|
||||
error: JSON.stringify(res.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (note && note.trim() !== "") {
|
||||
//Insert a note.
|
||||
const res2 = await insertNote({
|
||||
variables: {
|
||||
noteInput: {
|
||||
jobid: record.id,
|
||||
text: note,
|
||||
created_by: currentUser.email,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (res2.errors) {
|
||||
notification.open({
|
||||
type: "error",
|
||||
message: t("notes.errors.inserting", {
|
||||
error: JSON.stringify(res.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
if (record.refetch) record.refetch();
|
||||
|
||||
setVisible(false);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (visible) {
|
||||
form.setFieldsValue({
|
||||
note: null,
|
||||
date_last_contacted:
|
||||
record.date_last_contacted && moment(record.date_last_contacted),
|
||||
});
|
||||
}
|
||||
}, [visible, form, record.date_last_contacted]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Dropdown
|
||||
//trigger={["click"]}
|
||||
visible={visible}
|
||||
style={{
|
||||
height: "19px",
|
||||
}}
|
||||
overlay={
|
||||
<Card
|
||||
style={{ padding: "1rem" }}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Form form={form} onFinish={handleFinish} layout="vertical">
|
||||
<Form.Item name="date_last_contacted">
|
||||
<FormDateTimePickerComponent />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("notes.labels.notetoadd")} name="note">
|
||||
<Input.TextArea rows={4} />
|
||||
</Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
<Button onClick={() => setVisible(false)}>
|
||||
{t("general.actions.close")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</Card>
|
||||
}
|
||||
>
|
||||
<div
|
||||
onClick={() => setVisible(true)}
|
||||
style={{
|
||||
height: "19px",
|
||||
}}
|
||||
>
|
||||
<DateFormatter bordered={false}>
|
||||
{record.date_last_contacted}
|
||||
</DateFormatter>
|
||||
</div>
|
||||
</Dropdown>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -77,6 +77,10 @@ export default function ProductionListDetail({ jobs }) {
|
||||
phone={data.jobs_by_pk.ownr_ph1}
|
||||
jobid={data.jobs_by_pk.id}
|
||||
/>
|
||||
<StartChatButton
|
||||
phone={data.jobs_by_pk.ownr_ph2}
|
||||
jobid={data.jobs_by_pk.id}
|
||||
/>
|
||||
</Descriptions.Item>
|
||||
<Descriptions.Item label={t("jobs.fields.vehicle")}>
|
||||
{`${theJob.v_model_yr || ""} ${theJob.v_color || ""} ${
|
||||
|
||||
@@ -66,12 +66,7 @@ export function ProductionListSaveConfigButton({
|
||||
};
|
||||
const popMenu = (
|
||||
<div>
|
||||
<Form
|
||||
layout="vertical"
|
||||
form={form}
|
||||
onFinish={handleSaveConfig}
|
||||
initialValues={{ driveable: true, towin: false }}
|
||||
>
|
||||
<Form layout="vertical" form={form} onFinish={handleSaveConfig}>
|
||||
<Form.Item
|
||||
label={t("production.labels.viewname")}
|
||||
name="name"
|
||||
|
||||
@@ -55,6 +55,19 @@ export function ProductionListTable({
|
||||
if (assoc) {
|
||||
await updateDefaultProdView({
|
||||
variables: { assocId: assoc.id, view: value },
|
||||
update(cache) {
|
||||
cache.modify({
|
||||
id: cache.identify(bodyshop),
|
||||
fields: {
|
||||
associations(existingAssociations, { readField }) {
|
||||
return existingAssociations.map((a) => {
|
||||
if (a.useremail !== currentUser.email) return a;
|
||||
return { ...a, default_prod_list_view: value };
|
||||
});
|
||||
},
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -85,13 +98,18 @@ export function ProductionListTable({
|
||||
|
||||
setState(bodyshop.production_config[0].columns.tableState);
|
||||
};
|
||||
const assoc = bodyshop.associations.find(
|
||||
(a) => a.useremail === currentUser.email
|
||||
);
|
||||
|
||||
const defaultView = assoc && assoc.default_prod_list_view;
|
||||
return (
|
||||
<div style={{ width: "10rem" }}>
|
||||
<Select
|
||||
onSelect={handleSelect}
|
||||
placeholder={t("production.labels.selectview")}
|
||||
optionLabelProp="label"
|
||||
defaultValue={defaultView}
|
||||
>
|
||||
{bodyshop.production_config.map((config) => (
|
||||
<Select.Option key={config.name} label={config.name}>
|
||||
|
||||
@@ -32,12 +32,11 @@ export function ProductionListTable({
|
||||
}) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const defaultView = useMemo(() => {
|
||||
const assoc = bodyshop.associations.find(
|
||||
(a) => a.useremail === currentUser.email
|
||||
);
|
||||
return assoc && assoc.default_prod_list_view;
|
||||
}, [bodyshop.associations, currentUser.email]);
|
||||
const assoc = bodyshop.associations.find(
|
||||
(a) => a.useremail === currentUser.email
|
||||
);
|
||||
|
||||
const defaultView = assoc && assoc.default_prod_list_view;
|
||||
|
||||
const [state, setState] = useState(
|
||||
(bodyshop.production_config &&
|
||||
|
||||
@@ -1,19 +1,18 @@
|
||||
import { Button, Space } from "antd";
|
||||
import { Space, Tag } from "antd";
|
||||
import Axios from "axios";
|
||||
import React, { useEffect } from "react";
|
||||
//import QboImg from "./qbo_signin.png";
|
||||
import queryString from "query-string";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import React, { useEffect } from "react";
|
||||
import { useCookies } from "react-cookie";
|
||||
import { useHistory, useLocation } from "react-router-dom";
|
||||
import QboSignIn from "../../assets/qbo/C2QB_green_btn_med_default.svg";
|
||||
|
||||
export default function QboAuthorizeComponent() {
|
||||
const location = useLocation();
|
||||
|
||||
const [, setCookie] = useCookies(["access_token", "refresh_token"]);
|
||||
const history = useHistory();
|
||||
const [cookies, setCookie] = useCookies(["access_token", "refresh_token"]);
|
||||
|
||||
const handleQbSignIn = async () => {
|
||||
const result = await Axios.post("/qbo/authorize");
|
||||
console.log("pushing to history", result.data);
|
||||
window.location.href = result.data;
|
||||
};
|
||||
const qs = queryString.parse(location.search);
|
||||
@@ -35,42 +34,24 @@ export default function QboAuthorizeComponent() {
|
||||
path: "/",
|
||||
expires,
|
||||
});
|
||||
|
||||
history.push({ pathname: `/manage/accounting/receivables` });
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [qs, location, setCookie]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Space wrap>
|
||||
<Button onClick={handleQbSignIn}>
|
||||
{/* <img
|
||||
src={QboImg}
|
||||
alt="Sign in with Intuit"
|
||||
onClick={handleQbSignIn}
|
||||
/> */}
|
||||
auth
|
||||
</Button>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const response = await Axios.get(`/qbo/refresh`, {
|
||||
withCredentials: true,
|
||||
});
|
||||
console.log(response);
|
||||
}}
|
||||
>
|
||||
Refresh Token
|
||||
</Button>
|
||||
<Button
|
||||
onClick={async () => {
|
||||
const response = await Axios.post(`/qbo/receivables`, {
|
||||
withCredentials: true,
|
||||
});
|
||||
console.log(response);
|
||||
}}
|
||||
>
|
||||
REC
|
||||
</Button>
|
||||
</Space>
|
||||
<Space>
|
||||
<img
|
||||
onClick={handleQbSignIn}
|
||||
alt="Sign In to QuickBooks Online"
|
||||
src={QboSignIn}
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
{!cookies.qbo_realmId && (
|
||||
<Tag color="red">No QuickBooks company has been connected.</Tag>
|
||||
)}
|
||||
{error && JSON.parse(decodeURIComponent(error)).error_description}
|
||||
</div>
|
||||
</Space>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -124,7 +124,7 @@ export function ScheduleJobModalComponent({
|
||||
handleDateBlur();
|
||||
}}
|
||||
>
|
||||
<DateFormatter>{d}</DateFormatter>
|
||||
<DateFormatter includeDay>{d}</DateFormatter>
|
||||
</Button>
|
||||
))}
|
||||
</Space>
|
||||
|
||||
@@ -18,6 +18,11 @@ 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 graphProps = {
|
||||
strokeWidth: 3,
|
||||
};
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
@@ -51,7 +56,7 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
||||
}
|
||||
|
||||
const theValue = {
|
||||
date: moment(val).format("D dd"),
|
||||
date: moment(val).format("D ddd"),
|
||||
paintHrs: _.round(dayhrs.painthrs, 1),
|
||||
bodyHrs: _.round(dayhrs.bodyhrs, 1),
|
||||
accTargetHrs: _.round(
|
||||
@@ -81,36 +86,37 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
||||
margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
|
||||
>
|
||||
<CartesianGrid stroke="#f5f5f5" />
|
||||
<XAxis dataKey="date" />
|
||||
<YAxis />
|
||||
<XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} />
|
||||
<YAxis strokeWidth={graphProps.strokeWidth} />
|
||||
<Tooltip />
|
||||
<Legend />
|
||||
<Area
|
||||
type="monotone"
|
||||
name="Accumulated Hours"
|
||||
dataKey="accHrs"
|
||||
fill="#8884d8"
|
||||
stroke="#8884d8"
|
||||
fill="lightgreen"
|
||||
stroke="green"
|
||||
/>
|
||||
<Bar
|
||||
name="Body Hours"
|
||||
dataKey="bodyHrs"
|
||||
stackId="day"
|
||||
barSize={20}
|
||||
fill="#cecece"
|
||||
fill="darkblue"
|
||||
/>
|
||||
<Bar
|
||||
name="Paint Hours"
|
||||
dataKey="paintHrs"
|
||||
stackId="day"
|
||||
barSize={20}
|
||||
fill="#413ea0"
|
||||
fill="darkred"
|
||||
/>
|
||||
<Line
|
||||
name="Target Hours"
|
||||
type="monotone"
|
||||
dataKey="accTargetHrs"
|
||||
stroke="#ff7300"
|
||||
strokeWidth={graphProps.strokeWidth}
|
||||
/>
|
||||
</ComposedChart>
|
||||
</ResponsiveContainer>
|
||||
|
||||
@@ -1,12 +1,33 @@
|
||||
import { Col, Row } from "antd";
|
||||
import React from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import ScoreboardChart from "../scoreboard-chart/scoreboard-chart.component";
|
||||
import ScoreboardLastDays from "../scoreboard-last-days/scoreboard-last-days.component";
|
||||
import ScoreboardTargetsTable from "../scoreboard-targets-table/scoreboard-targets-table.component";
|
||||
|
||||
export default function ScoreboardDisplayComponent({ scoreboardSubscription }) {
|
||||
const { data } = scoreboardSubscription;
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import moment from "moment";
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { GET_BLOCKED_DAYS } from "../../graphql/scoreboard.queries";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ScoreboardDisplayComponent);
|
||||
|
||||
export function ScoreboardDisplayComponent({
|
||||
bodyshop,
|
||||
scoreboardSubscription,
|
||||
}) {
|
||||
const { data } = scoreboardSubscription;
|
||||
const client = useApolloClient();
|
||||
const scoreBoardlist = (data && data.scoreboard) || [];
|
||||
|
||||
const sbEntriesByDate = {};
|
||||
@@ -19,6 +40,29 @@ export default function ScoreboardDisplayComponent({ scoreboardSubscription }) {
|
||||
sbEntriesByDate[entryDate].push(i);
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
//Update the locals.
|
||||
async function setMomentSettings() {
|
||||
const {
|
||||
data: { appointments },
|
||||
} = await client.query({
|
||||
query: GET_BLOCKED_DAYS,
|
||||
variables: {
|
||||
start: moment().startOf("month"),
|
||||
end: moment().endOf("month"),
|
||||
},
|
||||
});
|
||||
|
||||
moment.updateLocale("ca", {
|
||||
workingWeekdays: translateSettingsToWorkingDays(bodyshop.workingdays),
|
||||
holidays: appointments.map((h) => moment(h.start).format("MM-DD-YYYY")),
|
||||
holidayFormat: "MM-DD-YYYY",
|
||||
});
|
||||
}
|
||||
|
||||
setMomentSettings();
|
||||
}, [client, bodyshop]);
|
||||
|
||||
return (
|
||||
<Row gutter={[16, 16]}>
|
||||
<Col span={24}>
|
||||
@@ -35,3 +79,30 @@ export default function ScoreboardDisplayComponent({ scoreboardSubscription }) {
|
||||
</Row>
|
||||
);
|
||||
}
|
||||
|
||||
function translateSettingsToWorkingDays(workingdays) {
|
||||
const days = [];
|
||||
|
||||
if (workingdays.monday) {
|
||||
days.push(1);
|
||||
}
|
||||
if (workingdays.tuesday) {
|
||||
days.push(2);
|
||||
}
|
||||
if (workingdays.wednesday) {
|
||||
days.push(3);
|
||||
}
|
||||
if (workingdays.thursday) {
|
||||
days.push(4);
|
||||
}
|
||||
if (workingdays.friday) {
|
||||
days.push(5);
|
||||
}
|
||||
if (workingdays.saturday) {
|
||||
days.push(6);
|
||||
}
|
||||
if (workingdays.sunday) {
|
||||
days.push(0);
|
||||
}
|
||||
return days;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import moment from "moment-business-days";
|
||||
|
||||
moment.updateLocale("ca", {
|
||||
workingWeekdays: [1, 2, 3, 4, 5],
|
||||
});
|
||||
// moment.updateLocale("ca", {
|
||||
// workingWeekdays: [1, 2, 3, 4, 5, 6],
|
||||
// });
|
||||
|
||||
export const CalculateWorkingDaysThisMonth = () => {
|
||||
return moment().endOf("month").businessDaysIntoMonth();
|
||||
|
||||
@@ -121,6 +121,13 @@ export default function ShopInfoGeneral({ form }) {
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")}>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.qbo")}
|
||||
valuePropName="checked"
|
||||
name={["accountingconfig", "qbo"]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.accountingtiers")}
|
||||
rules={[
|
||||
|
||||
@@ -42,7 +42,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_lab")}
|
||||
@@ -55,7 +55,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_lad")}
|
||||
@@ -68,7 +68,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_lae")}
|
||||
@@ -81,7 +81,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_laf")}
|
||||
@@ -94,7 +94,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_lag")}
|
||||
@@ -107,7 +107,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_lam")}
|
||||
@@ -120,7 +120,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_lar")}
|
||||
@@ -133,7 +133,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_las")}
|
||||
@@ -146,7 +146,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_la1")}
|
||||
@@ -159,7 +159,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_la2")}
|
||||
@@ -172,7 +172,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_la3")}
|
||||
@@ -185,7 +185,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_la4")}
|
||||
@@ -198,7 +198,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_mash")}
|
||||
@@ -211,7 +211,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_mapa")}
|
||||
@@ -224,7 +224,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_ma2s")}
|
||||
@@ -237,7 +237,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_ma3s")}
|
||||
@@ -250,7 +250,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
{
|
||||
// <Form.Item
|
||||
@@ -264,7 +264,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
// },
|
||||
// ]}
|
||||
// >
|
||||
// <CurrencyInput />
|
||||
// <CurrencyInput min={0} />
|
||||
// </Form.Item>
|
||||
// <Form.Item
|
||||
// label={t("jobs.fields.rate_macs")}
|
||||
@@ -277,7 +277,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
// },
|
||||
// ]}
|
||||
// >
|
||||
// <CurrencyInput />
|
||||
// <CurrencyInput min={0} />
|
||||
// </Form.Item>
|
||||
}
|
||||
<Form.Item
|
||||
@@ -291,7 +291,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.rate_mahw")}
|
||||
@@ -304,7 +304,7 @@ export default function ShopInfoLaborRates({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
|
||||
@@ -515,6 +515,27 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t(
|
||||
"bodyshop.fields.responsibilitycenters.laa"
|
||||
)}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
key={`${index}costs-LAA`}
|
||||
name={[field.name, "costs", "LAA"]}
|
||||
>
|
||||
<Select>
|
||||
{costOptions.map((item, idx) => (
|
||||
<Select.Option key={idx} value={item}>
|
||||
{item}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t(
|
||||
"bodyshop.fields.responsibilitycenters.lab"
|
||||
@@ -1086,6 +1107,27 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t(
|
||||
"bodyshop.fields.responsibilitycenters.laa"
|
||||
)}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
key={`${index}profits-LAA`}
|
||||
name={[field.name, "profits", "LAA"]}
|
||||
>
|
||||
<Select>
|
||||
{profitOptions.map((item, idx) => (
|
||||
<Select.Option key={idx} value={item}>
|
||||
{item}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t(
|
||||
"bodyshop.fields.responsibilitycenters.lab"
|
||||
@@ -1673,6 +1715,24 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.laa")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_responsibility_centers", "defaults", "costs", "LAA"]}
|
||||
>
|
||||
<Select>
|
||||
{costOptions.map((item, idx) => (
|
||||
<Select.Option key={idx} value={item}>
|
||||
{item}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.lab")}
|
||||
rules={[
|
||||
@@ -2161,6 +2221,24 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.laa")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
name={["md_responsibility_centers", "defaults", "profits", "LAA"]}
|
||||
>
|
||||
<Select>
|
||||
{profitOptions.map((item, idx) => (
|
||||
<Select.Option key={idx} value={item}>
|
||||
{item}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.responsibilitycenters.lab")}
|
||||
rules={[
|
||||
|
||||
@@ -50,7 +50,9 @@ export function TechClockInComponent({ form, bodyshop, technician }) {
|
||||
{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>
|
||||
|
||||
@@ -139,7 +139,9 @@ export function TechClockOffButton({
|
||||
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>
|
||||
))
|
||||
)}
|
||||
|
||||
@@ -83,7 +83,9 @@ export function TechClockedInList({ technician }) {
|
||||
<DateTimeFormatter>{ticket.clockon}</DateTimeFormatter>
|
||||
</DataLabel>
|
||||
<DataLabel label={t("timetickets.fields.cost_center")}>
|
||||
{ticket.cost_center}
|
||||
{ticket.cost_center === "timetickets.labels.shift"
|
||||
? t(ticket.cost_center)
|
||||
: ticket.cost_center}
|
||||
</DataLabel>
|
||||
</Card>
|
||||
</List.Item>
|
||||
|
||||
@@ -63,12 +63,12 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
|
||||
null,
|
||||
...additionalParams,
|
||||
};
|
||||
console.log(
|
||||
"%c[Analytics]",
|
||||
"background-color: green ;font-weight:bold;",
|
||||
eventName,
|
||||
eventParams
|
||||
);
|
||||
// console.log(
|
||||
// "%c[Analytics]",
|
||||
// "background-color: green ;font-weight:bold;",
|
||||
// eventName,
|
||||
// eventParams
|
||||
// );
|
||||
logEvent(analytics, eventName, eventParams);
|
||||
|
||||
//Log event to OpenReplay server.
|
||||
|
||||
@@ -28,6 +28,7 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
|
||||
ownr_co_nm
|
||||
ownr_fn
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ea
|
||||
clm_total
|
||||
id
|
||||
@@ -126,6 +127,7 @@ export const QUERY_APPOINTMENT_BY_DATE = gql`
|
||||
ownr_ln
|
||||
ownr_fn
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ea
|
||||
clm_total
|
||||
id
|
||||
|
||||
@@ -93,6 +93,7 @@ export const QUERY_BILLS_BY_JOBID = gql`
|
||||
backordered_on
|
||||
}
|
||||
order_number
|
||||
comments
|
||||
user_email
|
||||
}
|
||||
bills(where: { jobid: { _eq: $jobid } }, order_by: { date: desc }) {
|
||||
|
||||
@@ -10,6 +10,7 @@ export const QUERY_ALL_ACTIVE_JOBS = gql`
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ea
|
||||
owner {
|
||||
id
|
||||
@@ -77,7 +78,9 @@ export const QUERY_PARTS_QUEUE = gql`
|
||||
) {
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ea
|
||||
plate_no
|
||||
plate_st
|
||||
@@ -98,6 +101,7 @@ export const QUERY_PARTS_QUEUE = gql`
|
||||
status
|
||||
updated_at
|
||||
vehicleid
|
||||
ownerid
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -110,6 +114,7 @@ export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
|
||||
ro_number
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
v_model_yr
|
||||
v_model_desc
|
||||
clm_no
|
||||
@@ -119,9 +124,11 @@ export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
|
||||
actual_in
|
||||
scheduled_completion
|
||||
scheduled_delivery
|
||||
date_last_contacted
|
||||
ins_co_nm
|
||||
clm_total
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
special_coverage_policy
|
||||
owner_owing
|
||||
production_vars
|
||||
@@ -356,6 +363,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
kmin
|
||||
kmout
|
||||
referral_source
|
||||
referral_source_extra
|
||||
unit_number
|
||||
po_number
|
||||
special_coverage_policy
|
||||
@@ -451,6 +459,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
ownr_zip
|
||||
ownr_ctry
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
production_vars
|
||||
ca_gst_registrant
|
||||
ownerid
|
||||
@@ -467,6 +476,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
ownr_zip
|
||||
ownr_ctry
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
}
|
||||
labor_rate_desc
|
||||
rate_la1
|
||||
@@ -505,6 +515,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
date_open
|
||||
date_scheduled
|
||||
date_invoiced
|
||||
date_last_contacted
|
||||
date_exported
|
||||
status
|
||||
owner_owing
|
||||
@@ -540,6 +551,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
db_ref
|
||||
manual_line
|
||||
prt_dsmk_p
|
||||
prt_dsmk_m
|
||||
billlines(limit: 1, order_by: { bill: { date: desc } }) {
|
||||
id
|
||||
quantity
|
||||
@@ -559,6 +571,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
parts_order {
|
||||
id
|
||||
order_number
|
||||
comments
|
||||
order_date
|
||||
user_email
|
||||
vendor {
|
||||
@@ -622,7 +635,7 @@ export const GET_JOB_RECONCILIATION_BY_PK = gql`
|
||||
is_credit_memo
|
||||
isinhouse
|
||||
exported
|
||||
billlines {
|
||||
billlines(where: { deductedfromlbr: { _eq: false } }) {
|
||||
actual_price
|
||||
quantity
|
||||
actual_cost
|
||||
@@ -680,8 +693,10 @@ export const QUERY_JOB_CARD_DETAILS = gql`
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ea
|
||||
ca_gst_registrant
|
||||
owner_owing
|
||||
special_coverage_policy
|
||||
available_jobs {
|
||||
id
|
||||
@@ -741,6 +756,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
|
||||
scheduled_in
|
||||
scheduled_delivery
|
||||
date_invoiced
|
||||
date_last_contacted
|
||||
date_open
|
||||
date_exported
|
||||
|
||||
@@ -826,6 +842,7 @@ export const QUERY_TECH_JOB_DETAILS = gql`
|
||||
scheduled_in
|
||||
scheduled_delivery
|
||||
date_invoiced
|
||||
date_last_contacted
|
||||
date_open
|
||||
date_exported
|
||||
voided
|
||||
@@ -960,6 +977,7 @@ export const CONVERT_JOB_TO_RO = gql`
|
||||
$driveable: Boolean
|
||||
$towin: Boolean
|
||||
$referral_source: String
|
||||
$referral_source_extra: String
|
||||
) {
|
||||
update_jobs(
|
||||
where: { id: { _eq: $jobId } }
|
||||
@@ -971,6 +989,7 @@ export const CONVERT_JOB_TO_RO = gql`
|
||||
towin: $towin
|
||||
driveable: $driveable
|
||||
referral_source: $referral_source
|
||||
referral_source_extra: $referral_source_extra
|
||||
}
|
||||
) {
|
||||
returning {
|
||||
@@ -980,6 +999,7 @@ export const CONVERT_JOB_TO_RO = gql`
|
||||
class
|
||||
ins_co_nm
|
||||
referral_source
|
||||
referral_source_extra
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1004,6 +1024,7 @@ export const GET_JOB_INFO_FOR_STRIPE = gql`
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ea
|
||||
}
|
||||
}
|
||||
@@ -1493,6 +1514,7 @@ export const QUERY_ALL_JOB_FIELDS = gql`
|
||||
rate_mash
|
||||
rate_matd
|
||||
referral_source
|
||||
referral_source_extra
|
||||
regie_number
|
||||
selling_dealer
|
||||
selling_dealer_contact
|
||||
@@ -1636,8 +1658,10 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
|
||||
) {
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
ownerid
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
ownr_ea
|
||||
plate_no
|
||||
plate_st
|
||||
@@ -1853,6 +1877,7 @@ export const GET_JOB_FOR_CC_CONTRACT = gql`
|
||||
ownr_city
|
||||
ownr_zip
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -51,3 +51,34 @@ export const UPDATE_SCOREBOARD_ENTRY = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_SCOREBOARD_ENTRY = gql`
|
||||
query QUERY_SCOREBOARD_ENTRY($jobid: uuid!) {
|
||||
scoreboard(where: { jobid: { _eq: $jobid } }) {
|
||||
bodyhrs
|
||||
date
|
||||
id
|
||||
painthrs
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const GET_BLOCKED_DAYS = gql`
|
||||
query GET_BLOCKED_DAYS($start: timestamptz, $end: timestamptz) {
|
||||
appointments(
|
||||
where: {
|
||||
_and: [
|
||||
{ block: { _eq: true } }
|
||||
{ canceled: { _eq: false } }
|
||||
{ start: { _gte: $start } }
|
||||
{ end: { _lte: $end } }
|
||||
]
|
||||
}
|
||||
) {
|
||||
id
|
||||
block
|
||||
start
|
||||
end
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -23,6 +23,7 @@ export const GLOBAL_SEARCH_QUERY = gql`
|
||||
ownr_ln
|
||||
ownr_co_nm
|
||||
ownr_ph1
|
||||
ownr_ph2
|
||||
}
|
||||
search_vehicles(args: { search: $search }) {
|
||||
id
|
||||
|
||||
@@ -612,7 +612,7 @@ export const Pricing11DataSource = {
|
||||
<br /> 14500GB流量包
|
||||
<br /> 14国内按峰值宽带账单
|
||||
<br /> 14弹性计算
|
||||
<br /> 14云服务器 ECS{" "}
|
||||
<br /> 14云服务器 ECS
|
||||
</span>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -85,7 +85,6 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
});
|
||||
};
|
||||
|
||||
console.log("Manual State", state);
|
||||
const handleFinish = (values) => {
|
||||
let job = Object.assign(
|
||||
{},
|
||||
@@ -142,6 +141,10 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
}
|
||||
|
||||
job = { ...job, ...ownerData };
|
||||
|
||||
if (job.owner === null) delete job.owner;
|
||||
if (job.vehicle === null) delete job.vehicle;
|
||||
|
||||
runInsertJob(job);
|
||||
};
|
||||
|
||||
|
||||
@@ -116,8 +116,8 @@ export function PartsQueuePageComponent({ bodyshop }) {
|
||||
// sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
|
||||
// sortOrder: sortcolumn === "owner" && sortorder,
|
||||
render: (text, record) => {
|
||||
return record.owner ? (
|
||||
<Link to={"/manage/owners/" + record.owner.id}>
|
||||
return record.ownerid ? (
|
||||
<Link to={"/manage/owners/" + record.ownerid}>
|
||||
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
|
||||
record.ownr_co_nm || ""
|
||||
}`}
|
||||
|
||||
@@ -396,6 +396,7 @@
|
||||
"la2": "LA2",
|
||||
"la3": "LA3",
|
||||
"la4": "LA4",
|
||||
"laa": "Aluminum",
|
||||
"lab": "Body",
|
||||
"lad": "Diagnostic",
|
||||
"lae": "Electrical",
|
||||
@@ -509,6 +510,7 @@
|
||||
"orderstatuses": "Order Statuses",
|
||||
"partslocations": "Parts Locations",
|
||||
"printlater": "Print Later",
|
||||
"qbo": "Use QuickBooks Online?",
|
||||
"rbac": "Role Based Access Control",
|
||||
"responsibilitycenters": {
|
||||
"costs": "Cost Centers",
|
||||
@@ -1053,6 +1055,7 @@
|
||||
"profitcenter_part": "Profit Center: Part",
|
||||
"prt_dsmk_p": "Line Markup %",
|
||||
"status": "Status",
|
||||
"tax_part": "Tax Part",
|
||||
"total": "Total",
|
||||
"unq_seq": "Seq #"
|
||||
},
|
||||
@@ -1195,6 +1198,7 @@
|
||||
"date_estimated": "Date Estimated",
|
||||
"date_exported": "Exported",
|
||||
"date_invoiced": "Invoiced",
|
||||
"date_last_contacted": "Last Contacted Date",
|
||||
"date_open": "Open",
|
||||
"date_scheduled": "Scheduled",
|
||||
"ded_amt": "Deductible",
|
||||
@@ -1286,6 +1290,7 @@
|
||||
"owner_owing": "Cust. Owes",
|
||||
"ownr_ea": "Email",
|
||||
"ownr_ph1": "Phone 1",
|
||||
"ownr_ph2": "Phone 2",
|
||||
"paa": "Aftermarket",
|
||||
"pac": "Rechromed",
|
||||
"pae": "Existing",
|
||||
@@ -1334,6 +1339,8 @@
|
||||
"rate_mapa": "Paint Materials",
|
||||
"rate_mash": "Shop Material",
|
||||
"rate_matd": "Tire Disposal",
|
||||
"referral_source_extra": "Other Referral Source",
|
||||
"referral_source_other": "",
|
||||
"referralsource": "Referral Source",
|
||||
"regie_number": "Registration #",
|
||||
"repairtotal": "Repair Total",
|
||||
@@ -1732,6 +1739,9 @@
|
||||
"edit": "Edit Note",
|
||||
"new": "New Note"
|
||||
},
|
||||
"errors": {
|
||||
"inserting": "Error inserting note. {{error}}"
|
||||
},
|
||||
"fields": {
|
||||
"createdby": "Created By",
|
||||
"critical": "Critical",
|
||||
@@ -1740,7 +1750,8 @@
|
||||
"updatedat": "Updated At"
|
||||
},
|
||||
"labels": {
|
||||
"newnoteplaceholder": "Add a note..."
|
||||
"newnoteplaceholder": "Add a note...",
|
||||
"notetoadd": "Note to Add"
|
||||
},
|
||||
"successes": {
|
||||
"create": "Note created successfully.",
|
||||
@@ -1769,6 +1780,7 @@
|
||||
"ownr_fn": "First Name",
|
||||
"ownr_ln": "Last Name",
|
||||
"ownr_ph1": "Phone 1",
|
||||
"ownr_ph2": "Phone 2",
|
||||
"ownr_st": "Province/State",
|
||||
"ownr_title": "Title",
|
||||
"ownr_zip": "Zip/Postal Code",
|
||||
@@ -1810,9 +1822,10 @@
|
||||
"act_price": "Price",
|
||||
"backordered_eta": "B.O. ETA",
|
||||
"backordered_on": "B.O. On",
|
||||
"comments": "Comments",
|
||||
"cost": "Cost",
|
||||
"db_price": "List Price",
|
||||
"deliver_by": "Date",
|
||||
"deliver_by": "Deliver By",
|
||||
"job_line_id": "Job Line Id",
|
||||
"line_desc": "Line Description",
|
||||
"line_remarks": "Remarks",
|
||||
@@ -1952,6 +1965,7 @@
|
||||
"coversheet_landscape": "Coversheet (Landscape)",
|
||||
"coversheet_portrait": "Coversheet Portrait",
|
||||
"csi_invitation": "CSI Invitation",
|
||||
"csi_invitation_action": "CSI Invite",
|
||||
"diagnostic_authorization": "Diagnostic Authorization",
|
||||
"estimate": "Estimate Only",
|
||||
"estimate_detail": "Estimate Details",
|
||||
@@ -1964,6 +1978,7 @@
|
||||
"guarantee": "Repair Guarantee",
|
||||
"invoice_customer_payable": "Invoice (Customer Payable)",
|
||||
"invoice_total_payable": "Invoice (Total Payable)",
|
||||
"iou_form": "IOU Form",
|
||||
"job_costing_ro": "Job Costing",
|
||||
"job_notes": "Job Notes",
|
||||
"key_tag": "Key Tag",
|
||||
@@ -1982,6 +1997,8 @@
|
||||
"qc_sheet": "Quality Control Sheet",
|
||||
"ro_totals": "RO Totals",
|
||||
"ro_with_description": "RO Summary with Descriptions",
|
||||
"sgi_certificate_of_repairs": "SGI - Certificate of Repairs",
|
||||
"sgi_windshield_auth": "SGI - Windshield Authorization",
|
||||
"stolen_recovery_checklist": "Stolen Recovery Checklist",
|
||||
"supplement_request": "Supplement Request",
|
||||
"thank_you_ro": "Thank You Letter",
|
||||
@@ -2016,7 +2033,7 @@
|
||||
},
|
||||
"subjects": {
|
||||
"jobs": {
|
||||
"parts_order": "$t(printcenter.jobs.parts_order) PO: {{ro_number}}"
|
||||
"parts_order": "Parts Order PO: {{ro_number}}"
|
||||
}
|
||||
},
|
||||
"vendors": {
|
||||
|
||||
@@ -396,6 +396,7 @@
|
||||
"la2": "",
|
||||
"la3": "",
|
||||
"la4": "",
|
||||
"laa": "",
|
||||
"lab": "",
|
||||
"lad": "",
|
||||
"lae": "",
|
||||
@@ -509,6 +510,7 @@
|
||||
"orderstatuses": "",
|
||||
"partslocations": "",
|
||||
"printlater": "",
|
||||
"qbo": "",
|
||||
"rbac": "",
|
||||
"responsibilitycenters": {
|
||||
"costs": "",
|
||||
@@ -1053,6 +1055,7 @@
|
||||
"profitcenter_part": "",
|
||||
"prt_dsmk_p": "",
|
||||
"status": "Estado",
|
||||
"tax_part": "",
|
||||
"total": "",
|
||||
"unq_seq": "Seq #"
|
||||
},
|
||||
@@ -1195,6 +1198,7 @@
|
||||
"date_estimated": "Fecha estimada",
|
||||
"date_exported": "Exportado",
|
||||
"date_invoiced": "Facturado",
|
||||
"date_last_contacted": "",
|
||||
"date_open": "Abierto",
|
||||
"date_scheduled": "Programado",
|
||||
"ded_amt": "Deducible",
|
||||
@@ -1286,6 +1290,7 @@
|
||||
"owner_owing": "Cust. Debe",
|
||||
"ownr_ea": "Email",
|
||||
"ownr_ph1": "Teléfono 1",
|
||||
"ownr_ph2": "",
|
||||
"paa": "",
|
||||
"pac": "",
|
||||
"pae": "",
|
||||
@@ -1334,6 +1339,8 @@
|
||||
"rate_mapa": "Tasa de materiales de pintura",
|
||||
"rate_mash": "Comprar material de tarifa",
|
||||
"rate_matd": "Tasa de eliminación de neumáticos",
|
||||
"referral_source_extra": "",
|
||||
"referral_source_other": "",
|
||||
"referralsource": "Fuente de referencia",
|
||||
"regie_number": "N. ° de registro",
|
||||
"repairtotal": "Reparación total",
|
||||
@@ -1732,6 +1739,9 @@
|
||||
"edit": "Editar nota",
|
||||
"new": "Nueva nota"
|
||||
},
|
||||
"errors": {
|
||||
"inserting": ""
|
||||
},
|
||||
"fields": {
|
||||
"createdby": "Creado por",
|
||||
"critical": "Crítico",
|
||||
@@ -1740,7 +1750,8 @@
|
||||
"updatedat": "Actualizado en"
|
||||
},
|
||||
"labels": {
|
||||
"newnoteplaceholder": "Agrega una nota..."
|
||||
"newnoteplaceholder": "Agrega una nota...",
|
||||
"notetoadd": ""
|
||||
},
|
||||
"successes": {
|
||||
"create": "Nota creada con éxito.",
|
||||
@@ -1769,6 +1780,7 @@
|
||||
"ownr_fn": "Nombre de pila",
|
||||
"ownr_ln": "Apellido",
|
||||
"ownr_ph1": "Teléfono 1",
|
||||
"ownr_ph2": "",
|
||||
"ownr_st": "Provincia del estado",
|
||||
"ownr_title": "Título",
|
||||
"ownr_zip": "código postal",
|
||||
@@ -1810,6 +1822,7 @@
|
||||
"act_price": "",
|
||||
"backordered_eta": "",
|
||||
"backordered_on": "",
|
||||
"comments": "",
|
||||
"cost": "",
|
||||
"db_price": "",
|
||||
"deliver_by": "",
|
||||
@@ -1952,6 +1965,7 @@
|
||||
"coversheet_landscape": "",
|
||||
"coversheet_portrait": "",
|
||||
"csi_invitation": "",
|
||||
"csi_invitation_action": "",
|
||||
"diagnostic_authorization": "",
|
||||
"estimate": "",
|
||||
"estimate_detail": "",
|
||||
@@ -1964,6 +1978,7 @@
|
||||
"guarantee": "",
|
||||
"invoice_customer_payable": "",
|
||||
"invoice_total_payable": "",
|
||||
"iou_form": "",
|
||||
"job_costing_ro": "",
|
||||
"job_notes": "",
|
||||
"key_tag": "",
|
||||
@@ -1982,6 +1997,8 @@
|
||||
"qc_sheet": "",
|
||||
"ro_totals": "",
|
||||
"ro_with_description": "",
|
||||
"sgi_certificate_of_repairs": "",
|
||||
"sgi_windshield_auth": "",
|
||||
"stolen_recovery_checklist": "",
|
||||
"supplement_request": "",
|
||||
"thank_you_ro": "",
|
||||
|
||||
@@ -396,6 +396,7 @@
|
||||
"la2": "",
|
||||
"la3": "",
|
||||
"la4": "",
|
||||
"laa": "",
|
||||
"lab": "",
|
||||
"lad": "",
|
||||
"lae": "",
|
||||
@@ -509,6 +510,7 @@
|
||||
"orderstatuses": "",
|
||||
"partslocations": "",
|
||||
"printlater": "",
|
||||
"qbo": "",
|
||||
"rbac": "",
|
||||
"responsibilitycenters": {
|
||||
"costs": "",
|
||||
@@ -1053,6 +1055,7 @@
|
||||
"profitcenter_part": "",
|
||||
"prt_dsmk_p": "",
|
||||
"status": "Statut",
|
||||
"tax_part": "",
|
||||
"total": "",
|
||||
"unq_seq": "Seq #"
|
||||
},
|
||||
@@ -1195,6 +1198,7 @@
|
||||
"date_estimated": "Date estimée",
|
||||
"date_exported": "Exportés",
|
||||
"date_invoiced": "Facturé",
|
||||
"date_last_contacted": "",
|
||||
"date_open": "Ouvrir",
|
||||
"date_scheduled": "Prévu",
|
||||
"ded_amt": "Déductible",
|
||||
@@ -1286,6 +1290,7 @@
|
||||
"owner_owing": "Cust. Owes",
|
||||
"ownr_ea": "Email",
|
||||
"ownr_ph1": "Téléphone 1",
|
||||
"ownr_ph2": "",
|
||||
"paa": "",
|
||||
"pac": "",
|
||||
"pae": "",
|
||||
@@ -1334,6 +1339,8 @@
|
||||
"rate_mapa": "Taux de matériaux de peinture",
|
||||
"rate_mash": "Tarif du matériel de la boutique",
|
||||
"rate_matd": "Taux d'élimination des pneus",
|
||||
"referral_source_extra": "",
|
||||
"referral_source_other": "",
|
||||
"referralsource": "Source de référence",
|
||||
"regie_number": "Enregistrement #",
|
||||
"repairtotal": "Réparation totale",
|
||||
@@ -1732,6 +1739,9 @@
|
||||
"edit": "Note éditée",
|
||||
"new": "Nouvelle note"
|
||||
},
|
||||
"errors": {
|
||||
"inserting": ""
|
||||
},
|
||||
"fields": {
|
||||
"createdby": "Créé par",
|
||||
"critical": "Critique",
|
||||
@@ -1740,7 +1750,8 @@
|
||||
"updatedat": "Mis à jour à"
|
||||
},
|
||||
"labels": {
|
||||
"newnoteplaceholder": "Ajouter une note..."
|
||||
"newnoteplaceholder": "Ajouter une note...",
|
||||
"notetoadd": ""
|
||||
},
|
||||
"successes": {
|
||||
"create": "Remarque créée avec succès.",
|
||||
@@ -1769,6 +1780,7 @@
|
||||
"ownr_fn": "Prénom",
|
||||
"ownr_ln": "Nom de famille",
|
||||
"ownr_ph1": "Téléphone 1",
|
||||
"ownr_ph2": "",
|
||||
"ownr_st": "Etat / Province",
|
||||
"ownr_title": "Titre",
|
||||
"ownr_zip": "Zip / code postal",
|
||||
@@ -1810,6 +1822,7 @@
|
||||
"act_price": "",
|
||||
"backordered_eta": "",
|
||||
"backordered_on": "",
|
||||
"comments": "",
|
||||
"cost": "",
|
||||
"db_price": "",
|
||||
"deliver_by": "",
|
||||
@@ -1952,6 +1965,7 @@
|
||||
"coversheet_landscape": "",
|
||||
"coversheet_portrait": "",
|
||||
"csi_invitation": "",
|
||||
"csi_invitation_action": "",
|
||||
"diagnostic_authorization": "",
|
||||
"estimate": "",
|
||||
"estimate_detail": "",
|
||||
@@ -1964,6 +1978,7 @@
|
||||
"guarantee": "",
|
||||
"invoice_customer_payable": "",
|
||||
"invoice_total_payable": "",
|
||||
"iou_form": "",
|
||||
"job_costing_ro": "",
|
||||
"job_notes": "",
|
||||
"key_tag": "",
|
||||
@@ -1982,6 +1997,8 @@
|
||||
"qc_sheet": "",
|
||||
"ro_totals": "",
|
||||
"ro_with_description": "",
|
||||
"sgi_certificate_of_repairs": "",
|
||||
"sgi_windshield_auth": "",
|
||||
"stolen_recovery_checklist": "",
|
||||
"supplement_request": "",
|
||||
"thank_you_ro": "",
|
||||
|
||||
@@ -3,7 +3,11 @@ import moment from "moment";
|
||||
import React from "react";
|
||||
|
||||
export function DateFormatter(props) {
|
||||
return props.children ? moment(props.children).format("MM/DD/YYYY") : null;
|
||||
return props.children
|
||||
? moment(props.children).format(
|
||||
props.includeDay ? "ddd MM/DD/YYYY" : "MM/DD/YYYY"
|
||||
)
|
||||
: null;
|
||||
}
|
||||
|
||||
export function DateTimeFormatter(props) {
|
||||
|
||||
@@ -38,9 +38,9 @@ const roundTripLink = new ApolloLink((operation, forward) => {
|
||||
return forward(operation).map((data) => {
|
||||
// Called after server responds
|
||||
const time = new Date() - operation.getContext().start;
|
||||
console.log(
|
||||
`Operation ${operation.operationName} took ${time} to complete`
|
||||
);
|
||||
// console.log(
|
||||
// `Operation ${operation.operationName} took ${time} to complete`
|
||||
// );
|
||||
TrackExecutionTime(operation.operationName, time);
|
||||
return data;
|
||||
});
|
||||
|
||||
@@ -2,13 +2,13 @@ import { gql } from "@apollo/client";
|
||||
import { notification } from "antd";
|
||||
import axios from "axios";
|
||||
import jsreport from "jsreport-browser-client-dist";
|
||||
import _ from "lodash";
|
||||
import moment from "moment";
|
||||
import { auth } from "../firebase/firebase.utils";
|
||||
import { setEmailOptions } from "../redux/email/email.actions";
|
||||
import { store } from "../redux/store";
|
||||
import client from "../utils/GraphQLClient";
|
||||
import { TemplateList } from "./TemplateConstants";
|
||||
import _ from "lodash";
|
||||
const server = process.env.REACT_APP_REPORTS_SERVER_URL;
|
||||
jsreport.serverUrl = server;
|
||||
|
||||
@@ -144,6 +144,7 @@ export async function RenderTemplates(
|
||||
// ...rootTemplate.templateObject.context,
|
||||
headerpath: `/${bodyshop.imexshopid}/header.html`,
|
||||
bodyshop: bodyshop,
|
||||
offset: moment().utcOffset(),
|
||||
},
|
||||
};
|
||||
|
||||
@@ -195,35 +196,40 @@ export const GenerateDocuments = async (templates) => {
|
||||
|
||||
const fetchContextData = async (templateObject) => {
|
||||
console.log("Fetching context data", templateObject);
|
||||
const bodyshop = store.getState().user.bodyshop;
|
||||
|
||||
jsreport.headers["Authorization"] =
|
||||
"Bearer " + (await auth.currentUser.getIdToken());
|
||||
|
||||
const folders = await axios.get(`${server}/odata/folders`);
|
||||
const shopSpecificFolder = folders.data.value.find(
|
||||
(f) => f.name === bodyshop.imexshopid
|
||||
);
|
||||
|
||||
const jsReportQueries = await axios.get(
|
||||
`${server}/odata/assets?$filter=name eq '${templateObject.name}.query'`
|
||||
);
|
||||
|
||||
let templateQueryToExecute,
|
||||
useShopSpecificTemplate = false;
|
||||
if (jsReportQueries.data.value.length === 0) {
|
||||
//We have no query to execute. Just render the template.
|
||||
} else if (jsReportQueries.data.value.length === 1) {
|
||||
//We're using the default template. Get the query and execute.
|
||||
templateQueryToExecute = atob(jsReportQueries.data.value[0].content);
|
||||
} else if (jsReportQueries.data.value.length === 2) {
|
||||
//There's a custom template. Use that query instead and execute. We find it because it has a parent folder.
|
||||
templateQueryToExecute = atob(
|
||||
jsReportQueries.data.value.filter((v) => !!v.folder)[0].content
|
||||
);
|
||||
useShopSpecificTemplate = true;
|
||||
} else {
|
||||
//We have too many queries to choose from. Throw an error.
|
||||
alert(
|
||||
"There are too many queries to choose from. Please ensure there are no conflicting keys."
|
||||
);
|
||||
throw new Error(
|
||||
"There are too many queries to choose from. Please ensure there are no conflicting keys."
|
||||
let templateQueryToExecute;
|
||||
let useShopSpecificTemplate = false;
|
||||
// let shopSpecificTemplate;
|
||||
|
||||
if (shopSpecificFolder) {
|
||||
let shopSpecificTemplate = jsReportQueries.data.value.find(
|
||||
(f) => f?.folder?.shortid === shopSpecificFolder.shortid
|
||||
);
|
||||
if (shopSpecificTemplate) {
|
||||
useShopSpecificTemplate = true;
|
||||
templateQueryToExecute = atob(shopSpecificTemplate.content);
|
||||
}
|
||||
}
|
||||
|
||||
if (!templateQueryToExecute) {
|
||||
const generalTemplate = jsReportQueries.data.value.find((f) => !f.folder);
|
||||
useShopSpecificTemplate = false;
|
||||
templateQueryToExecute = atob(generalTemplate.content);
|
||||
}
|
||||
|
||||
let contextData = {};
|
||||
if (templateQueryToExecute) {
|
||||
const { data } = await client.query({
|
||||
|
||||
@@ -352,6 +352,28 @@ export const TemplateList = (type, context) => {
|
||||
disabled: false,
|
||||
group: "ro",
|
||||
},
|
||||
sgi_certificate_of_repairs: {
|
||||
title: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"),
|
||||
description: "Thank You Letter by RO",
|
||||
key: "sgi_certificate_of_repairs",
|
||||
subject: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"),
|
||||
disabled: false,
|
||||
group: "ro",
|
||||
regions: {
|
||||
CA_SK: true,
|
||||
},
|
||||
},
|
||||
sgi_windshield_auth: {
|
||||
title: i18n.t("printcenter.jobs.sgi_windshield_auth"),
|
||||
description: "Thank You Letter by RO",
|
||||
key: "sgi_windshield_auth",
|
||||
subject: i18n.t("printcenter.jobs.sgi_windshield_auth"),
|
||||
disabled: false,
|
||||
group: "pre",
|
||||
regions: {
|
||||
CA_SK: true,
|
||||
},
|
||||
},
|
||||
// parts_label_multi: {
|
||||
// title: i18n.t("printcenter.jobs.parts_label_multi"),
|
||||
// description: "Thank You Letter by RO",
|
||||
@@ -360,6 +382,14 @@ export const TemplateList = (type, context) => {
|
||||
// disabled: false,
|
||||
// group: "ro",
|
||||
// },
|
||||
iou_form: {
|
||||
title: i18n.t("printcenter.jobs.iou_form"),
|
||||
description: "CASL Authorization",
|
||||
subject: i18n.t("printcenter.jobs.iou_form"),
|
||||
key: "iou_form",
|
||||
disabled: false,
|
||||
group: "post",
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(!type || type === "job_special"
|
||||
@@ -370,6 +400,13 @@ export const TemplateList = (type, context) => {
|
||||
key: "special_thirdpartypayer",
|
||||
disabled: false,
|
||||
},
|
||||
csi_invitation_action: {
|
||||
title: i18n.t("printcenter.jobs.csi_invitation_action"),
|
||||
description: "CSI invite",
|
||||
key: "csi_invitation_action",
|
||||
subject: i18n.t("printcenter.jobs.csi_invitation_action"),
|
||||
disabled: false,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(!type || type === "appointment"
|
||||
|
||||
922
client/yarn.lock
@@ -1,2 +1,7 @@
|
||||
version: 2
|
||||
endpoint: https://bodyshop-dev-db.herokuapp.com
|
||||
admin_secret: Dev-BodyShopApp!
|
||||
metadata_directory: metadata
|
||||
actions:
|
||||
kind: synchronous
|
||||
handler_webhook_baseurl: http://localhost:3000
|
||||
|
||||
0
hasura/metadata/actions.graphql
Normal file
6
hasura/metadata/actions.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
actions: []
|
||||
custom_types:
|
||||
enums: []
|
||||
input_objects: []
|
||||
objects: []
|
||||
scalars: []
|
||||