Compare commits
76 Commits
feature/20
...
release/20
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
edb58ebc81 | ||
|
|
8d9507dce1 | ||
|
|
1c473c95a2 | ||
|
|
44c17bd7a2 | ||
|
|
5213b0b315 | ||
|
|
0541167cc5 | ||
|
|
26e47ff203 | ||
|
|
d2b2a5399d | ||
|
|
5a4d6d3e8c | ||
|
|
61a5e180f4 | ||
|
|
d5b8ea3ac5 | ||
|
|
4e87ef179b | ||
|
|
3b7c31626d | ||
|
|
a57e35354d | ||
|
|
a269fd3ad8 | ||
|
|
86bee9ad0d | ||
|
|
f8b57ca9bd | ||
|
|
5f7b780195 | ||
|
|
ba5400d04a | ||
|
|
0190e737c1 | ||
|
|
0d12ab36a9 | ||
|
|
8f52efbaca | ||
|
|
d6dc5db185 | ||
|
|
58d1859640 | ||
|
|
dba648aea2 | ||
|
|
12f6206f88 | ||
|
|
7d9fd06b6d | ||
|
|
b68de683b0 | ||
|
|
142df39cd2 | ||
|
|
ef79ccc299 | ||
|
|
6d2d96be5c | ||
|
|
8ff2a6e6c4 | ||
|
|
b793aa3394 | ||
|
|
be2cfb908a | ||
|
|
e34084b146 | ||
|
|
c1d7168260 | ||
|
|
b3a3709b72 | ||
|
|
dd59f3d026 | ||
|
|
ccb00ff391 | ||
|
|
b37970a6df | ||
|
|
2cd7fcfbd8 | ||
|
|
77819b06fe | ||
|
|
4e936b4cff | ||
|
|
b2362a85fa | ||
|
|
f45f351678 | ||
|
|
311171fa0a | ||
|
|
7e05f03f4d | ||
|
|
76826c1e80 | ||
|
|
18618efdc0 | ||
|
|
d1952dfc25 | ||
|
|
fe5f4f2727 | ||
|
|
fd7c907b8f | ||
|
|
0273255c2c | ||
|
|
e86160e530 | ||
|
|
9f7e0d611a | ||
|
|
0b523efa95 | ||
|
|
6b64499e24 | ||
|
|
e78e628bec | ||
|
|
ed8eb51c2f | ||
|
|
124d68ef68 | ||
|
|
3e802c1465 | ||
|
|
ac18e78897 | ||
|
|
fed16a4aa3 | ||
|
|
7ac1fa5abf | ||
|
|
c4fdef445e | ||
|
|
11cfef904b | ||
|
|
c9ed8a9360 | ||
|
|
7999895323 | ||
|
|
71ef3dadc5 | ||
|
|
f0d6c5e1b1 | ||
|
|
991df9c48f | ||
|
|
84b39f3d2b | ||
|
|
4ab0947cc8 | ||
|
|
8cbef14ea3 | ||
|
|
105ecd4221 | ||
|
|
3176cfcc56 |
6
_reference/Responsibility Center Setup.md
Normal file
6
_reference/Responsibility Center Setup.md
Normal file
File diff suppressed because one or more lines are too long
@@ -3575,6 +3575,95 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>dms</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>default_journal</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>dms_acctnumber</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>dms_wip_acctnumber</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>mappingname</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>
|
||||
<concept_node>
|
||||
<name>email</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -7458,6 +7547,74 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<folder_node>
|
||||
<name>dms</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>cdk_dealerid</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>title</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>
|
||||
<concept_node>
|
||||
<name>emaillater</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>employees</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -7689,6 +7846,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>printlater</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>
|
||||
@@ -12770,6 +12948,32 @@
|
||||
</concept_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>labels</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>attempts</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>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
@@ -16799,6 +17003,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>dmsautoallocate</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>export</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -18384,6 +18609,137 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<folder_node>
|
||||
<name>dms</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>center</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>
|
||||
<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_dms_acctnumber</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>dms_wip_acctnumber</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>sale</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>sale_dms_acctnumber</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>
|
||||
<concept_node>
|
||||
<name>driveable</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -34880,6 +35236,37 @@
|
||||
</folder_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>scoredboard</name>
|
||||
<children>
|
||||
<folder_node>
|
||||
<name>successes</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>updated</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>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>tech</name>
|
||||
<children>
|
||||
@@ -35451,6 +35838,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>flat_rate</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>memo</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
// craco.config.js
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
const CracoLessPlugin = require("craco-less");
|
||||
const SentryWebpackPlugin = require("@sentry/webpack-plugin");
|
||||
|
||||
module.exports = {
|
||||
plugins: [
|
||||
{
|
||||
plugin: SentryWebpackPlugin,
|
||||
options: {
|
||||
// sentry-cli configuration
|
||||
authToken:
|
||||
"6b45b028a02342db97a9a2f92c0959058665443d379d4a3a876430009e744260",
|
||||
org: "snapt-software",
|
||||
project: "imexonline",
|
||||
release: process.env.REACT_APP_GIT_SHA,
|
||||
|
||||
// webpack-specific configuration
|
||||
include: ".",
|
||||
ignore: ["node_modules", "webpack.config.js"],
|
||||
},
|
||||
},
|
||||
{
|
||||
plugin: CracoLessPlugin,
|
||||
options: {
|
||||
@@ -53,4 +69,5 @@ module.exports = {
|
||||
},
|
||||
}),
|
||||
},
|
||||
devtool: "source-map",
|
||||
};
|
||||
|
||||
43861
client/package-lock.json
generated
Normal file
43861
client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,10 @@
|
||||
"@craco/craco": "^6.2.0",
|
||||
"@fingerprintjs/fingerprintjs": "^3.2.0",
|
||||
"@lourenci/react-kanban": "^2.1.0",
|
||||
"@openreplay/tracker": "^3.2.1",
|
||||
"@openreplay/tracker-assist": "^3.0.3",
|
||||
"@openreplay/tracker-graphql": "^3.0.0",
|
||||
"@openreplay/tracker-redux": "^3.0.0",
|
||||
"@sentry/react": "^6.10.0",
|
||||
"@sentry/tracing": "^6.10.0",
|
||||
"@stripe/react-stripe-js": "^1.4.0",
|
||||
@@ -108,6 +112,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/webpack-plugin": "^1.17.1",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.2"
|
||||
}
|
||||
|
||||
@@ -8,8 +8,28 @@ import { useTranslation } from "react-i18next";
|
||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||
import client from "../utils/GraphQLClient";
|
||||
import App from "./App";
|
||||
import trackerGraphQL from "@openreplay/tracker-graphql";
|
||||
//import trackerRedux from "@openreplay/tracker-redux";
|
||||
import Tracker from "@openreplay/tracker";
|
||||
//import trackerAssist from "@openreplay/tracker-assist";
|
||||
moment.locale("en-US");
|
||||
|
||||
export const tracker = new Tracker({
|
||||
projectKey: "FPjeYIbwJyvhrVVTTLHo",
|
||||
ingestPoint: "https://replay.bodyshop.app/ingest",
|
||||
// ...(process.env.NODE_ENV === null || process.env.NODE_ENV === "development"
|
||||
// ? { __DISABLE_SECURE_MODE: true }
|
||||
// : {}),
|
||||
// beaconSize: 10485760,
|
||||
onStart: ({ sessionID }) =>
|
||||
console.log(
|
||||
"******** OpenReplay tracker started with session: ",
|
||||
sessionID
|
||||
),
|
||||
});
|
||||
//tracker.use(trackerAssist({ confirmText: "Confimr hep" })); // check the list of available options below
|
||||
export const recordGraphQL = tracker.use(trackerGraphQL());
|
||||
tracker.start();
|
||||
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
|
||||
|
||||
export default function AppContainer() {
|
||||
|
||||
@@ -109,6 +109,17 @@ export default function AccountingPayablesTableComponent({ loading, bills }) {
|
||||
<Checkbox disabled checked={record.is_credit_memo} />
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("exportlogs.labels.attempts"),
|
||||
dataIndex: "attempts",
|
||||
key: "attempts",
|
||||
|
||||
render: (text, record) => {
|
||||
const success = record.exportlogs.filter((e) => e.successful).length;
|
||||
const attempts = record.exportlogs.length;
|
||||
return `${success}/${attempts}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
@@ -121,6 +132,7 @@ export default function AccountingPayablesTableComponent({ loading, bills }) {
|
||||
billId={record.id}
|
||||
disabled={transInProgress || !!record.exported}
|
||||
loadingCallback={setTransInProgress}
|
||||
setSelectedBills={setSelectedBills}
|
||||
/>
|
||||
</div>
|
||||
),
|
||||
|
||||
@@ -108,7 +108,17 @@ export default function AccountingPayablesTableComponent({
|
||||
<DateTimeFormatter>{record.exportedat}</DateTimeFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("exportlogs.labels.attempts"),
|
||||
dataIndex: "attempts",
|
||||
key: "attempts",
|
||||
|
||||
render: (text, record) => {
|
||||
const success = record.exportlogs.filter((e) => e.successful).length;
|
||||
const attempts = record.exportlogs.length;
|
||||
return `${success}/${attempts}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
@@ -120,6 +130,7 @@ export default function AccountingPayablesTableComponent({
|
||||
paymentId={record.id}
|
||||
disabled={transInProgress || !!record.exportedat}
|
||||
loadingCallback={setTransInProgress}
|
||||
setSelectedPayments={setSelectedPayments}
|
||||
/>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -114,17 +114,28 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) {
|
||||
);
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("exportlogs.labels.attempts"),
|
||||
dataIndex: "attempts",
|
||||
key: "attempts",
|
||||
|
||||
render: (text, record) => {
|
||||
const success = record.exportlogs.filter((e) => e.successful).length;
|
||||
const attempts = record.exportlogs.length;
|
||||
return `${success}/${attempts}`;
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t("general.labels.actions"),
|
||||
dataIndex: "actions",
|
||||
key: "actions",
|
||||
sorter: (a, b) => a.clm_total - b.clm_total,
|
||||
|
||||
render: (text, record) => (
|
||||
<Space wrap>
|
||||
<JobExportButton
|
||||
jobId={record.id}
|
||||
disabled={!!record.date_exported}
|
||||
setSelectedJobs={setSelectedJobs}
|
||||
/>
|
||||
<Link to={`/manage/jobs/${record.id}/close`}>
|
||||
<Button>{t("jobs.labels.viewallocations")}</Button>
|
||||
|
||||
@@ -72,9 +72,11 @@ export function BillEnterModalLinesComponent({
|
||||
quantity: opt.part_qty || 1,
|
||||
actual_price: opt.cost,
|
||||
cost_center: opt.part_type
|
||||
? responsibilityCenters.defaults.costs[
|
||||
? responsibilityCenters.defaults &&
|
||||
(responsibilityCenters.defaults.costs[
|
||||
opt.part_type
|
||||
] || null
|
||||
] ||
|
||||
null)
|
||||
: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,13 +1,8 @@
|
||||
import Dinero from "dinero.js";
|
||||
|
||||
export const CalculateBillTotal = (invoice) => {
|
||||
const {
|
||||
total,
|
||||
billlines,
|
||||
federal_tax_rate,
|
||||
local_tax_rate,
|
||||
state_tax_rate,
|
||||
} = invoice;
|
||||
const { total, billlines, federal_tax_rate, local_tax_rate, state_tax_rate } =
|
||||
invoice;
|
||||
|
||||
//TODO Determine why this recalculates so many times.
|
||||
let subtotal = Dinero({ amount: 0 });
|
||||
@@ -20,8 +15,7 @@ export const CalculateBillTotal = (invoice) => {
|
||||
billlines.forEach((i) => {
|
||||
if (!!i) {
|
||||
const itemTotal = Dinero({
|
||||
amount:
|
||||
Math.round(((i.actual_cost || 0) * 100 + Number.EPSILON) * 100) / 100,
|
||||
amount: Math.round((i.actual_cost || 0) * 100),
|
||||
}).multiply(i.quantity || 1);
|
||||
|
||||
subtotal = subtotal.add(itemTotal);
|
||||
|
||||
@@ -83,8 +83,10 @@ export function ContractsList({
|
||||
render: (text, record) => (
|
||||
<Link to={`/manage/courtesycars/${record.courtesycar.id}`}>{`${
|
||||
record.courtesycar.year
|
||||
} ${record.courtesycar.make} ${record.courtesycar.model} ${
|
||||
record.courtesycar.plate ? `(${record.courtesycar.plate})` : ""
|
||||
} ${record.courtesycar.make} ${record.courtesycar.model}${
|
||||
record.courtesycar.plate ? ` (${record.courtesycar.plate})` : ""
|
||||
}${
|
||||
record.courtesycar.fleetnumber ? ` (${record.courtesycar.fleetnumber})` : ""
|
||||
}`}</Link>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
import { Button, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import Dinero from "dinero.js";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(DmsAllocationsSummary);
|
||||
|
||||
export function DmsAllocationsSummary({ socket, bodyshop, jobId }) {
|
||||
const { t } = useTranslation();
|
||||
const [allocationsSummary, setAllocationsSummary] = useState([]);
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.dms.center"),
|
||||
dataIndex: "center",
|
||||
key: "center",
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.sale"),
|
||||
dataIndex: "sale",
|
||||
key: "sale",
|
||||
render: (text, record) => Dinero(record.sale).toFormat(),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.cost"),
|
||||
dataIndex: "cost",
|
||||
key: "cost",
|
||||
render: (text, record) => Dinero(record.cost).toFormat(),
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.sale_dms_acctnumber"),
|
||||
dataIndex: "sale_dms_acctnumber",
|
||||
key: "sale_dms_acctnumber",
|
||||
render: (text, record) =>
|
||||
record.profitCenter && record.profitCenter.dms_acctnumber,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.cost_dms_acctnumber"),
|
||||
dataIndex: "cost_dms_acctnumber",
|
||||
key: "cost_dms_acctnumber",
|
||||
render: (text, record) =>
|
||||
record.costCenter && record.costCenter.dms_acctnumber,
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.dms_wip_acctnumber"),
|
||||
dataIndex: "dms_wip_acctnumber",
|
||||
key: "dms_wip_acctnumber",
|
||||
render: (text, record) =>
|
||||
record.costCenter && record.costCenter.dms_wip_acctnumber,
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Table
|
||||
title={() => (
|
||||
<Button
|
||||
onClick={() => {
|
||||
socket.emit("cdk-calculate-allocations", jobId, (ack) =>
|
||||
setAllocationsSummary(ack)
|
||||
);
|
||||
}}
|
||||
>
|
||||
Get
|
||||
</Button>
|
||||
)}
|
||||
pagination={{ position: "top", defaultPageSize: 50 }}
|
||||
columns={columns}
|
||||
rowKey="center"
|
||||
dataSource={allocationsSummary}
|
||||
/>
|
||||
);
|
||||
}
|
||||
115
client/src/components/dms-cdk-makes/dms-cdk-makes.component.jsx
Normal file
115
client/src/components/dms-cdk-makes/dms-cdk-makes.component.jsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import React, { useState } from "react";
|
||||
import { Modal, Button, Table, Input } from "antd";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsCdkMakes);
|
||||
|
||||
export function DmsCdkMakes({ bodyshop, form, socket }) {
|
||||
const [makesList, setMakesList] = useState([]);
|
||||
const [searchText, setSearchText] = useState("");
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [selectedModel, setSelectedModel] = useState(null);
|
||||
const { t } = useTranslation();
|
||||
const columns = [
|
||||
{
|
||||
title: t("jobs.fields.dms.makeFullName"),
|
||||
dataIndex: "makeFullName",
|
||||
key: "makeFullName",
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.modelFullName"),
|
||||
dataIndex: "modelFullName",
|
||||
key: "modelFullName",
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.makeCode"),
|
||||
dataIndex: "makeCode",
|
||||
key: "makeCode",
|
||||
},
|
||||
{
|
||||
title: t("jobs.fields.dms.modelCode"),
|
||||
dataIndex: "modelCode",
|
||||
key: "modelCode",
|
||||
},
|
||||
];
|
||||
|
||||
const filteredMakes =
|
||||
searchText !== "" && searchText
|
||||
? makesList.filter(
|
||||
(make) =>
|
||||
searchText
|
||||
.split(" ")
|
||||
.some((v) =>
|
||||
make.makeFullName.toLowerCase().includes(v.toLowerCase())
|
||||
) ||
|
||||
searchText
|
||||
.split(" ")
|
||||
.some((v) =>
|
||||
make.modelFullName.toLowerCase().includes(v.toLowerCase())
|
||||
)
|
||||
)
|
||||
: makesList;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Modal width={"90%"} visible={visible} onCancel={() => setVisible(false)}>
|
||||
<Table
|
||||
title={() => (
|
||||
<Input.Search
|
||||
onSearch={(val) => setSearchText(val)}
|
||||
placeholder={t("general.labels.search")}
|
||||
/>
|
||||
)}
|
||||
columns={columns}
|
||||
loading={loading}
|
||||
id="id"
|
||||
dataSource={filteredMakes}
|
||||
onRow={(record) => {
|
||||
return {
|
||||
onClick: setSelectedModel(record),
|
||||
};
|
||||
}}
|
||||
rowSelection={{
|
||||
onSelect: (record, selected, ...props) => {
|
||||
console.log(
|
||||
"🚀 ~ file: dms-cdk-makes.component.jsx ~ line 85 ~ record, selected, ...props",
|
||||
record,
|
||||
selected,
|
||||
...props
|
||||
);
|
||||
|
||||
setSelectedModel(record);
|
||||
},
|
||||
|
||||
type: "radio",
|
||||
selectedRowKeys: [selectedModel && selectedModel.id],
|
||||
}}
|
||||
/>
|
||||
</Modal>
|
||||
<Button
|
||||
onClick={() => {
|
||||
setVisible(true);
|
||||
setLoading(true);
|
||||
socket.emit("cdk-get-makes", bodyshop.cdk_dealerid, (makes) => {
|
||||
console.log("Called back", makes);
|
||||
setMakesList(makes);
|
||||
setLoading(false);
|
||||
});
|
||||
}}
|
||||
>
|
||||
Get Makes
|
||||
</Button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import { Button, Table } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { socket } from "../../pages/dms/dms.container";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import { alphaSort } from "../../utils/sorters";
|
||||
export default function DmsCustomerSelector() {
|
||||
const { t } = useTranslation();
|
||||
const [customerList, setcustomerList] = useState([]);
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [selectedCustomer, setSelectedCustomer] = useState(null);
|
||||
|
||||
socket.on("cdk-select-customer", (customerList, callback) => {
|
||||
setVisible(true);
|
||||
setcustomerList(customerList);
|
||||
});
|
||||
|
||||
const onOk = () => {
|
||||
setVisible(false);
|
||||
socket.emit("cdk-selected-customer", selectedCustomer);
|
||||
};
|
||||
|
||||
const columns = [
|
||||
{
|
||||
title: t("dms.fields.name1"),
|
||||
dataIndex: ["name1", "fullName"],
|
||||
key: "name1",
|
||||
sorter: (a, b) => alphaSort(a.name1?.fullName, b.name1?.fullName),
|
||||
},
|
||||
{
|
||||
title: t("dms.fields.name2"),
|
||||
dataIndex: ["name2", "fullName"],
|
||||
key: "name2",
|
||||
sorter: (a, b) => alphaSort(a.name2?.fullName, b.name2?.fullName),
|
||||
},
|
||||
{
|
||||
title: t("dms.fields.phone"),
|
||||
dataIndex: ["contactInfo", "mainTelephoneNumber", "value"],
|
||||
key: "phone",
|
||||
render: (record, value) => (
|
||||
<PhoneFormatter>
|
||||
{record.contactInfo?.mainTelephoneNumber?.value}
|
||||
</PhoneFormatter>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: t("dms.fields.address"),
|
||||
//dataIndex: ["name2", "fullName"],
|
||||
key: "address",
|
||||
render: (record, value) =>
|
||||
`${record.address?.addressLine[0]}, ${record.address?.city} ${record.address?.stateOrProvince} ${record.address?.postalCode}`,
|
||||
},
|
||||
];
|
||||
|
||||
if (!visible) return <></>;
|
||||
return (
|
||||
<Table
|
||||
title={() => (
|
||||
<div>
|
||||
<Button onClick={onOk}>Select</Button>
|
||||
</div>
|
||||
)}
|
||||
pagination={{ position: "top" }}
|
||||
columns={columns}
|
||||
rowKey={(record) => record.id.value}
|
||||
dataSource={customerList}
|
||||
//onChange={handleTableChange}
|
||||
rowSelection={{
|
||||
onSelect: (props) => {
|
||||
setSelectedCustomer(props.id.value);
|
||||
},
|
||||
type: "radio",
|
||||
selectedRowKeys: [selectedCustomer],
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
import { Divider, Space, Tag, Timeline } from "antd";
|
||||
import moment from "moment";
|
||||
import React from "react";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
|
||||
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents);
|
||||
|
||||
export function DmsLogEvents({ socket, logs, bodyshop }) {
|
||||
return (
|
||||
<Timeline pending reverse={true}>
|
||||
{logs.map((log, idx) => (
|
||||
<Timeline.Item key={idx} color={LogLevelHierarchy(log.level)}>
|
||||
<Space wrap align="start" style={{}}>
|
||||
<Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag>
|
||||
<span>{moment(log.timestamp).format("MM/DD/YYYY HH:MM:ss")}</span>
|
||||
<Divider type="vertical" />
|
||||
<span>{log.message}</span>
|
||||
</Space>
|
||||
</Timeline.Item>
|
||||
))}
|
||||
</Timeline>
|
||||
);
|
||||
}
|
||||
|
||||
function LogLevelHierarchy(level) {
|
||||
switch (level) {
|
||||
case "TRACE":
|
||||
return "pink";
|
||||
case "DEBUG":
|
||||
return "orange";
|
||||
case "INFO":
|
||||
return "blue";
|
||||
case "WARNING":
|
||||
return "yellow";
|
||||
case "ERROR":
|
||||
return "red";
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
150
client/src/components/dms-post-form/dms-post-form.component.jsx
Normal file
150
client/src/components/dms-post-form/dms-post-form.component.jsx
Normal file
@@ -0,0 +1,150 @@
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { Button, Form, Input } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import DmsCdkMakes from "../dms-cdk-makes/dms-cdk-makes.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(DmsPostForm);
|
||||
|
||||
export function DmsPostForm({ bodyshop, socket, jobId }) {
|
||||
const [form] = Form.useForm();
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Form form={form} layout="vertical">
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
name="journal"
|
||||
label={t("jobs.fields.dms.journal")}
|
||||
initialValue={
|
||||
bodyshop.cdk_configuration &&
|
||||
bodyshop.cdk_configuration.default_journal
|
||||
}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="dms_make"
|
||||
label={t("jobs.fields.dms.dms_make")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="dms_make"
|
||||
label={t("jobs.fields.dms.dms_make")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<DmsCdkMakes form={form} socket={socket} />
|
||||
</LayoutFormRow>
|
||||
|
||||
<Form.List name={["payers"]}>
|
||||
{(fields, { add, remove }) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<LayoutFormRow>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.name")}
|
||||
key={`${index}name`}
|
||||
name={[field.name, "name"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.account")}
|
||||
key={`${index}account`}
|
||||
name={[field.name, "account"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.amount")}
|
||||
key={`${index}amount`}
|
||||
name={[field.name, "amount"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<CurrencyInput />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.dms.payer.controlnumber")}
|
||||
key={`${index}controlnumber`}
|
||||
name={[field.name, "controlnumber"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
</LayoutFormRow>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{t("general.actions.add")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
@@ -117,21 +117,22 @@ export function JobChecklistForm({
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//Updae Owner Allow to Text
|
||||
const updateOwnerResult = await updateOwner({
|
||||
variables: {
|
||||
ownerId: job.owner.id,
|
||||
owner: { allow_text_message: values.allow_text_message },
|
||||
},
|
||||
});
|
||||
|
||||
if (!!updateOwnerResult.errors) {
|
||||
notification["error"]({
|
||||
message: t("checklist.errors.complete", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
if (type === "intake" && job.owner && job.owner.id) {
|
||||
//Updae Owner Allow to Text
|
||||
const updateOwnerResult = await updateOwner({
|
||||
variables: {
|
||||
ownerId: job.owner.id,
|
||||
owner: { allow_text_message: values.allow_text_message },
|
||||
},
|
||||
});
|
||||
|
||||
if (!!updateOwnerResult.errors) {
|
||||
notification["error"]({
|
||||
message: t("checklist.errors.complete", {
|
||||
error: JSON.stringify(result.errors),
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
@@ -176,7 +177,7 @@ export function JobChecklistForm({
|
||||
initialValues={{
|
||||
...(type === "intake" && {
|
||||
addToProduction: true,
|
||||
allow_text_message: job.owner.allow_text_message,
|
||||
allow_text_message: job.owner && job.owner.allow_text_message,
|
||||
scheduled_completion:
|
||||
(job && job.scheduled_completion) ||
|
||||
moment().businessAdd(
|
||||
|
||||
@@ -72,7 +72,7 @@ export function JobLineStatusPopup({ bodyshop, jobline, disabled }) {
|
||||
);
|
||||
return (
|
||||
<div
|
||||
style={{ width: "100%", minHeight: "2rem", cursor: "pointer" }}
|
||||
style={{ width: "100%", minHeight: "1rem", cursor: "pointer" }}
|
||||
onClick={() => !disabled && setEditing(true)}
|
||||
>
|
||||
{jobline.status}
|
||||
|
||||
@@ -22,10 +22,10 @@ export default function JobAdminDeleteIntake({ job }) {
|
||||
mutation DELETE_DELIVERY($jobId: uuid!) {
|
||||
update_jobs_by_pk(
|
||||
pk_columns: { id: $jobId }
|
||||
_set: { deliverychecklist: null }
|
||||
_set: { deliverchecklist: null }
|
||||
) {
|
||||
id
|
||||
deliverychecklist
|
||||
deliverchecklist
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
import { Col, notification, Row } from "antd";
|
||||
import Axios from "axios";
|
||||
import Dinero from "dinero.js";
|
||||
import _ from "lodash";
|
||||
import moment from "moment";
|
||||
import queryString from "query-string";
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
@@ -90,13 +89,7 @@ export function JobsAvailableContainer({
|
||||
|
||||
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
|
||||
|
||||
if (
|
||||
!(
|
||||
|
||||
estData &&
|
||||
estData.est_data
|
||||
)
|
||||
) {
|
||||
if (!(estData && estData.est_data)) {
|
||||
//We don't have the right data. Error!
|
||||
setInsertLoading(false);
|
||||
notification["error"]({
|
||||
@@ -104,6 +97,24 @@ export function JobsAvailableContainer({
|
||||
});
|
||||
return;
|
||||
}
|
||||
//IO-539 Check for Parts Rate on PAL for SGI use case.
|
||||
|
||||
if (
|
||||
estData.est_data.parts_tax_rates &&
|
||||
estData.est_data.parts_tax_rates.PAL &&
|
||||
(estData.est_data.parts_tax_rates.PAL.prt_tax_rt === null ||
|
||||
estData.est_data.parts_tax_rates.PAL.prt_tax_rt === 0)
|
||||
) {
|
||||
console.log("checking");
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for used parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}%.`
|
||||
);
|
||||
if (res) {
|
||||
estData.est_data.parts_tax_rates.PAL.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.est_data.parts_tax_rates.PAL.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
|
||||
const newTotals = (
|
||||
await Axios.post("/job/totals", {
|
||||
@@ -115,10 +126,7 @@ export function JobsAvailableContainer({
|
||||
).data;
|
||||
|
||||
let existingVehicles;
|
||||
if (
|
||||
estData.est_data.vehicle &&
|
||||
estData.est_data.vin
|
||||
) {
|
||||
if (estData.est_data.vehicle && estData.est_data.vin) {
|
||||
//There's vehicle data, need to double check the VIN.
|
||||
existingVehicles = await client.query({
|
||||
query: SEARCH_VEHICLE_BY_VIN,
|
||||
@@ -196,33 +204,48 @@ export function JobsAvailableContainer({
|
||||
|
||||
setJobModalVisible(false);
|
||||
setInsertLoading(true);
|
||||
const estData = replaceEmpty(estDataRaw.data.available_jobs_by_pk);
|
||||
if (
|
||||
!(
|
||||
|
||||
estData &&
|
||||
estData.est_data
|
||||
)
|
||||
) {
|
||||
|
||||
const estData = estDataRaw.data.available_jobs_by_pk;
|
||||
|
||||
if (!(estData && estData.est_data)) {
|
||||
//We don't have the right data. Error!
|
||||
setInsertLoading(false);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.creating", { error: "No job data present." }),
|
||||
});
|
||||
} else {
|
||||
//IO-539 Check for Parts Rate on PAL for SGI use case.
|
||||
if (
|
||||
estData.est_data.parts_tax_rates &&
|
||||
estData.est_data.parts_tax_rates.PAL &&
|
||||
(estData.est_data.parts_tax_rates.PAL.prt_tax_rt === null ||
|
||||
estData.est_data.parts_tax_rates.PAL.prt_tax_rt === 0)
|
||||
) {
|
||||
console.log("checking");
|
||||
const res = await confirmDialog(
|
||||
`ImEX Online has detected that there is a missing tax rate for used parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}%.`
|
||||
);
|
||||
if (res) {
|
||||
estData.est_data.parts_tax_rates.PAL.prt_tax_rt =
|
||||
bodyshop.bill_tax_rates.state_tax_rate / 100;
|
||||
estData.est_data.parts_tax_rates.PAL.prt_tax_in = true;
|
||||
}
|
||||
}
|
||||
|
||||
//create upsert job
|
||||
let supp = _.cloneDeep(estData.est_data);
|
||||
let supp = replaceEmpty({ ...estData.est_data });
|
||||
|
||||
delete supp.owner;
|
||||
delete supp.vehicle;
|
||||
if (importOptions.overrideHeaders) {
|
||||
delete supp.ins_co_nm;
|
||||
if (!importOptions.overrideHeaders) {
|
||||
HeaderFields.forEach((item) => delete supp[item]);
|
||||
}
|
||||
|
||||
let suppDelta = await GetSupplementDelta(
|
||||
client,
|
||||
selectedJob,
|
||||
estData.est_data.joblines.data
|
||||
supp.joblines.data
|
||||
);
|
||||
|
||||
delete supp.joblines;
|
||||
@@ -394,10 +417,18 @@ export default connect(
|
||||
)(JobsAvailableContainer);
|
||||
|
||||
function replaceEmpty(someObj, replaceValue = null) {
|
||||
const replacer = (key, value) => (value === "" ? replaceValue : value);
|
||||
const replacer = (key, value) =>
|
||||
value === "" ? replaceValue || null : value;
|
||||
//^ because you seem to want to replace (strings) "null" or "undefined" too
|
||||
console.log(someObj)
|
||||
const temp = JSON.stringify(someObj, replacer);
|
||||
console.log(`temp`, temp);
|
||||
console.log("Parsed", JSON.parse(temp));
|
||||
return JSON.parse(temp);
|
||||
}
|
||||
|
||||
function confirmDialog(msg) {
|
||||
return new Promise(function (resolve, reject) {
|
||||
let confirmed = window.confirm(msg);
|
||||
|
||||
return confirmed ? resolve(true) : reject(false);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Button } from "antd";
|
||||
import { Button, Dropdown, Menu } from "antd";
|
||||
import _ from "lodash";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -12,11 +12,8 @@ const mapStateToProps = createStructuredSelector({
|
||||
|
||||
export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
|
||||
const { t } = useTranslation();
|
||||
const handleAllocate = () => {
|
||||
logImEXEvent("jobs_close_allocate_auto");
|
||||
|
||||
const { defaults } = bodyshop.md_responsibility_centers;
|
||||
|
||||
const handleAllocate = (defaults) => {
|
||||
form.setFieldsValue({
|
||||
joblines: joblines.map((jl) => {
|
||||
const ret = _.cloneDeep(jl);
|
||||
@@ -32,7 +29,7 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
|
||||
}
|
||||
//Verify that this is also manually updated in server/job-costing
|
||||
if (!jl.part_type && !jl.mod_lbr_ty) {
|
||||
const lineDesc = jl.line_desc.toLowerCase();
|
||||
const lineDesc = jl.line_desc ? jl.line_desc.toLowerCase() : "";
|
||||
if (lineDesc.includes("shop materials")) {
|
||||
ret.profitcenter_part = defaults.profits["MASH"];
|
||||
} else if (lineDesc.includes("paint/materials")) {
|
||||
@@ -48,8 +45,36 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Button onClick={handleAllocate} disabled={disabled}>
|
||||
const handleAutoAllocateClick = () => {
|
||||
logImEXEvent("jobs_close_allocate_auto");
|
||||
|
||||
const { defaults } = bodyshop.md_responsibility_centers;
|
||||
handleAllocate(defaults);
|
||||
};
|
||||
|
||||
const handleMenuClick = ({ item, key, keyPath, domEvent }) => {
|
||||
logImEXEvent("jobs_close_allocate_auto_dms");
|
||||
handleAllocate(
|
||||
bodyshop.md_responsibility_centers.dms_defaults.find(
|
||||
(x) => x.name === key
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
const overlay = bodyshop.cdk_dealerid && (
|
||||
<Menu onClick={handleMenuClick}>
|
||||
{bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => (
|
||||
<Menu.Item key={mapping.name}>{mapping.name}</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
);
|
||||
|
||||
return bodyshop.cdk_dealerid ? (
|
||||
<Dropdown overlay={overlay}>
|
||||
<Button disabled={disabled}>{t("jobs.actions.dmsautoallocate")}</Button>
|
||||
</Dropdown>
|
||||
) : (
|
||||
<Button onClick={handleAutoAllocateClick} disabled={disabled}>
|
||||
{t("jobs.actions.autoallocate")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -24,6 +24,7 @@ export function JobsCloseExportButton({
|
||||
currentUser,
|
||||
jobId,
|
||||
disabled,
|
||||
setSelectedJobs,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updateJob] = useMutation(UPDATE_JOB);
|
||||
@@ -147,6 +148,11 @@ export function JobsCloseExportButton({
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (setSelectedJobs) {
|
||||
setSelectedJobs((selectedJobs) => {
|
||||
return selectedJobs.filter((i) => i !== jobId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { alphaSort } from "../../utils/sorters";
|
||||
import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component";
|
||||
import "./labor-allocations-table.styles.scss";
|
||||
import { CalculateAllocationsTotals } from "./labor-allocations-table.utility";
|
||||
|
||||
import _ from "lodash";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
technician: selectTechnician,
|
||||
@@ -113,7 +113,7 @@ export function LaborAllocationsTable({
|
||||
color: record.difference >= 0 ? "green" : "red",
|
||||
}}
|
||||
>
|
||||
{record.difference}
|
||||
{_.round(record.difference, 1)}
|
||||
</strong>
|
||||
),
|
||||
},
|
||||
|
||||
@@ -106,6 +106,12 @@ export default function PartsOrderModalComponent({
|
||||
label={t("parts_orders.fields.quantity")}
|
||||
key={`${index}quantity`}
|
||||
name={[field.name, "quantity"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<InputNumber />
|
||||
</Form.Item>
|
||||
|
||||
@@ -25,6 +25,7 @@ export function PayableExportButton({
|
||||
billId,
|
||||
disabled,
|
||||
loadingCallback,
|
||||
setSelectedBills,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updateBill] = useMutation(UPDATE_BILLS);
|
||||
@@ -142,6 +143,11 @@ export function PayableExportButton({
|
||||
}),
|
||||
});
|
||||
}
|
||||
if (setSelectedBills) {
|
||||
setSelectedBills((selectedBills) => {
|
||||
return selectedBills.filter((i) => i !== billId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
|
||||
@@ -24,6 +24,7 @@ export function PaymentExportButton({
|
||||
paymentId,
|
||||
disabled,
|
||||
loadingCallback,
|
||||
setSelectedPayments,
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
const [updatePayment] = useMutation(UPDATE_PAYMENTS);
|
||||
@@ -141,6 +142,12 @@ export function PaymentExportButton({
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if (setSelectedPayments) {
|
||||
setSelectedPayments((selectedBills) => {
|
||||
return selectedBills.filter((i) => i !== paymentId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
|
||||
@@ -155,7 +155,7 @@ export function ProductionListTable({
|
||||
// }
|
||||
// };
|
||||
|
||||
if (!!!columns || columns.length === 0) return <div>No columns found.</div>;
|
||||
if (!!!columns) return <div>No columns found.</div>;
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
|
||||
import _ from "lodash";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
@@ -52,17 +52,22 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
|
||||
|
||||
const theValue = {
|
||||
date: moment(val).format("D dd"),
|
||||
paintHrs: dayhrs.painthrs,
|
||||
bodyHrs: dayhrs.bodyhrs,
|
||||
accTargetHrs: Utils.AsOfDateTargetHours(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget +
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
val
|
||||
paintHrs: _.round(dayhrs.painthrs, 1),
|
||||
bodyHrs: _.round(dayhrs.bodyhrs, 1),
|
||||
accTargetHrs: _.round(
|
||||
Utils.AsOfDateTargetHours(
|
||||
bodyshop.scoreboard_target.dailyBodyTarget +
|
||||
bodyshop.scoreboard_target.dailyPaintTarget,
|
||||
val
|
||||
),
|
||||
1
|
||||
),
|
||||
accHrs:
|
||||
accHrs: _.round(
|
||||
acc.length > 0
|
||||
? acc[acc.length - 1].accHrs + dayhrs.painthrs + dayhrs.bodyhrs
|
||||
: dayhrs.painthrs + dayhrs.bodyhrs,
|
||||
1
|
||||
),
|
||||
};
|
||||
|
||||
return [...acc, theValue];
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { Button, Card, Tabs } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import ShopInfoGeneral from "./shop-info.general.component";
|
||||
import ShopInfoIntakeChecklistComponent from "./shop-info.intake.component";
|
||||
import ShopInfoLaborRates from "./shop-info.laborrates.component";
|
||||
@@ -11,7 +14,15 @@ import ShopInfoROStatusComponent from "./shop-info.rostatus.component";
|
||||
import ShopInfoSchedulingComponent from "./shop-info.scheduling.component";
|
||||
import ShopInfoSpeedPrint from "./shop-info.speedprint.component";
|
||||
|
||||
export default function ShopInfoComponent({ form, saveLoading }) {
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent);
|
||||
|
||||
export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<Card
|
||||
@@ -53,6 +64,7 @@ export default function ShopInfoComponent({ form, saveLoading }) {
|
||||
>
|
||||
<ShopInfoResponsibilityCenterComponent form={form} />
|
||||
</Tabs.TabPane>
|
||||
|
||||
<Tabs.TabPane key="checklists" tab={t("bodyshop.labels.checklists")}>
|
||||
<ShopInfoIntakeChecklistComponent form={form} />
|
||||
</Tabs.TabPane>
|
||||
|
||||
@@ -165,6 +165,20 @@ export default function ShopInfoGeneral({ form }) {
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.printlater")}
|
||||
valuePropName="checked"
|
||||
name={["accountingconfig", "printlater"]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.labels.emaillater")}
|
||||
valuePropName="checked"
|
||||
name={["accountingconfig", "emaillater"]}
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.inhousevendorid")}
|
||||
name={"inhousevendorid"}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,10 @@
|
||||
import { Card, Space, Table } from "antd";
|
||||
import { EditFilled } from "@ant-design/icons";
|
||||
import { Card, Space, Table } from "antd";
|
||||
import moment from "moment";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import {
|
||||
selectAuthLevel,
|
||||
@@ -16,8 +17,6 @@ import RbacWrapper, {
|
||||
HasRbacAccess,
|
||||
} from "../rbac-wrapper/rbac-wrapper.component";
|
||||
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
authLevel: selectAuthLevel,
|
||||
@@ -268,8 +267,12 @@ export function TimeTicketList({
|
||||
<Table.Summary.Cell />
|
||||
<Table.Summary.Cell />
|
||||
<Table.Summary.Cell />
|
||||
<Table.Summary.Cell>{totals.productivehrs}</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>{totals.actualhrs}</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{totals.productivehrs.toFixed(1)}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{totals.actualhrs.toFixed(1)}
|
||||
</Table.Summary.Cell>
|
||||
<Table.Summary.Cell>
|
||||
{totals.actualhrs === 0 || !totals.actualhrs
|
||||
? "∞"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Form, Input, InputNumber, Select } from "antd";
|
||||
import { Form, Input, InputNumber, Select, Switch } from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -48,7 +48,9 @@ export function TimeTicketModalComponent({
|
||||
{emps &&
|
||||
emps.rates.map((item) => (
|
||||
<Select.Option key={item.cost_center}>
|
||||
{item.cost_center}
|
||||
{item.cost_center === "timetickets.labels.shift"
|
||||
? t(item.cost_center)
|
||||
: item.cost_center}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
@@ -111,7 +113,17 @@ export function TimeTicketModalComponent({
|
||||
},
|
||||
]}
|
||||
>
|
||||
<EmployeeSearchSelect options={employeeAutoCompleteOptions} />
|
||||
<EmployeeSearchSelect
|
||||
options={employeeAutoCompleteOptions}
|
||||
onSelect={(value) => {
|
||||
console.log(value);
|
||||
const emps =
|
||||
employeeAutoCompleteOptions &&
|
||||
employeeAutoCompleteOptions.filter((e) => e.id === value)[0];
|
||||
console.log(emps);
|
||||
form.setFieldsValue({ flat_rate: emps && emps.flat_rate });
|
||||
}}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
shouldUpdate={(prev, cur) => prev.employeeid !== cur.employeeid}
|
||||
@@ -138,6 +150,14 @@ export function TimeTicketModalComponent({
|
||||
);
|
||||
}}
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="flat_rate"
|
||||
label={t("timetickets.fields.flat_rate")}
|
||||
valuePropName="checked"
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
|
||||
<LayoutFormRow>
|
||||
|
||||
@@ -24,6 +24,10 @@ export const QUERY_JOBS_FOR_EXPORT = gql`
|
||||
clm_total
|
||||
clm_no
|
||||
ins_co_nm
|
||||
exportlogs {
|
||||
id
|
||||
successful
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
@@ -37,6 +41,10 @@ export const QUERY_BILLS_FOR_EXPORT = gql`
|
||||
invoice_number
|
||||
is_credit_memo
|
||||
total
|
||||
exportlogs {
|
||||
id
|
||||
successful
|
||||
}
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
@@ -73,6 +81,10 @@ export const QUERY_PAYMENTS_FOR_EXPORT = gql`
|
||||
transactionid
|
||||
paymentnum
|
||||
date
|
||||
exportlogs {
|
||||
id
|
||||
successful
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -93,6 +93,7 @@ export const QUERY_BODYSHOP = gql`
|
||||
features
|
||||
attach_pdf_to_email
|
||||
tt_allow_post_to_invoiced
|
||||
cdk_configuration
|
||||
employees {
|
||||
id
|
||||
active
|
||||
@@ -182,6 +183,7 @@ export const UPDATE_SHOP = gql`
|
||||
cdk_dealerid
|
||||
attach_pdf_to_email
|
||||
tt_allow_post_to_invoiced
|
||||
cdk_configuration
|
||||
employees {
|
||||
id
|
||||
first_name
|
||||
|
||||
@@ -58,6 +58,7 @@ export const GET_LINE_TICKET_BY_PK = gql`
|
||||
jobid
|
||||
employeeid
|
||||
memo
|
||||
flat_rate
|
||||
employee {
|
||||
id
|
||||
first_name
|
||||
|
||||
@@ -318,6 +318,7 @@ export const QUERY_JOB_COSTING_DETAILS = gql`
|
||||
cost_center
|
||||
actualhrs
|
||||
productivehrs
|
||||
flat_rate
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1849,6 +1850,10 @@ export const QUERY_JOB_CHECKLISTS = gql`
|
||||
scheduled_delivery
|
||||
actual_delivery
|
||||
production_vars
|
||||
owner {
|
||||
id
|
||||
allow_text_message
|
||||
}
|
||||
bodyshop {
|
||||
id
|
||||
intakechecklist
|
||||
@@ -1871,3 +1876,100 @@ export const FIND_JOBS_BY_CLAIM = gql`
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const QUERY_JOB_EXPORT_DMS = gql`
|
||||
query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
ro_number
|
||||
invoice_allocation
|
||||
ins_co_id
|
||||
id
|
||||
ded_amt
|
||||
ded_status
|
||||
depreciation_taxes
|
||||
other_amount_payable
|
||||
towing_payable
|
||||
storage_payable
|
||||
adjustment_bottom_line
|
||||
federal_tax_rate
|
||||
state_tax_rate
|
||||
local_tax_rate
|
||||
tax_tow_rt
|
||||
tax_str_rt
|
||||
tax_paint_mat_rt
|
||||
tax_sub_rt
|
||||
tax_lbr_rt
|
||||
tax_levies_rt
|
||||
parts_tax_rates
|
||||
job_totals
|
||||
rate_la1
|
||||
rate_la2
|
||||
rate_la3
|
||||
rate_la4
|
||||
rate_laa
|
||||
rate_lab
|
||||
rate_lad
|
||||
rate_lae
|
||||
rate_laf
|
||||
rate_lag
|
||||
rate_lam
|
||||
rate_lar
|
||||
rate_las
|
||||
rate_lau
|
||||
rate_ma2s
|
||||
rate_ma2t
|
||||
rate_ma3s
|
||||
rate_mabl
|
||||
rate_macs
|
||||
rate_mahw
|
||||
rate_mapa
|
||||
rate_mash
|
||||
rate_matd
|
||||
status
|
||||
date_exported
|
||||
date_invoiced
|
||||
voided
|
||||
scheduled_completion
|
||||
actual_completion
|
||||
scheduled_delivery
|
||||
actual_delivery
|
||||
scheduled_in
|
||||
actual_in
|
||||
bills {
|
||||
id
|
||||
federal_tax_rate
|
||||
local_tax_rate
|
||||
state_tax_rate
|
||||
is_credit_memo
|
||||
billlines {
|
||||
actual_cost
|
||||
cost_center
|
||||
id
|
||||
quantity
|
||||
}
|
||||
}
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
id
|
||||
removed
|
||||
tax_part
|
||||
line_desc
|
||||
prt_dsmk_p
|
||||
prt_dsmk_m
|
||||
part_type
|
||||
oem_partno
|
||||
db_price
|
||||
act_price
|
||||
part_qty
|
||||
mod_lbr_ty
|
||||
db_hrs
|
||||
mod_lb_hrs
|
||||
lbr_op
|
||||
lbr_amt
|
||||
op_code_desc
|
||||
profitcenter_labor
|
||||
profitcenter_part
|
||||
prt_dsmk_p
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -2,7 +2,10 @@ import { gql } from "@apollo/client";
|
||||
|
||||
export const SUBSCRIPTION_SCOREBOARD = gql`
|
||||
subscription SUBSCRIPTION_SCOREBOARD($start: date!, $end: date!) {
|
||||
scoreboard(where: { _and: { date: { _gte: $start, _lte: $end } } }) {
|
||||
scoreboard(
|
||||
where: { _and: { date: { _gte: $start, _lte: $end } } }
|
||||
order_by: { date: asc }
|
||||
) {
|
||||
id
|
||||
painthrs
|
||||
bodyhrs
|
||||
|
||||
@@ -14,6 +14,7 @@ export const QUERY_TICKETS_BY_JOBID = gql`
|
||||
id
|
||||
memo
|
||||
jobid
|
||||
flat_rate
|
||||
employee {
|
||||
employee_number
|
||||
first_name
|
||||
@@ -42,6 +43,7 @@ export const QUERY_TIME_TICKETS_IN_RANGE = gql`
|
||||
productivehrs
|
||||
memo
|
||||
jobid
|
||||
flat_rate
|
||||
job {
|
||||
id
|
||||
ro_number
|
||||
@@ -72,6 +74,7 @@ export const INSERT_NEW_TIME_TICKET = gql`
|
||||
ciecacode
|
||||
date
|
||||
memo
|
||||
flat_rate
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,6 +101,7 @@ export const UPDATE_TIME_TICKET = gql`
|
||||
updated_at
|
||||
jobid
|
||||
date
|
||||
flat_rate
|
||||
memo
|
||||
}
|
||||
}
|
||||
@@ -121,6 +125,7 @@ export const QUERY_ACTIVE_TIME_TICKETS = gql`
|
||||
clockon
|
||||
memo
|
||||
cost_center
|
||||
flat_rate
|
||||
jobid
|
||||
job {
|
||||
id
|
||||
|
||||
@@ -1,16 +1,25 @@
|
||||
import { Result, Timeline, Space, Tag, Divider, Button } from "antd";
|
||||
//import { useQuery } from "@apollo/client";
|
||||
import { Button, Col, Result, Row, Select, Space } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import SocketIO from "socket.io-client";
|
||||
//import AlertComponent from "../../components/alert/alert.component";
|
||||
import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms-allocations-summary.component";
|
||||
import DmsCustomerSelector from "../../components/dms-customer-selector/dms-customer-selector.component";
|
||||
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
|
||||
import DmsPostForm from "../../components/dms-post-form/dms-post-form.component";
|
||||
//import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
//import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries";
|
||||
import {
|
||||
setBreadcrumbs,
|
||||
setSelectedHeader,
|
||||
} from "../../redux/application/application.actions";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import SocketIO from "socket.io-client";
|
||||
import { auth } from "../../firebase/firebase.utils";
|
||||
import moment from "moment";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -38,7 +47,15 @@ export const socket = SocketIO(
|
||||
|
||||
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
const { t } = useTranslation();
|
||||
const [logLevel, setLogLevel] = useState("TRACE");
|
||||
const [logs, setLogs] = useState([]);
|
||||
const search = queryString.parse(useLocation().search);
|
||||
const { jobId } = search;
|
||||
|
||||
// const { loading, error } = useQuery(QUERY_JOB_EXPORT_DMS, {
|
||||
// variables: { id: jobId },
|
||||
// skip: true, //!jobId,
|
||||
// });
|
||||
|
||||
useEffect(() => {
|
||||
document.title = t("titles.dms");
|
||||
@@ -55,6 +72,19 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
socket.on("connected", () => {
|
||||
console.log("Connected again.");
|
||||
});
|
||||
socket.on("reconnect", () => {
|
||||
console.log("Connected again.");
|
||||
setLogs((logs) => {
|
||||
return [
|
||||
...logs,
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "WARNING",
|
||||
message: "Reconnected to CDK Export Service",
|
||||
},
|
||||
];
|
||||
});
|
||||
});
|
||||
|
||||
socket.on("log-event", (payload) => {
|
||||
setLogs((logs) => {
|
||||
@@ -63,74 +93,68 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
});
|
||||
|
||||
socket.connect();
|
||||
socket.emit("set-log-level", "TRACE");
|
||||
socket.emit("set-log-level", logLevel);
|
||||
|
||||
return () => {
|
||||
socket.removeAllListeners();
|
||||
socket.disconnect();
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (!bodyshop.cdk_dealerid) return <Result status="404" />;
|
||||
if (!jobId || !bodyshop.cdk_dealerid) return <Result status="404" />;
|
||||
|
||||
const dmsType = determineDmsType(bodyshop);
|
||||
|
||||
// if (loading) return <LoadingSpinner />;
|
||||
// if (error) return <AlertComponent message={error.message} type="error" />;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Button
|
||||
onClick={() => {
|
||||
socket.emit(
|
||||
`${dmsType}-export-job`,
|
||||
"752a4f5f-22ab-414b-b182-98d4e62227ef"
|
||||
);
|
||||
}}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
<Space>
|
||||
<Button
|
||||
onClick={() => {
|
||||
socket.emit(
|
||||
`${dmsType}-export-job`,
|
||||
"752a4f5f-22ab-414b-b182-98d4e62227ef"
|
||||
);
|
||||
}}
|
||||
>
|
||||
Export
|
||||
</Button>
|
||||
<Select
|
||||
placeholder="Log Level"
|
||||
value={logLevel}
|
||||
onChange={(value) => {
|
||||
setLogLevel(value);
|
||||
socket.emit("set-log-level", value);
|
||||
}}
|
||||
>
|
||||
<Select.Option key="TRACE">TRACE</Select.Option>
|
||||
<Select.Option key="DEBUG">DEBUG</Select.Option>
|
||||
<Select.Option key="INFO">INFO</Select.Option>
|
||||
<Select.Option key="WARNING">WARNING</Select.Option>
|
||||
<Select.Option key="ERROR">ERROR</Select.Option>
|
||||
</Select>
|
||||
<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
||||
</Space>
|
||||
<Row gutter={32}>
|
||||
<Col span={18}>
|
||||
<DmsAllocationsSummary socket={socket} jobId={jobId} />
|
||||
<DmsPostForm socket={socket} jobId={jobId} />
|
||||
</Col>
|
||||
<Col span={6}>
|
||||
<div style={{ maxHeight: "500px", overflowY: "auto" }}>
|
||||
<DmsLogEvents socket={socket} logs={logs} />
|
||||
</div>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Button
|
||||
onClick={() => {
|
||||
setLogs([]);
|
||||
socket.disconnect();
|
||||
socket.connect();
|
||||
}}
|
||||
>
|
||||
reconnect
|
||||
</Button>
|
||||
|
||||
<Timeline pending={socket.connected && "Processing..."} reverse={true}>
|
||||
{logs.map((log, idx) => (
|
||||
<Timeline.Item key={idx} color={LogLevelHierarchy(log.level)}>
|
||||
<Space wrap align="start" style={{}}>
|
||||
<Tag color={LogLevelHierarchy(log.level)}>{log.level}</Tag>
|
||||
<span>{moment(log.timestamp).format("MM/DD/YYYY HH:MM:ss")}</span>
|
||||
<Divider type="vertical" />
|
||||
<span>{log.message}</span>
|
||||
</Space>
|
||||
</Timeline.Item>
|
||||
))}
|
||||
</Timeline>
|
||||
<DmsCustomerSelector />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function LogLevelHierarchy(level) {
|
||||
switch (level) {
|
||||
case "TRACE":
|
||||
return "pink";
|
||||
case "DEBUG":
|
||||
return "orange";
|
||||
case "INFO":
|
||||
return "blue";
|
||||
case "WARNING":
|
||||
return "yellow";
|
||||
case "ERROR":
|
||||
return "red";
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
const determineDmsType = (bodyshop) => {
|
||||
if (bodyshop.cdk_dealerid) return "cdk";
|
||||
else {
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
import DmsActions from "./dms.types";
|
||||
|
||||
export const endLoading = (options) => ({
|
||||
// type: DmsActions.END_LOADING,
|
||||
payload: options,
|
||||
});
|
||||
@@ -1,20 +0,0 @@
|
||||
import DmsActionTypes from "./dms.types";
|
||||
|
||||
const INITIAL_STATE = {
|
||||
eventLog: [],
|
||||
};
|
||||
|
||||
const dmsReducer = (state = INITIAL_STATE, action) => {
|
||||
switch (action.type) {
|
||||
// case ApplicationActionTypes.SET_SELECTED_HEADER:
|
||||
// return {
|
||||
// ...state,
|
||||
// selectedHeader: action.payload,
|
||||
// };
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default dmsReducer;
|
||||
@@ -1,14 +0,0 @@
|
||||
import { all, call } from "redux-saga/effects";
|
||||
//import DmsActionTypes from "./dms.types";
|
||||
|
||||
export function* onCalculateScheduleLoad() {
|
||||
// yield takeLatest(
|
||||
// DmsActionTypes.CALCULATE_SCHEDULE_LOAD,
|
||||
// calculateScheduleLoad
|
||||
// );
|
||||
}
|
||||
export function* calculateScheduleLoad({ payload: end }) {}
|
||||
|
||||
export function* dmsSagas() {
|
||||
yield all([call()]);
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
import { createSelector } from "reselect";
|
||||
|
||||
const selectDms = (state) => state.dms;
|
||||
|
||||
export const selectEventLog = createSelector(
|
||||
[selectDms],
|
||||
(dms) => dms.eventLog
|
||||
);
|
||||
@@ -1,4 +0,0 @@
|
||||
const DmsActionTypes = {
|
||||
ADD_EVENT: "ADD_EVENT",
|
||||
};
|
||||
export default DmsActionTypes;
|
||||
@@ -27,7 +27,7 @@ const middlewares = [
|
||||
if (process.env.NODE_ENV === "development") {
|
||||
middlewares.push(createLogger({ collapsed: true, diff: true }));
|
||||
}
|
||||
|
||||
//middlewares.push(Tracker.use(trackerRedux()));
|
||||
const composeEnhancers =
|
||||
typeof window === "object" && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
|
||||
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
|
||||
|
||||
@@ -28,6 +28,8 @@ import {
|
||||
validatePasswordResetSuccess,
|
||||
} from "./user.actions";
|
||||
import UserActionTypes from "./user.types";
|
||||
import * as Sentry from "@sentry/browser";
|
||||
import { tracker } from "../../App/App.container";
|
||||
|
||||
export function* onEmailSignInStart() {
|
||||
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
|
||||
@@ -67,6 +69,7 @@ export function* isUserAuthenticated() {
|
||||
}
|
||||
|
||||
LogRocket.identify(user.email);
|
||||
tracker.setUserID(user.email);
|
||||
yield put(
|
||||
signInSuccess({
|
||||
uid: user.uid,
|
||||
@@ -168,6 +171,8 @@ export function* onSignInSuccess() {
|
||||
|
||||
export function* signInSuccessSaga({ payload }) {
|
||||
LogRocket.identify(payload.email);
|
||||
tracker.setUserID(payload.email);
|
||||
|
||||
try {
|
||||
// window.$crisp.push(["set", "user:email", [payload.email]]);
|
||||
console.log("$crisp set nickname", [payload.displayName || payload.email]);
|
||||
@@ -178,6 +183,11 @@ export function* signInSuccessSaga({ payload }) {
|
||||
]);
|
||||
console.log("Setting $crisp segments", ["user"]);
|
||||
window.$crisp.push(["set", "session:segments", [["user"]]]);
|
||||
|
||||
Sentry.setUser({
|
||||
email: payload.email,
|
||||
username: payload.displayName || payload.email,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error updating Crisp settings.", error);
|
||||
}
|
||||
|
||||
@@ -231,6 +231,12 @@
|
||||
"deliver": {
|
||||
"templates": "Delivery Templates"
|
||||
},
|
||||
"dms": {
|
||||
"default_journal": "Default Journal",
|
||||
"dms_acctnumber": "DMS Account #",
|
||||
"dms_wip_acctnumber": "DMS W.I.P. Account #",
|
||||
"mappingname": "DMS Mapping Name"
|
||||
},
|
||||
"email": "General Shop Email",
|
||||
"enforce_class": "Enforce Class on Conversion?",
|
||||
"enforce_referral": "Enforce Referrals",
|
||||
@@ -467,6 +473,11 @@
|
||||
"defaultcostsmapping": "Default Costs Mapping",
|
||||
"defaultprofitsmapping": "Default Profits Mapping",
|
||||
"deliverchecklist": "Delivery Checklist",
|
||||
"dms": {
|
||||
"cdk_dealerid": "CDK Dealer ID",
|
||||
"title": "DMS"
|
||||
},
|
||||
"emaillater": "Email Later",
|
||||
"employees": "Employees",
|
||||
"insurancecos": "Insurance Companies",
|
||||
"intakechecklist": "Intake Checklist",
|
||||
@@ -478,6 +489,7 @@
|
||||
"notespresets": "Notes Presets",
|
||||
"orderstatuses": "Order Statuses",
|
||||
"partslocations": "Parts Locations",
|
||||
"printlater": "Print Later",
|
||||
"rbac": "Role Based Access Control",
|
||||
"responsibilitycenters": {
|
||||
"costs": "Cost Centers",
|
||||
@@ -815,6 +827,9 @@
|
||||
"exportlogs": {
|
||||
"fields": {
|
||||
"createdat": "Created At"
|
||||
},
|
||||
"labels": {
|
||||
"attempts": "Export Attempts"
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
@@ -1051,6 +1066,7 @@
|
||||
"changestatus": "Change Status",
|
||||
"convert": "Convert",
|
||||
"deliver": "Deliver",
|
||||
"dmsautoallocate": "DMS Auto Allocate",
|
||||
"export": "Export",
|
||||
"exportcustdata": "Export Customer Data",
|
||||
"exportselected": "Export Selected",
|
||||
@@ -1130,6 +1146,14 @@
|
||||
"ded_amt": "Deductible",
|
||||
"ded_status": "Deductible Status",
|
||||
"depreciation_taxes": "Depreciation/Taxes",
|
||||
"dms": {
|
||||
"center": "Center",
|
||||
"cost": "Cost",
|
||||
"cost_dms_acctnumber": "Cost DMS Acct #",
|
||||
"dms_wip_acctnumber": "Cost WIP DMS Acct #",
|
||||
"sale": "Sale",
|
||||
"sale_dms_acctnumber": "Sale DMS Acct #"
|
||||
},
|
||||
"driveable": "Driveable",
|
||||
"employee_body": "Body",
|
||||
"employee_csr": "Customer Service Rep.",
|
||||
@@ -2090,6 +2114,11 @@
|
||||
"updated": "Scoreboard updated."
|
||||
}
|
||||
},
|
||||
"scoredboard": {
|
||||
"successes": {
|
||||
"updated": "Scoreboard entry updated."
|
||||
}
|
||||
},
|
||||
"tech": {
|
||||
"fields": {
|
||||
"employeeid": "Employee ID",
|
||||
@@ -2133,6 +2162,7 @@
|
||||
"date": "Ticket Date",
|
||||
"efficiency": "Efficiency",
|
||||
"employee": "Employee",
|
||||
"flat_rate": "Flat Rate?",
|
||||
"memo": "Memo",
|
||||
"productivehrs": "Productive Hours",
|
||||
"ro_number": "Job to Post Against"
|
||||
|
||||
@@ -231,6 +231,12 @@
|
||||
"deliver": {
|
||||
"templates": ""
|
||||
},
|
||||
"dms": {
|
||||
"default_journal": "",
|
||||
"dms_acctnumber": "",
|
||||
"dms_wip_acctnumber": "",
|
||||
"mappingname": ""
|
||||
},
|
||||
"email": "",
|
||||
"enforce_class": "",
|
||||
"enforce_referral": "",
|
||||
@@ -467,6 +473,11 @@
|
||||
"defaultcostsmapping": "",
|
||||
"defaultprofitsmapping": "",
|
||||
"deliverchecklist": "",
|
||||
"dms": {
|
||||
"cdk_dealerid": "",
|
||||
"title": ""
|
||||
},
|
||||
"emaillater": "",
|
||||
"employees": "",
|
||||
"insurancecos": "",
|
||||
"intakechecklist": "",
|
||||
@@ -478,6 +489,7 @@
|
||||
"notespresets": "",
|
||||
"orderstatuses": "",
|
||||
"partslocations": "",
|
||||
"printlater": "",
|
||||
"rbac": "",
|
||||
"responsibilitycenters": {
|
||||
"costs": "",
|
||||
@@ -815,6 +827,9 @@
|
||||
"exportlogs": {
|
||||
"fields": {
|
||||
"createdat": ""
|
||||
},
|
||||
"labels": {
|
||||
"attempts": ""
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
@@ -1051,6 +1066,7 @@
|
||||
"changestatus": "Cambiar Estado",
|
||||
"convert": "Convertir",
|
||||
"deliver": "",
|
||||
"dmsautoallocate": "",
|
||||
"export": "",
|
||||
"exportcustdata": "",
|
||||
"exportselected": "",
|
||||
@@ -1130,6 +1146,14 @@
|
||||
"ded_amt": "Deducible",
|
||||
"ded_status": "Estado deducible",
|
||||
"depreciation_taxes": "Depreciación / Impuestos",
|
||||
"dms": {
|
||||
"center": "",
|
||||
"cost": "",
|
||||
"cost_dms_acctnumber": "",
|
||||
"dms_wip_acctnumber": "",
|
||||
"sale": "",
|
||||
"sale_dms_acctnumber": ""
|
||||
},
|
||||
"driveable": "",
|
||||
"employee_body": "",
|
||||
"employee_csr": "Representante de servicio al cliente.",
|
||||
@@ -2090,6 +2114,11 @@
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"scoredboard": {
|
||||
"successes": {
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"tech": {
|
||||
"fields": {
|
||||
"employeeid": "",
|
||||
@@ -2133,6 +2162,7 @@
|
||||
"date": "",
|
||||
"efficiency": "",
|
||||
"employee": "",
|
||||
"flat_rate": "",
|
||||
"memo": "",
|
||||
"productivehrs": "",
|
||||
"ro_number": ""
|
||||
|
||||
@@ -231,6 +231,12 @@
|
||||
"deliver": {
|
||||
"templates": ""
|
||||
},
|
||||
"dms": {
|
||||
"default_journal": "",
|
||||
"dms_acctnumber": "",
|
||||
"dms_wip_acctnumber": "",
|
||||
"mappingname": ""
|
||||
},
|
||||
"email": "",
|
||||
"enforce_class": "",
|
||||
"enforce_referral": "",
|
||||
@@ -467,6 +473,11 @@
|
||||
"defaultcostsmapping": "",
|
||||
"defaultprofitsmapping": "",
|
||||
"deliverchecklist": "",
|
||||
"dms": {
|
||||
"cdk_dealerid": "",
|
||||
"title": ""
|
||||
},
|
||||
"emaillater": "",
|
||||
"employees": "",
|
||||
"insurancecos": "",
|
||||
"intakechecklist": "",
|
||||
@@ -478,6 +489,7 @@
|
||||
"notespresets": "",
|
||||
"orderstatuses": "",
|
||||
"partslocations": "",
|
||||
"printlater": "",
|
||||
"rbac": "",
|
||||
"responsibilitycenters": {
|
||||
"costs": "",
|
||||
@@ -815,6 +827,9 @@
|
||||
"exportlogs": {
|
||||
"fields": {
|
||||
"createdat": ""
|
||||
},
|
||||
"labels": {
|
||||
"attempts": ""
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
@@ -1051,6 +1066,7 @@
|
||||
"changestatus": "Changer le statut",
|
||||
"convert": "Convertir",
|
||||
"deliver": "",
|
||||
"dmsautoallocate": "",
|
||||
"export": "",
|
||||
"exportcustdata": "",
|
||||
"exportselected": "",
|
||||
@@ -1130,6 +1146,14 @@
|
||||
"ded_amt": "Déductible",
|
||||
"ded_status": "Statut de franchise",
|
||||
"depreciation_taxes": "Amortissement / taxes",
|
||||
"dms": {
|
||||
"center": "",
|
||||
"cost": "",
|
||||
"cost_dms_acctnumber": "",
|
||||
"dms_wip_acctnumber": "",
|
||||
"sale": "",
|
||||
"sale_dms_acctnumber": ""
|
||||
},
|
||||
"driveable": "",
|
||||
"employee_body": "",
|
||||
"employee_csr": "représentant du service à la clientèle",
|
||||
@@ -2090,6 +2114,11 @@
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"scoredboard": {
|
||||
"successes": {
|
||||
"updated": ""
|
||||
}
|
||||
},
|
||||
"tech": {
|
||||
"fields": {
|
||||
"employeeid": "",
|
||||
@@ -2133,6 +2162,7 @@
|
||||
"date": "",
|
||||
"efficiency": "",
|
||||
"employee": "",
|
||||
"flat_rate": "",
|
||||
"memo": "",
|
||||
"productivehrs": "",
|
||||
"ro_number": ""
|
||||
|
||||
394
client/yarn.lock
394
client/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."bodyshops" DROP COLUMN "cdk_configuration";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "cdk_configuration" jsonb NULL;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,89 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- accountingconfig
|
||||
- address1
|
||||
- address2
|
||||
- appt_alt_transport
|
||||
- appt_colors
|
||||
- appt_length
|
||||
- attach_pdf_to_email
|
||||
- bill_tax_rates
|
||||
- cdk_dealerid
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
- default_adjustment_rate
|
||||
- deliverchecklist
|
||||
- email
|
||||
- enforce_class
|
||||
- enforce_referral
|
||||
- features
|
||||
- federal_tax_id
|
||||
- id
|
||||
- imexshopid
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- jc_hourly_rates
|
||||
- jobsizelimit
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
- md_classes
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_jobline_presets
|
||||
- md_labor_rates
|
||||
- md_messaging_presets
|
||||
- md_notes_presets
|
||||
- md_order_statuses
|
||||
- md_parts_locations
|
||||
- md_payment_types
|
||||
- md_rbac
|
||||
- md_referral_sources
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- messagingservicesid
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- region_config
|
||||
- schedule_end_time
|
||||
- schedule_start_time
|
||||
- scoreboard_target
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
- stripe_acct_id
|
||||
- sub_status
|
||||
- target_touchtime
|
||||
- template_header
|
||||
- textid
|
||||
- tt_allow_post_to_invoiced
|
||||
- updated_at
|
||||
- use_fippa
|
||||
- website
|
||||
- workingdays
|
||||
- zip_post
|
||||
computed_fields: []
|
||||
filter:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,90 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- accountingconfig
|
||||
- address1
|
||||
- address2
|
||||
- appt_alt_transport
|
||||
- appt_colors
|
||||
- appt_length
|
||||
- attach_pdf_to_email
|
||||
- bill_tax_rates
|
||||
- cdk_configuration
|
||||
- cdk_dealerid
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
- default_adjustment_rate
|
||||
- deliverchecklist
|
||||
- email
|
||||
- enforce_class
|
||||
- enforce_referral
|
||||
- features
|
||||
- federal_tax_id
|
||||
- id
|
||||
- imexshopid
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- jc_hourly_rates
|
||||
- jobsizelimit
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
- md_classes
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_jobline_presets
|
||||
- md_labor_rates
|
||||
- md_messaging_presets
|
||||
- md_notes_presets
|
||||
- md_order_statuses
|
||||
- md_parts_locations
|
||||
- md_payment_types
|
||||
- md_rbac
|
||||
- md_referral_sources
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- messagingservicesid
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- region_config
|
||||
- schedule_end_time
|
||||
- schedule_start_time
|
||||
- scoreboard_target
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
- stripe_acct_id
|
||||
- sub_status
|
||||
- target_touchtime
|
||||
- template_header
|
||||
- textid
|
||||
- tt_allow_post_to_invoiced
|
||||
- updated_at
|
||||
- use_fippa
|
||||
- website
|
||||
- workingdays
|
||||
- zip_post
|
||||
computed_fields: []
|
||||
filter:
|
||||
associations:
|
||||
user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,81 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- accountingconfig
|
||||
- address1
|
||||
- address2
|
||||
- appt_alt_transport
|
||||
- appt_colors
|
||||
- appt_length
|
||||
- attach_pdf_to_email
|
||||
- bill_tax_rates
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
- default_adjustment_rate
|
||||
- deliverchecklist
|
||||
- email
|
||||
- enforce_class
|
||||
- enforce_referral
|
||||
- federal_tax_id
|
||||
- id
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- jc_hourly_rates
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
- md_classes
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_jobline_presets
|
||||
- md_labor_rates
|
||||
- md_messaging_presets
|
||||
- md_notes_presets
|
||||
- md_order_statuses
|
||||
- md_parts_locations
|
||||
- md_payment_types
|
||||
- md_rbac
|
||||
- md_referral_sources
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- schedule_end_time
|
||||
- schedule_start_time
|
||||
- scoreboard_target
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
- target_touchtime
|
||||
- tt_allow_post_to_invoiced
|
||||
- updated_at
|
||||
- use_fippa
|
||||
- website
|
||||
- workingdays
|
||||
- zip_post
|
||||
filter:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,82 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- accountingconfig
|
||||
- address1
|
||||
- address2
|
||||
- appt_alt_transport
|
||||
- appt_colors
|
||||
- appt_length
|
||||
- attach_pdf_to_email
|
||||
- bill_tax_rates
|
||||
- cdk_configuration
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
- default_adjustment_rate
|
||||
- deliverchecklist
|
||||
- email
|
||||
- enforce_class
|
||||
- enforce_referral
|
||||
- federal_tax_id
|
||||
- id
|
||||
- inhousevendorid
|
||||
- insurance_vendor_id
|
||||
- intakechecklist
|
||||
- jc_hourly_rates
|
||||
- logo_img_path
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
- md_classes
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_jobline_presets
|
||||
- md_labor_rates
|
||||
- md_messaging_presets
|
||||
- md_notes_presets
|
||||
- md_order_statuses
|
||||
- md_parts_locations
|
||||
- md_payment_types
|
||||
- md_rbac
|
||||
- md_referral_sources
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
- schedule_end_time
|
||||
- schedule_start_time
|
||||
- scoreboard_target
|
||||
- shopname
|
||||
- shoprates
|
||||
- speedprint
|
||||
- ssbuckets
|
||||
- state
|
||||
- state_tax_id
|
||||
- target_touchtime
|
||||
- tt_allow_post_to_invoiced
|
||||
- updated_at
|
||||
- use_fippa
|
||||
- website
|
||||
- workingdays
|
||||
- zip_post
|
||||
filter:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: bodyshops
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,5 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."timetickets" DROP COLUMN "flat_rate";
|
||||
type: run_sql
|
||||
@@ -0,0 +1,6 @@
|
||||
- args:
|
||||
cascade: false
|
||||
read_only: false
|
||||
sql: ALTER TABLE "public"."timetickets" ADD COLUMN "flat_rate" boolean NULL DEFAULT
|
||||
false;
|
||||
type: run_sql
|
||||
@@ -0,0 +1,39 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
- args:
|
||||
permission:
|
||||
check:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- actualhrs
|
||||
- bodyshopid
|
||||
- ciecacode
|
||||
- clockoff
|
||||
- clockon
|
||||
- cost_center
|
||||
- created_at
|
||||
- date
|
||||
- employeeid
|
||||
- id
|
||||
- jobid
|
||||
- memo
|
||||
- productivehrs
|
||||
- rate
|
||||
- updated_at
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,40 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
type: drop_insert_permission
|
||||
- args:
|
||||
permission:
|
||||
check:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
columns:
|
||||
- actualhrs
|
||||
- bodyshopid
|
||||
- ciecacode
|
||||
- clockoff
|
||||
- clockon
|
||||
- cost_center
|
||||
- created_at
|
||||
- date
|
||||
- employeeid
|
||||
- flat_rate
|
||||
- id
|
||||
- jobid
|
||||
- memo
|
||||
- productivehrs
|
||||
- rate
|
||||
- updated_at
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
type: create_insert_permission
|
||||
@@ -0,0 +1,40 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- actualhrs
|
||||
- bodyshopid
|
||||
- ciecacode
|
||||
- clockoff
|
||||
- clockon
|
||||
- cost_center
|
||||
- created_at
|
||||
- date
|
||||
- employeeid
|
||||
- id
|
||||
- jobid
|
||||
- memo
|
||||
- productivehrs
|
||||
- rate
|
||||
- updated_at
|
||||
computed_fields: []
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,41 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
type: drop_select_permission
|
||||
- args:
|
||||
permission:
|
||||
allow_aggregations: false
|
||||
columns:
|
||||
- actualhrs
|
||||
- bodyshopid
|
||||
- ciecacode
|
||||
- clockoff
|
||||
- clockon
|
||||
- cost_center
|
||||
- created_at
|
||||
- date
|
||||
- employeeid
|
||||
- flat_rate
|
||||
- id
|
||||
- jobid
|
||||
- memo
|
||||
- productivehrs
|
||||
- rate
|
||||
- updated_at
|
||||
computed_fields: []
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
role: user
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
type: create_select_permission
|
||||
@@ -0,0 +1,39 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- actualhrs
|
||||
- bodyshopid
|
||||
- ciecacode
|
||||
- clockoff
|
||||
- clockon
|
||||
- cost_center
|
||||
- created_at
|
||||
- date
|
||||
- employeeid
|
||||
- id
|
||||
- jobid
|
||||
- memo
|
||||
- productivehrs
|
||||
- rate
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -0,0 +1,40 @@
|
||||
- args:
|
||||
role: user
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
type: drop_update_permission
|
||||
- args:
|
||||
permission:
|
||||
columns:
|
||||
- actualhrs
|
||||
- bodyshopid
|
||||
- ciecacode
|
||||
- clockoff
|
||||
- clockon
|
||||
- cost_center
|
||||
- created_at
|
||||
- date
|
||||
- employeeid
|
||||
- flat_rate
|
||||
- id
|
||||
- jobid
|
||||
- memo
|
||||
- productivehrs
|
||||
- rate
|
||||
- updated_at
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
_and:
|
||||
- user:
|
||||
authid:
|
||||
_eq: X-Hasura-User-Id
|
||||
- active:
|
||||
_eq: true
|
||||
set: {}
|
||||
role: user
|
||||
table:
|
||||
name: timetickets
|
||||
schema: public
|
||||
type: create_update_permission
|
||||
@@ -796,6 +796,7 @@ tables:
|
||||
- appt_length
|
||||
- attach_pdf_to_email
|
||||
- bill_tax_rates
|
||||
- cdk_configuration
|
||||
- cdk_dealerid
|
||||
- city
|
||||
- country
|
||||
@@ -873,6 +874,7 @@ tables:
|
||||
- appt_length
|
||||
- attach_pdf_to_email
|
||||
- bill_tax_rates
|
||||
- cdk_configuration
|
||||
- city
|
||||
- country
|
||||
- created_at
|
||||
@@ -4095,6 +4097,7 @@ tables:
|
||||
- created_at
|
||||
- date
|
||||
- employeeid
|
||||
- flat_rate
|
||||
- id
|
||||
- jobid
|
||||
- memo
|
||||
@@ -4114,6 +4117,7 @@ tables:
|
||||
- created_at
|
||||
- date
|
||||
- employeeid
|
||||
- flat_rate
|
||||
- id
|
||||
- jobid
|
||||
- memo
|
||||
@@ -4142,6 +4146,7 @@ tables:
|
||||
- created_at
|
||||
- date
|
||||
- employeeid
|
||||
- flat_rate
|
||||
- id
|
||||
- jobid
|
||||
- memo
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
"firebase-admin": "^9.11.0",
|
||||
"graphql": "^15.5.1",
|
||||
"graphql-request": "^3.4.0",
|
||||
"graylog2": "^0.2.1",
|
||||
"inline-css": "^3.0.0",
|
||||
"intuit-oauth": "^4.0.0",
|
||||
"lodash": "^4.17.21",
|
||||
|
||||
27
server.js
27
server.js
@@ -4,6 +4,7 @@ const bodyParser = require("body-parser");
|
||||
const path = require("path");
|
||||
const compression = require("compression");
|
||||
const twilio = require("twilio");
|
||||
const logger = require("./server/utils/logger");
|
||||
global.fetch = require("node-fetch");
|
||||
var fb = require("./server/firebase/firebase-handler");
|
||||
|
||||
@@ -33,23 +34,13 @@ app.post("/sendemail", fb.validateFirebaseIdToken, sendEmail.sendEmail);
|
||||
|
||||
//Test route to ensure Express is responding.
|
||||
app.get("/test", async function (req, res) {
|
||||
console.log("Incoming request verified.", req);
|
||||
const Commit = require("child_process").execSync(
|
||||
const commit = require("child_process").execSync(
|
||||
"git rev-parse --short HEAD"
|
||||
);
|
||||
logger.log("test-api-status", "DEBUG", "api", { commit });
|
||||
|
||||
res.status(200).send(`OK - ${Commit}`);
|
||||
res.status(200).send(`OK - ${commit}`);
|
||||
});
|
||||
const test = require("./server/_test/test.js");
|
||||
app.post("/test", test.testResponse);
|
||||
|
||||
//Accounting-IIF
|
||||
const accountingIIF = require("./server/accounting/iif/iif");
|
||||
app.post(
|
||||
"/accounting/iif/receivables",
|
||||
fb.validateFirebaseIdToken,
|
||||
accountingIIF.receivables
|
||||
);
|
||||
|
||||
//Accounting Qbxml
|
||||
const accountQbxml = require("./server/accounting/qbxml/qbxml");
|
||||
@@ -107,8 +98,8 @@ var scheduling = require("./server/scheduling/scheduling-job");
|
||||
app.post("/scheduling/job", fb.validateFirebaseIdToken, scheduling.job);
|
||||
|
||||
//Handlebars Paths for Email/Report Rendering
|
||||
var renderHandlebars = require("./server/render/renderHandlebars");
|
||||
app.post("/render", fb.validateFirebaseIdToken, renderHandlebars.render);
|
||||
// var renderHandlebars = require("./server/render/renderHandlebars");
|
||||
// app.post("/render", fb.validateFirebaseIdToken, renderHandlebars.render);
|
||||
var inlineCss = require("./server/render/inlinecss");
|
||||
app.post("/render/inlinecss", fb.validateFirebaseIdToken, inlineCss.inlinecss);
|
||||
|
||||
@@ -166,7 +157,11 @@ const io = new Server(server, {
|
||||
|
||||
server.listen(port, (error) => {
|
||||
if (error) throw error;
|
||||
console.log(`[${process.env.NODE_ENV}] Server running on port ${port}`);
|
||||
logger.log(
|
||||
`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server running on port ${port}`,
|
||||
"DEBUG",
|
||||
"api"
|
||||
);
|
||||
});
|
||||
exports.io = io;
|
||||
require("./server/web-sockets/web-socket");
|
||||
|
||||
@@ -1,74 +0,0 @@
|
||||
const path = require("path");
|
||||
const admin = require("../firebase/firebase-handler").admin;
|
||||
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
`.env.${process.env.NODE_ENV || "development"}`
|
||||
),
|
||||
});
|
||||
|
||||
exports.testResponse = async (req, res) => {
|
||||
console.log("Test");
|
||||
|
||||
const uniqueTokens = [
|
||||
"f7B-k-ceDNCEAIFYCfhF3M:APA91bEn-xOmUahCBMJBBDqXpVOZJnnb_qhWlo8eOPrIkvFeSc2nqaKd4D8zs3qqZ_VNgS_OhifsetJXcwtczO8N4k3xfDzCyI3i6j6YTUNK56QC-WNmVOLR2C_g-owy7hSvhGhWilZ3",
|
||||
"eNdzsUqRBBZCM8LQKvqk6e:APA91bFgL0VQLf_TooYmHKQ7_b4H--ZmUYCdgiZpT7dxHSyEkpcCHUz33K7sKqgifUk8rMAEhSsHWa0TJgLbOJxWD6lJaGEpXn8G3PbunkJsJCNCA3CprMONylBr9d6hnQ5wnjUX2Gt6",
|
||||
];
|
||||
|
||||
var message = {
|
||||
notification: {
|
||||
title: "Test Notification",
|
||||
body: "Test Body",
|
||||
//click_action: "TEST CLICK ACTION",
|
||||
},
|
||||
data: {
|
||||
jobid: "1234",
|
||||
title: "Test Notification",
|
||||
body: "Test Body",
|
||||
},
|
||||
tokens: uniqueTokens,
|
||||
// android: {
|
||||
// notification: {
|
||||
// body: "This is an FCM notification specifically for android.",
|
||||
// title: "FCM Notification for Android",
|
||||
// image: "/logo192.png",
|
||||
// badge: "/logo192.png",
|
||||
// },
|
||||
// },
|
||||
webpush: {
|
||||
headers: {
|
||||
// Urgency: "high",
|
||||
},
|
||||
notification: {
|
||||
body: "This is a message from FCM to web",
|
||||
requireInteraction: "true",
|
||||
actions: [{ action: "the action - matched in sw", title: "Read" }],
|
||||
|
||||
// renotify: true,
|
||||
//tag: "1234", image: "/logo192.png",
|
||||
badge: "/logo192.png",
|
||||
//badge: "/badge-icon.png",
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Send a message to the device corresponding to the provided
|
||||
// registration token.
|
||||
admin
|
||||
.messaging()
|
||||
.sendMulticast(message)
|
||||
.then((response) => {
|
||||
// Response is a message ID string.
|
||||
console.log(
|
||||
"[TEST] Successfully sent FCM Broadcast.:",
|
||||
response
|
||||
//JSON.stringify(response)
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error sending message:", error);
|
||||
});
|
||||
|
||||
res.status(200).send("OK");
|
||||
};
|
||||
@@ -1,167 +0,0 @@
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
|
||||
const path = require("path");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
`.env.${process.env.NODE_ENV || "development"}`
|
||||
),
|
||||
});
|
||||
const queries = require("../../graphql-client/queries");
|
||||
const Dinero = require("dinero.js");
|
||||
|
||||
exports.default = async (req, res) => {
|
||||
const BearerToken = req.headers.authorization;
|
||||
const { jobId } = req.body;
|
||||
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||
headers: {
|
||||
Authorization: BearerToken,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: BearerToken })
|
||||
.request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { id: jobId });
|
||||
|
||||
const { jobs_by_pk } = result;
|
||||
const { bodyshop } = jobs_by_pk;
|
||||
//Build the IIF file.
|
||||
const response = [];
|
||||
response.push(TRNS_HEADER);
|
||||
response.push(
|
||||
generateInvoiceHeader(jobs_by_pk, bodyshop.md_responsibility_centers.ar)
|
||||
);
|
||||
|
||||
//Allocations
|
||||
const invoice_allocation = jobs_by_pk.invoice_allocation;
|
||||
Object.keys(invoice_allocation.partsAllocations).forEach(
|
||||
(partsAllocationKey) => {
|
||||
if (
|
||||
!!!invoice_allocation.partsAllocations[partsAllocationKey].allocations
|
||||
)
|
||||
return;
|
||||
|
||||
invoice_allocation.partsAllocations[
|
||||
partsAllocationKey
|
||||
].allocations.forEach((alloc) => {
|
||||
response.push(
|
||||
generateInvoiceLine(
|
||||
jobs_by_pk,
|
||||
alloc,
|
||||
bodyshop.md_responsibility_centers
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
);
|
||||
Object.keys(invoice_allocation.labMatAllocations).forEach(
|
||||
(AllocationKey) => {
|
||||
if (!!!invoice_allocation.labMatAllocations[AllocationKey].allocations)
|
||||
return;
|
||||
|
||||
invoice_allocation.labMatAllocations[AllocationKey].allocations.forEach(
|
||||
(alloc) => {
|
||||
response.push(
|
||||
generateInvoiceLine(
|
||||
jobs_by_pk,
|
||||
alloc,
|
||||
bodyshop.md_responsibility_centers
|
||||
)
|
||||
);
|
||||
}
|
||||
);
|
||||
}
|
||||
);
|
||||
//End Allocations
|
||||
|
||||
//Taxes
|
||||
const taxMapping = bodyshop.md_responsibility_centers.taxes;
|
||||
const { federal_tax, state_tax, local_tax } = JSON.parse(
|
||||
jobs_by_pk.job_totals
|
||||
).totals;
|
||||
const federal_tax_dinero = Dinero(federal_tax);
|
||||
const state_tax_dinero = Dinero(state_tax);
|
||||
const local_tax_dinero = Dinero(local_tax);
|
||||
|
||||
if (federal_tax_dinero.getAmount() > 0) {
|
||||
response.push(
|
||||
generateTaxLine(jobs_by_pk, federal_tax_dinero, "federal", taxMapping)
|
||||
);
|
||||
}
|
||||
if (state_tax_dinero.getAmount() > 0) {
|
||||
response.push(
|
||||
generateTaxLine(jobs_by_pk, state_tax_dinero, "state", taxMapping)
|
||||
);
|
||||
}
|
||||
if (local_tax_dinero.getAmount() > 0) {
|
||||
response.push(
|
||||
generateTaxLine(jobs_by_pk, local_tax_dinero, "local", taxMapping)
|
||||
);
|
||||
}
|
||||
//End Taxes
|
||||
|
||||
response.push(END_TRNS);
|
||||
|
||||
//Prep the response and send it.
|
||||
res.setHeader("Content-type", "application/octet-stream");
|
||||
res.setHeader("Content-disposition", "attachment; filename=file.txt");
|
||||
res.setHeader("filename", `${jobs_by_pk.ro_number}-RECEIVABLES.iif`);
|
||||
res.send(response.join("\n"));
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
}
|
||||
};
|
||||
|
||||
const TRNS_HEADER = `!TRNS TRNSID TRNSTYPE DATE ACCNT NAME CLASS AMOUNT DOCNUM MEMO CLEAR TOPRINT NAMEISTAXABLE ADDR1 ADDR2 ADDR3 ADDR4 DUEDATE TERMS OTHER1 PONUM
|
||||
!SPL SPLID TRNSTYPE DATE ACCNT NAME CLASS AMOUNT DOCNUM MEMO CLEAR QNTY PRICE INVITEM PAYMETH TAXABLE VALADJ SERVICEDATE OTHER2 EXTRA
|
||||
!ENDTRNS`;
|
||||
|
||||
const generateInvoiceHeader = (job, arMapping) =>
|
||||
`TRNS INVOICE ${generateJobInvoiceDate(job)} ${arMapping.name} GUO DA Acct.# ${
|
||||
job.ownerid
|
||||
}:${job.ro_number} 0100 ${job.clm_total} ${job.ro_number} N N Y GUO DA Acct.# ${
|
||||
job.ownr_id
|
||||
}:${job.ro_number} ${job.ownr_addr1} ${job.ownr_city} ${job.ownr_st} ${
|
||||
job.ownr_zip
|
||||
} `;
|
||||
|
||||
const generateInvoiceLine = (job, allocation, responsibilityCenters) => {
|
||||
const { amount, center } = allocation;
|
||||
const DineroAmount = Dinero(amount);
|
||||
const account = responsibilityCenters.profits.find(
|
||||
(i) => i.name.toLowerCase() === center.toLowerCase()
|
||||
);
|
||||
|
||||
if (!!!account) {
|
||||
throw new Error(
|
||||
`A matching account does not exist for the allocation. Center: ${center}`
|
||||
);
|
||||
}
|
||||
|
||||
return `SPL INVOICE ${generateJobInvoiceDate(job)} ${
|
||||
account.accountname
|
||||
} 0100 ${DineroAmount.multiply(-1).toFormat(DineroQbFormat)} ${job.ro_number} ${
|
||||
account.accountdesc
|
||||
} N ${DineroAmount.toFormat(DineroQbFormat)} ${account.accountitem} Y N `;
|
||||
};
|
||||
|
||||
const generateTaxLine = (job, amount, type, taxMapping) => {
|
||||
return `SPL INVOICE ${generateJobInvoiceDate(job)} ${
|
||||
taxMapping[type].accountname
|
||||
} ${taxMapping[type].accountdesc} 0100 ${amount
|
||||
.multiply(-1)
|
||||
.toFormat(DineroQbFormat)} ${job.ro_number} N ${taxMapping[type].rate.toFixed(
|
||||
2
|
||||
)}% ${taxMapping[type].accountitem} N N AUTOSTAX `;
|
||||
};
|
||||
|
||||
const END_TRNS = `ENDTRNS`;
|
||||
|
||||
const generateJobInvoiceDate = (job) => {
|
||||
return `${new Date(job.date_invoiced).getMonth() + 1}/${new Date(
|
||||
job.date_invoiced
|
||||
).getDate()}/${new Date(job.date_invoiced).getFullYear()}`;
|
||||
};
|
||||
@@ -1 +0,0 @@
|
||||
exports.receivables = require("./iif-receivables").default
|
||||
@@ -6,7 +6,7 @@ const Dinero = require("dinero.js");
|
||||
var builder = require("xmlbuilder2");
|
||||
const QbXmlUtils = require("./qbxml-utils");
|
||||
const moment = require("moment");
|
||||
|
||||
const logger = require("../../utils/logger");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
@@ -25,6 +25,13 @@ exports.default = async (req, res) => {
|
||||
});
|
||||
|
||||
try {
|
||||
logger.log(
|
||||
"qbxml-payable-create",
|
||||
"DEBUG",
|
||||
req.user.email,
|
||||
req.body.billsToQuery
|
||||
);
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: BearerToken })
|
||||
.request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, {
|
||||
@@ -42,10 +49,15 @@ exports.default = async (req, res) => {
|
||||
});
|
||||
|
||||
//For each invoice.
|
||||
|
||||
res.status(200).json(QbXmlToExecute);
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
logger.log(
|
||||
"qbxml-payable-error",
|
||||
"ERROR",
|
||||
req.user.email,
|
||||
req.body.billsToQuery,
|
||||
{ error }
|
||||
);
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
}
|
||||
};
|
||||
@@ -89,7 +101,6 @@ const generateBill = (bill) => {
|
||||
.end({ pretty: true });
|
||||
|
||||
const billQbxml_Full = QbXmlUtils.addQbxmlHeader(billQbxml_partial);
|
||||
console.log("generateBill -> billQbxml_Full", billQbxml_Full);
|
||||
|
||||
return billQbxml_Full;
|
||||
};
|
||||
@@ -113,13 +124,6 @@ const generateBillLine = (billLine, responsibilityCenters, jobClass) => {
|
||||
};
|
||||
};
|
||||
|
||||
// [
|
||||
// {
|
||||
// AccountRef: { FullName: "BODY SHOP COST:SUBLET" },
|
||||
// Amount: invoice.amount,
|
||||
// },
|
||||
// ],
|
||||
|
||||
const findTaxCode = (billLine, taxcode) => {
|
||||
const {
|
||||
applicable_taxes: { local, state, federal },
|
||||
@@ -131,7 +135,6 @@ const findTaxCode = (billLine, taxcode) => {
|
||||
!!t.federal === !!federal
|
||||
);
|
||||
if (t.length === 1) {
|
||||
console.log(t);
|
||||
return t[0].code;
|
||||
} else if (t.length > 1) {
|
||||
return "Multiple Tax Codes Match";
|
||||
|
||||
@@ -7,6 +7,8 @@ var builder = require("xmlbuilder2");
|
||||
const moment = require("moment");
|
||||
const QbXmlUtils = require("./qbxml-utils");
|
||||
const QbxmlReceivables = require("./qbxml-receivables");
|
||||
const logger = require("../../utils/logger");
|
||||
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
@@ -27,6 +29,14 @@ exports.default = async (req, res) => {
|
||||
});
|
||||
|
||||
try {
|
||||
logger.log(
|
||||
"qbxml-payments-create",
|
||||
"DEBUG",
|
||||
req.user.email,
|
||||
req.body.paymentsToQuery,
|
||||
null
|
||||
);
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: BearerToken })
|
||||
.request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
|
||||
@@ -80,13 +90,18 @@ exports.default = async (req, res) => {
|
||||
|
||||
res.status(200).json(QbXmlToExecute);
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
logger.log(
|
||||
"qbxml-payments-error",
|
||||
"error",
|
||||
req.user.email,
|
||||
req.body.paymentsToQuery,
|
||||
error
|
||||
);
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
}
|
||||
};
|
||||
|
||||
const generatePayment = (payment, isThreeTier, twoTierPref) => {
|
||||
console.log("generatePayment -> payment", payment);
|
||||
let paymentQbxmlObj;
|
||||
if (payment.amount > 0) {
|
||||
paymentQbxmlObj = {
|
||||
@@ -194,7 +209,6 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => {
|
||||
.end({ pretty: true });
|
||||
|
||||
const paymentQbxmlFull = QbXmlUtils.addQbxmlHeader(paymentQbxmlPartial);
|
||||
console.log("generateBill -> paymentQbxmlFull", paymentQbxmlFull);
|
||||
|
||||
return paymentQbxmlFull;
|
||||
};
|
||||
|
||||
@@ -6,6 +6,8 @@ const Dinero = require("dinero.js");
|
||||
const moment = require("moment");
|
||||
var builder = require("xmlbuilder2");
|
||||
const QbXmlUtils = require("./qbxml-utils");
|
||||
const logger = require("../../utils/logger");
|
||||
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
@@ -27,6 +29,14 @@ exports.default = async (req, res) => {
|
||||
});
|
||||
|
||||
try {
|
||||
logger.log(
|
||||
"qbxml-receivables-create",
|
||||
"DEBUG",
|
||||
req.user.email,
|
||||
req.body.jobIds,
|
||||
null
|
||||
);
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: BearerToken })
|
||||
.request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { ids: jobIds });
|
||||
@@ -93,7 +103,13 @@ exports.default = async (req, res) => {
|
||||
|
||||
res.status(200).json(QbXmlToExecute);
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
logger.log(
|
||||
"qbxml-receivables-error",
|
||||
"error",
|
||||
req.user.email,
|
||||
req.body.jobIds,
|
||||
error
|
||||
);
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
}
|
||||
};
|
||||
@@ -106,13 +122,13 @@ const generateSourceCustomerQbxml = (jobs_by_pk, bodyshop) => {
|
||||
CustomerAddRq: {
|
||||
CustomerAdd: {
|
||||
Name: jobs_by_pk.ins_co_nm,
|
||||
BillAddress: {
|
||||
Addr1: jobs_by_pk.ownr_addr1,
|
||||
Addr2: jobs_by_pk.ownr_addr2,
|
||||
City: jobs_by_pk.ownr_city,
|
||||
State: jobs_by_pk.ownr_st,
|
||||
PostalCode: jobs_by_pk.ownrzip,
|
||||
},
|
||||
// BillAddress: {
|
||||
// Addr1: jobs_by_pk.ownr_addr1,
|
||||
// Addr2: jobs_by_pk.ownr_addr2,
|
||||
// City: jobs_by_pk.ownr_city,
|
||||
// State: jobs_by_pk.ownr_st,
|
||||
// PostalCode: jobs_by_pk.ownr_zip,
|
||||
// },
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -165,6 +181,25 @@ const generateJobQbxml = (
|
||||
FullName: ParentRefName,
|
||||
}
|
||||
: null,
|
||||
...(tierLevel === 3
|
||||
? {
|
||||
BillAddress: {
|
||||
Addr1: jobs_by_pk.ownr_addr1,
|
||||
Addr2: jobs_by_pk.ownr_addr2,
|
||||
City: jobs_by_pk.ownr_city,
|
||||
State: jobs_by_pk.ownr_st,
|
||||
PostalCode: jobs_by_pk.ownr_zip,
|
||||
},
|
||||
ShipAddress: {
|
||||
Addr1: jobs_by_pk.ownr_addr1,
|
||||
Addr2: jobs_by_pk.ownr_addr2,
|
||||
City: jobs_by_pk.ownr_city,
|
||||
State: jobs_by_pk.ownr_st,
|
||||
PostalCode: jobs_by_pk.ownr_zip,
|
||||
},
|
||||
Email: jobs_by_pk.ownr_ea,
|
||||
}
|
||||
: {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -207,7 +242,7 @@ const generateInvoiceQbxml = (
|
||||
jobs_by_pk.joblines.map((jobline) => {
|
||||
//Parts Lines
|
||||
if (jobline.db_ref === "936008") {
|
||||
//If either of these DB REFs change, they also need to change in job-totals calculations.
|
||||
//If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
|
||||
hasMapaLine = true;
|
||||
}
|
||||
if (jobline.db_ref === "936007") {
|
||||
@@ -220,7 +255,7 @@ const generateInvoiceQbxml = (
|
||||
}).multiply(jobline.part_qty || 1);
|
||||
|
||||
if (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0) {
|
||||
console.log("Have a part discount", jobline);
|
||||
// console.log("Have a part discount", jobline);
|
||||
DineroAmount = DineroAmount.add(
|
||||
DineroAmount.percentage(jobline.prt_dsmk_p || 0)
|
||||
);
|
||||
@@ -230,6 +265,13 @@ const generateInvoiceQbxml = (
|
||||
);
|
||||
|
||||
if (!account) {
|
||||
logger.log(
|
||||
"qbxml-receivables-no-account",
|
||||
"warn",
|
||||
null,
|
||||
jobline.id,
|
||||
null
|
||||
);
|
||||
throw new Error(
|
||||
`A matching account does not exist for the part allocation. Center: ${jobline.profitcenter_part}`
|
||||
);
|
||||
@@ -289,7 +331,7 @@ const generateInvoiceQbxml = (
|
||||
// console.log("Done creating hash", JSON.stringify(invoiceLineHash));
|
||||
|
||||
if (!hasMapaLine && jobs_by_pk.job_totals.rates.mapa.total.amount > 0) {
|
||||
console.log("Adding MAPA Line Manually.");
|
||||
// console.log("Adding MAPA Line Manually.");
|
||||
const mapaAccountName = responsibilityCenters.defaults.profits.MAPA;
|
||||
|
||||
const mapaAccount = responsibilityCenters.profits.find(
|
||||
@@ -309,12 +351,12 @@ const generateInvoiceQbxml = (
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.log("NO MAPA ACCOUNT FOUND!!");
|
||||
//console.log("NO MAPA ACCOUNT FOUND!!");
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasMashLine && jobs_by_pk.job_totals.rates.mash.total.amount > 0) {
|
||||
console.log("Adding MASH Line Manually.");
|
||||
// console.log("Adding MASH Line Manually.");
|
||||
|
||||
const mashAccountName = responsibilityCenters.defaults.profits.MASH;
|
||||
|
||||
@@ -335,7 +377,7 @@ const generateInvoiceQbxml = (
|
||||
},
|
||||
});
|
||||
} else {
|
||||
console.log("NO MASH ACCOUNT FOUND!!");
|
||||
// console.log("NO MASH ACCOUNT FOUND!!");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -423,6 +465,18 @@ const generateInvoiceQbxml = (
|
||||
|
||||
TxnDate: moment(jobs_by_pk.date_invoiced).format("YYYY-MM-DD"),
|
||||
RefNumber: jobs_by_pk.ro_number,
|
||||
BillAddress: {
|
||||
Addr1: jobs_by_pk.ownr_co_nm
|
||||
? jobs_by_pk.ownr_co_nm.substring(0, 30)
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${
|
||||
jobs_by_pk.ownr_fn || ""
|
||||
}`.substring(0, 30)}`,
|
||||
Addr2: jobs_by_pk.ownr_addr1,
|
||||
Addr3: jobs_by_pk.ownr_addr2,
|
||||
City: jobs_by_pk.ownr_city,
|
||||
State: jobs_by_pk.ownr_st,
|
||||
PostalCode: jobs_by_pk.ownr_zip,
|
||||
},
|
||||
ShipAddress: {
|
||||
Addr1: jobs_by_pk.ownr_co_nm
|
||||
? jobs_by_pk.ownr_co_nm.substring(0, 30)
|
||||
@@ -433,9 +487,14 @@ const generateInvoiceQbxml = (
|
||||
Addr3: jobs_by_pk.ownr_addr2,
|
||||
City: jobs_by_pk.ownr_city,
|
||||
State: jobs_by_pk.ownr_st,
|
||||
PostalCode: jobs_by_pk.ownrzip,
|
||||
PostalCode: jobs_by_pk.ownr_zip,
|
||||
},
|
||||
PONumber: jobs_by_pk.clm_no,
|
||||
IsToBePrinted: bodyshop.accountingconfig.printlater,
|
||||
...(jobs_by_pk.ownr_ea
|
||||
? { IsToBeEmailed: bodyshop.accountingconfig.emaillater }
|
||||
: {}),
|
||||
|
||||
InvoiceLineAdd: InvoiceLineAdd,
|
||||
},
|
||||
},
|
||||
|
||||
167
server/cdk/cdk-calculate-allocations.js
Normal file
167
server/cdk/cdk-calculate-allocations.js
Normal file
@@ -0,0 +1,167 @@
|
||||
const path = require("path");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
`.env.${process.env.NODE_ENV || "development"}`
|
||||
),
|
||||
});
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
|
||||
const queries = require("../graphql-client/queries");
|
||||
const CdkBase = require("../web-sockets/web-socket");
|
||||
|
||||
const Dinero = require("dinero.js");
|
||||
const _ = require("lodash");
|
||||
|
||||
exports.default = async function (socket, jobid) {
|
||||
try {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`Received request to calculate allocations for ${jobid}`
|
||||
);
|
||||
const job = await QueryJobData(socket, jobid);
|
||||
const { bodyshop } = job;
|
||||
|
||||
const taxAllocations = {
|
||||
local: {
|
||||
center: bodyshop.md_responsibility_centers.taxes.local.name,
|
||||
sale: Dinero(job.job_totals.totals.local_tax),
|
||||
cost: Dinero(),
|
||||
profitCenter: bodyshop.md_responsibility_centers.taxes.local,
|
||||
costCenter: bodyshop.md_responsibility_centers.taxes.local,
|
||||
},
|
||||
state: {
|
||||
center: bodyshop.md_responsibility_centers.taxes.state.name,
|
||||
sale: Dinero(job.job_totals.totals.state_tax),
|
||||
cost: Dinero(),
|
||||
profitCenter: bodyshop.md_responsibility_centers.taxes.state,
|
||||
costCenter: bodyshop.md_responsibility_centers.taxes.state,
|
||||
},
|
||||
federal: {
|
||||
center: bodyshop.md_responsibility_centers.taxes.federal.name,
|
||||
sale: Dinero(job.job_totals.totals.federal_tax),
|
||||
cost: Dinero(),
|
||||
profitCenter: bodyshop.md_responsibility_centers.taxes.federal,
|
||||
costCenter: bodyshop.md_responsibility_centers.taxes.federal,
|
||||
},
|
||||
};
|
||||
|
||||
const profitCenterHash = job.joblines.reduce((acc, val) => {
|
||||
//Check the Parts Assignment
|
||||
if (val.profitcenter_part) {
|
||||
if (!acc[val.profitcenter_part]) acc[val.profitcenter_part] = Dinero();
|
||||
|
||||
acc[val.profitcenter_part] = acc[val.profitcenter_part].add(
|
||||
Dinero({
|
||||
amount: Math.round((val.act_price || 0) * 100),
|
||||
}).multiply(val.part_qty || 0)
|
||||
);
|
||||
}
|
||||
if (val.profitcenter_labor) {
|
||||
//Check the Labor Assignment.
|
||||
|
||||
if (!acc[val.profitcenter_labor])
|
||||
acc[val.profitcenter_labor] = Dinero();
|
||||
|
||||
acc[val.profitcenter_labor] = acc[val.profitcenter_labor].add(
|
||||
Dinero({
|
||||
amount: Math.round(
|
||||
job[`rate_${val.mod_lbr_ty.toLowerCase()}`] * 100
|
||||
),
|
||||
}).multiply(val.mod_lb_hrs)
|
||||
);
|
||||
}
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const costCenterHash = job.bills.reduce((bill_acc, bill_val) => {
|
||||
bill_val.billlines.map((line_val) => {
|
||||
if (!bill_acc[line_val.cost_center])
|
||||
bill_acc[line_val.cost_center] = Dinero();
|
||||
const lineDinero = Dinero({
|
||||
amount: Math.round((line_val.actual_cost || 0) * 100),
|
||||
})
|
||||
.multiply(line_val.quantity)
|
||||
.multiply(bill_val.is_credit_memo ? -1 : 1);
|
||||
|
||||
bill_acc[line_val.cost_center] =
|
||||
bill_acc[line_val.cost_center].add(lineDinero);
|
||||
|
||||
//Add appropriate tax amounts.
|
||||
const {
|
||||
applicable_taxes: { local, state, federal },
|
||||
} = line_val;
|
||||
if (local) {
|
||||
taxAllocations.local.cost = taxAllocations.local.cost.add(
|
||||
lineDinero.percentage(bill_val.local_tax_rate || 0)
|
||||
);
|
||||
}
|
||||
if (state) {
|
||||
taxAllocations.state.cost = taxAllocations.state.cost.add(
|
||||
lineDinero.percentage(bill_val.state_tax_rate || 0)
|
||||
);
|
||||
}
|
||||
if (federal) {
|
||||
taxAllocations.federal.cost = taxAllocations.federal.cost.add(
|
||||
lineDinero.percentage(bill_val.federal_tax_rate || 0)
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
return bill_acc;
|
||||
}, {});
|
||||
|
||||
const jobAllocations = _.union(
|
||||
Object.keys(profitCenterHash),
|
||||
Object.keys(costCenterHash)
|
||||
).map((key) => {
|
||||
const profitCenter = bodyshop.md_responsibility_centers.profits.find(
|
||||
(c) => c.name === key
|
||||
);
|
||||
const costCenter = bodyshop.md_responsibility_centers.costs.find(
|
||||
(c) => c.name === key
|
||||
);
|
||||
|
||||
return {
|
||||
center: key,
|
||||
sale: profitCenterHash[key] ? profitCenterHash[key] : Dinero(),
|
||||
cost: costCenterHash[key] ? costCenterHash[key] : Dinero(),
|
||||
profitCenter,
|
||||
costCenter,
|
||||
};
|
||||
});
|
||||
|
||||
return [
|
||||
...jobAllocations,
|
||||
...Object.keys(taxAllocations)
|
||||
.filter(
|
||||
(key) =>
|
||||
taxAllocations[key].sale.getAmount() > 0 ||
|
||||
taxAllocations[key].cost.getAmount() > 0
|
||||
)
|
||||
.map((key) => taxAllocations[key]),
|
||||
];
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error encountered in CdkCalculateAllocations. ${error}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
async function QueryJobData(socket, jobid) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`Job data query result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
return result.jobs_by_pk;
|
||||
}
|
||||
78
server/cdk/cdk-get-makes.js
Normal file
78
server/cdk/cdk-get-makes.js
Normal file
@@ -0,0 +1,78 @@
|
||||
const path = require("path");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
`.env.${process.env.NODE_ENV || "development"}`
|
||||
),
|
||||
});
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
const soap = require("soap");
|
||||
const queries = require("../graphql-client/queries");
|
||||
const CdkBase = require("../web-sockets/web-socket");
|
||||
const CdkWsdl = require("./cdk-wsdl").default;
|
||||
const logger = require("../utils/logger");
|
||||
const Dinero = require("dinero.js");
|
||||
const _ = require("lodash");
|
||||
const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
|
||||
const { performance } = require("perf_hooks");
|
||||
|
||||
exports.default = async function (socket, cdk_dealerid) {
|
||||
try {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`Getting makes and models list from CDK.`
|
||||
);
|
||||
return await GetCdkMakes(socket, cdk_dealerid);
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error encountered in CdkGetMakes. ${error}`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
async function GetCdkMakes(socket, cdk_dealerid) {
|
||||
CdkBase.createLogEvent(socket, "TRACE", `{1} Begin GetCDkMakes WSDL Call`);
|
||||
|
||||
try {
|
||||
const soapClientVehicleInsert = await soap.createClientAsync(
|
||||
CdkWsdl.VehicleInsert
|
||||
);
|
||||
const start = performance.now();
|
||||
|
||||
const soapResponseVehicleSearch =
|
||||
await soapClientVehicleInsert.getMakeModelAsync(
|
||||
{
|
||||
arg0: CDK_CREDENTIALS,
|
||||
arg1: { id: cdk_dealerid },
|
||||
},
|
||||
|
||||
{}
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseVehicleSearch);
|
||||
const [
|
||||
result, //rawResponse, soapheader, rawRequest
|
||||
] = soapResponseVehicleSearch;
|
||||
const end = performance.now();
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`soapClientVehicleInsert.getMakeModelAsync Result Length ${
|
||||
result.return.length
|
||||
} and took ${end - start}ms`
|
||||
);
|
||||
|
||||
return result.return.map((element, index) => {
|
||||
return { id: index, ...element };
|
||||
});
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error in GetCdkMakes - ${JSON.stringify(error, null, 2)}`
|
||||
);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
@@ -10,40 +10,167 @@ const soap = require("soap");
|
||||
const queries = require("../graphql-client/queries");
|
||||
const CdkBase = require("../web-sockets/web-socket");
|
||||
const CdkWsdl = require("./cdk-wsdl").default;
|
||||
|
||||
const IMEX_CDK_USER = process.env.IMEX_CDK_USER,
|
||||
IMEX_CDK_PASSWORD = process.env.IMEX_CDK_PASSWORD;
|
||||
const logger = require("../utils/logger");
|
||||
const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
|
||||
|
||||
exports.default = async function (socket, jobid) {
|
||||
socket.logEvents = [];
|
||||
socket.recordid = jobid;
|
||||
try {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`Received Job export request for id ${jobid}`
|
||||
);
|
||||
|
||||
//The following values will be stored on the socket to allow callbacks.
|
||||
//let clVFV, clADPV, clADPC;
|
||||
const JobData = await QueryJobData(socket, jobid);
|
||||
console.log(JSON.stringify(JobData, null, 2));
|
||||
const DealerId = JobData.bodyshop.cdk_dealerid;
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`Dealer ID detected: ${JSON.stringify(DealerId)}`
|
||||
);
|
||||
|
||||
// Begin Calculate VID from DMS {1}
|
||||
await DetermineDMSVid(socket, JobData);
|
||||
//{1} Begin Calculate DMS Vehicle Id
|
||||
socket.clVFV = await CalculateDmsVid(socket, JobData);
|
||||
if (socket.clVFV.newId === "Y") {
|
||||
//{1.2} This is a new Vehicle ID
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{1.2} clVFV DMSVid does *not* exist.`
|
||||
);
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`{1.2} clVFV: ${JSON.stringify(socket.clVFV, null, 2)}`
|
||||
);
|
||||
//Check if DMSCustId is Empty - which it should always be?
|
||||
//{6.6} Should check to see if a customer exists so that we can marry it to the new vehicle.
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{6.6} Trying to find customer ID in DMS.`
|
||||
);
|
||||
|
||||
//Array
|
||||
const strIDS = await FindCustomerIdFromDms(socket, JobData);
|
||||
if (strIDS && strIDS.length > 0) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{8.2} ${strIDS.length} Customer ID(s) found.`
|
||||
);
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`{8.2} strIDS: ${JSON.stringify(strIDS, null, 2)}`
|
||||
);
|
||||
if (strIDS.length > 1) {
|
||||
//We have multiple IDs
|
||||
//TODO: Do we need to let the person select it?
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"WARNING",
|
||||
`{F} Mutliple customer ids have been found (${strIDS.length})`
|
||||
);
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`Asking for user intervention to select customer.`
|
||||
);
|
||||
socket.emit("cdk-select-customer", strIDS);
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{8.5} Customer ID(s) *not* found.`
|
||||
);
|
||||
|
||||
//Create a customer number, then use that to insert the customer record.
|
||||
const newCustomerNumber = await GenerateCustomerNumberFromDms(
|
||||
socket,
|
||||
JobData
|
||||
);
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{10.1} New Customer number generated. newCustomerNumber: ${newCustomerNumber}`
|
||||
);
|
||||
|
||||
//Use the new customer number to insert the customer record.
|
||||
socket.clADPC = await CreateCustomerInDms(
|
||||
socket,
|
||||
JobData,
|
||||
newCustomerNumber
|
||||
);
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{11.1} New Customer inserted.`
|
||||
);
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`{11.1} clADPC: ${JSON.stringify(socket.clADPC, null, 2)}`
|
||||
);
|
||||
}
|
||||
} else {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{1.1} clVFV DMSVid does exist.`);
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`{1.1} clVFV: ${JSON.stringify(socket.clVFV, null, 2)}`
|
||||
);
|
||||
|
||||
//{2} Begin Find Vehicle in DMS
|
||||
socket.clADPV = await FindVehicleInDms(socket, JobData, socket.clVFV); //TODO: Verify that this should always return a result. If an ID was found previously, it should be correct?
|
||||
|
||||
//{2.2} Check if the vehicle was found in the DMS.
|
||||
if (socket.clADPV.AppErrorNo === "0") {
|
||||
//Vehicle was found.
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{1.4} Vehicle was found in the DMS.`
|
||||
);
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`{1.4} clADPV: ${JSON.stringify(socket.clADPV, null, 2)}`
|
||||
);
|
||||
} else {
|
||||
//Vehicle was not found.
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{6.4} Vehicle does not exist in DMS. Will have to create one.`
|
||||
);
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`{6.4} clVFV: ${JSON.stringify(socket.clVFV, null, 2)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error encountered in JobExport. ${error}`
|
||||
`Error encountered in CdkJobExport. ${error}`
|
||||
);
|
||||
} finally {
|
||||
//Ensure we always insert logEvents
|
||||
//GQL to insert logevents.
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`Capturing log events to database.`
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -61,27 +188,267 @@ async function QueryJobData(socket, jobid) {
|
||||
return result.jobs_by_pk;
|
||||
}
|
||||
|
||||
async function DetermineDMSVid(socket, JobData) {
|
||||
CdkBase.createLogEvent(socket, "TRACE", "{1} Begin Determine DMS VehicleID");
|
||||
async function CreateCustomerInDms(socket, JobData, newCustomerNumber) {
|
||||
CdkBase.createLogEvent(socket, "DEBUG", `{11} Begin Create Customer in DMS`);
|
||||
|
||||
try {
|
||||
//Create SOAP Request for <getVehIds/>
|
||||
const soapClient = await soap.createClientAsync(CdkWsdl.VehicleSearch);
|
||||
const result = await soapClient.searchIDsByVINAsync(
|
||||
{
|
||||
arg0: { password: IMEX_CDK_PASSWORD, username: IMEX_CDK_USER },
|
||||
arg1: { id: JobData.bodyshop.cdk_dealerid },
|
||||
arg2: { VIN: JobData.v_vin },
|
||||
},
|
||||
|
||||
{}
|
||||
const soapClientCustomerInsertUpdate = await soap.createClientAsync(
|
||||
CdkWsdl.CustomerInsertUpdate
|
||||
);
|
||||
console.log(result);
|
||||
const soapResponseCustomerInsertUpdate =
|
||||
await soapClientCustomerInsertUpdate.insertAsync(
|
||||
{
|
||||
arg0: CDK_CREDENTIALS,
|
||||
arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
|
||||
arg2: { userId: null },
|
||||
arg3: {
|
||||
//Copied the required fields from the other integration.
|
||||
//TODO: Verify whether we need to bring more information in.
|
||||
id: { value: newCustomerNumber },
|
||||
address: {
|
||||
city: JobData.ownr_city,
|
||||
country: null,
|
||||
postalcode: JobData.ownr_zip,
|
||||
stateOrProvince: JobData.ownr_st,
|
||||
},
|
||||
contactInfo: {
|
||||
mainTelephoneNumber: { main: true, value: JobData.ownr_ph1 },
|
||||
},
|
||||
demographics: null,
|
||||
name1: {
|
||||
companyname: null,
|
||||
firstName: JobData.ownr_fn,
|
||||
fullname: null,
|
||||
lastName: JobData.ownr_ln,
|
||||
middleName: null,
|
||||
nameType: "Person",
|
||||
suffix: null,
|
||||
title: null,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
{}
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
|
||||
const [
|
||||
result, //rawResponse, soapheader, rawRequest
|
||||
] = soapResponseCustomerInsertUpdate;
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`soapClientCustomerInsertUpdate.insertAsync Result ${JSON.stringify(
|
||||
result,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
const customer = result && result.return;
|
||||
return customer;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error in DetermineDMSVid - ${JSON.stringify(error, null, 2)}`
|
||||
`Error in CreateCustomerInDms - ${JSON.stringify(error, null, 2)}`
|
||||
);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function GenerateCustomerNumberFromDms(socket, JobData) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{10} Begin Generate Customer Number from DMS`
|
||||
);
|
||||
|
||||
try {
|
||||
const soapClientCustomerInsertUpdate = await soap.createClientAsync(
|
||||
CdkWsdl.CustomerInsertUpdate
|
||||
);
|
||||
const soapResponseCustomerInsertUpdate =
|
||||
await soapClientCustomerInsertUpdate.getCustomerNumberAsync(
|
||||
{
|
||||
arg0: CDK_CREDENTIALS,
|
||||
arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
|
||||
arg2: { userId: null },
|
||||
},
|
||||
|
||||
{}
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
|
||||
const [
|
||||
result, //rawResponse, soapheader, rawRequest
|
||||
] = soapResponseCustomerInsertUpdate;
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`soapClientCustomerInsertUpdate.getCustomerNumberAsync Result ${JSON.stringify(
|
||||
result,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
const customerNumber =
|
||||
result && result.return && result.return.customerNumber;
|
||||
return customerNumber;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error in GenerateCustomerNumberFromDms - ${JSON.stringify(
|
||||
error,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function FindCustomerIdFromDms(socket, JobData) {
|
||||
const ownerName = `${JobData.ownr_ln},${JobData.ownr_fn}`;
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{8} Begin Read Customer from DMS using OWNER NAME: ${ownerName}`
|
||||
);
|
||||
|
||||
try {
|
||||
const soapClientCustomerSearch = await soap.createClientAsync(
|
||||
CdkWsdl.CustomerSearch
|
||||
);
|
||||
const soapResponseCustomerSearch =
|
||||
await soapClientCustomerSearch.executeSearchAsync(
|
||||
{
|
||||
arg0: CDK_CREDENTIALS,
|
||||
arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
|
||||
arg2: {
|
||||
verb: "EXACT",
|
||||
key: ownerName,
|
||||
},
|
||||
},
|
||||
|
||||
{}
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseCustomerSearch);
|
||||
const [
|
||||
result, // rawResponse, soapheader, rawRequest
|
||||
] = soapResponseCustomerSearch;
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`soapClientCustomerSearch.executeSearchBulkAsync Result ${JSON.stringify(
|
||||
result,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
const CustomersFromDms = result && result.return;
|
||||
return CustomersFromDms;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error in FindCustomerIdFromDms - ${JSON.stringify(error, null, 2)}`
|
||||
);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function FindVehicleInDms(socket, JobData, clVFV) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`{2}/{6} Begin Find Vehicle In DMS using clVFV: ${clVFV}`
|
||||
);
|
||||
|
||||
try {
|
||||
const soapClientVehicleInsertUpdate = await soap.createClientAsync(
|
||||
CdkWsdl.VehicleInsertUpdate
|
||||
);
|
||||
const soapResponseVehicleInsertUpdate =
|
||||
await soapClientVehicleInsertUpdate.readBulkAsync(
|
||||
{
|
||||
arg0: CDK_CREDENTIALS,
|
||||
arg1: { id: JobData.bodyshop.cdk_dealerid },
|
||||
arg2: {
|
||||
fileType: "VEHICLES",
|
||||
vehiclesVehicleId: clVFV.vehiclesVehId,
|
||||
},
|
||||
},
|
||||
|
||||
{}
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
|
||||
const [
|
||||
result, //rawResponse, soapheader, rawRequest
|
||||
] = soapResponseVehicleInsertUpdate;
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`soapClientVehicleInsertUpdate.readBulkAsync Result ${JSON.stringify(
|
||||
result,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
const VehicleFromDMS = result && result.return && result.return[0];
|
||||
return VehicleFromDMS;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error in FindVehicleInDms - ${JSON.stringify(error, null, 2)}`
|
||||
);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function CalculateDmsVid(socket, JobData) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`{1} Begin Calculate DMS Vehicle ID using VIN: ${JobData.v_vin}`
|
||||
);
|
||||
|
||||
try {
|
||||
const soapClientVehicleInsertUpdate = await soap.createClientAsync(
|
||||
CdkWsdl.VehicleInsertUpdate
|
||||
);
|
||||
const soapResponseVehicleInsertUpdate =
|
||||
await soapClientVehicleInsertUpdate.getVehIdsAsync(
|
||||
{
|
||||
arg0: CDK_CREDENTIALS,
|
||||
arg1: { id: JobData.bodyshop.cdk_dealerid },
|
||||
arg2: { VIN: JobData.v_vin },
|
||||
},
|
||||
|
||||
{}
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
|
||||
const [
|
||||
result, //rawResponse, soapheader, rawRequest
|
||||
] = soapResponseVehicleInsertUpdate;
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`soapClientVehicleInsertUpdate.searchIDsByVINAsync Result ${JSON.stringify(
|
||||
result,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
const DmsVehicle = result && result.return && result.return[0];
|
||||
return DmsVehicle;
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error in CalculateDmsVid - ${JSON.stringify(error, null, 2)}`
|
||||
);
|
||||
throw new Error(error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,99 @@
|
||||
exports.default = {
|
||||
VehicleSearch:
|
||||
"https://uat-3pa.dmotorworks.com/pip-vehicle/services/VehicleSearch?wsdl",
|
||||
const path = require("path");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
`.env.${process.env.NODE_ENV || "development"}`
|
||||
),
|
||||
});
|
||||
const CdkBase = require("../web-sockets/web-socket");
|
||||
|
||||
const IMEX_CDK_USER = process.env.IMEX_CDK_USER,
|
||||
IMEX_CDK_PASSWORD = process.env.IMEX_CDK_PASSWORD;
|
||||
const CDK_CREDENTIALS = {
|
||||
password: IMEX_CDK_PASSWORD,
|
||||
username: IMEX_CDK_USER,
|
||||
};
|
||||
|
||||
exports.CDK_CREDENTIALS = CDK_CREDENTIALS;
|
||||
// const cdkDomain =
|
||||
// process.env.NODE_ENV === "production"
|
||||
// ? "https://3pa.dmotorworks.com"
|
||||
// : "https://uat-3pa.dmotorworks.com";
|
||||
|
||||
function CheckCdkResponseForError(socket, soapResponse) {
|
||||
if (!soapResponse[0]) {
|
||||
//The response was null, this might be ok, it might not.
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"WARNING",
|
||||
`Warning detected in CDK Response - it appears to be null. Stack: ${
|
||||
new Error().stack
|
||||
}`
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const ResultToCheck = soapResponse[0].return;
|
||||
|
||||
if (Array.isArray(ResultToCheck)) {
|
||||
ResultToCheck.forEach((result) => checkIndividualResult(socket, result));
|
||||
} else {
|
||||
checkIndividualResult(socket, ResultToCheck);
|
||||
}
|
||||
}
|
||||
exports.CheckCdkResponseForError = CheckCdkResponseForError;
|
||||
|
||||
function checkIndividualResult(socket, ResultToCheck) {
|
||||
if (
|
||||
ResultToCheck.errorLevel === 0 ||
|
||||
ResultToCheck.errorLevel === "0" ||
|
||||
ResultToCheck.code === "success" ||
|
||||
(!ResultToCheck.code && !ResultToCheck.errorLevel)
|
||||
)
|
||||
//TODO: Verify that this is the best way to detect errors.
|
||||
return;
|
||||
else {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error detected in CDK Response - ${JSON.stringify(
|
||||
ResultToCheck,
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
|
||||
throw new Error(
|
||||
`Error found while validating CDK response for ${JSON.stringify(
|
||||
ResultToCheck,
|
||||
null,
|
||||
2
|
||||
)}:`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
exports.checkIndividualResult = checkIndividualResult;
|
||||
|
||||
const cdkDomain = "https://uat-3pa.dmotorworks.com";
|
||||
exports.default = {
|
||||
// VehicleSearch: `${cdkDomain}/pip-vehicle/services/VehicleSearch?wsdl`,
|
||||
VehicleInsertUpdate: `${cdkDomain}/pip-vehicle/services/VehicleInsertUpdate?wsdl`,
|
||||
CustomerInsertUpdate: `${cdkDomain}/pip-customer/services/CustomerInsertUpdate?wsdl`,
|
||||
CustomerSearch: `${cdkDomain}/pip-customer/services/CustomerSearch?wsdl`,
|
||||
VehicleSearch: `${cdkDomain}/pip-vehicle/services/VehicleSearch?wsdl`,
|
||||
VehicleInsert: `${cdkDomain}/pip-vehicle/services/VehicleInsertUpdate?wsdl`,
|
||||
};
|
||||
|
||||
// The following login credentials will be used for all PIPs and all environments (User Acceptance Testing and Production).
|
||||
// Only the URLs will change from https://uat-3pa.dmoto... to https://3pa.dmoto... or https://api-dit.connect... to https://api.connect...
|
||||
// Accounting GL/Accounting GL WIP Update - https://uat-3pa.dmotorworks.com/pip-accounting-gl/services/AccountingGLInsertUpdate?wsdl
|
||||
// Customer Insert Update - https://uat-3pa.dmotorworks.com/pip-customer/services/CustomerInsertUpdate?wsdl
|
||||
// Help Database Location - https://uat-3pa.dmotorworks.com/pip-help-database-location/services/HelpDatabaseLocation?wsdl
|
||||
// Parts Inventory Insert Update - https://uat-3pa.dmotorworks.com/pip-parts-inventory/services/PartsInventoryInsertUpdate?wsdl
|
||||
// Purchase Order Insert - https://uat-3pa.dmotorworks.com/pip-purchase-order/services/PurchaseOrderInsert?wsdl
|
||||
// Repair Order MLS Insert Update - https://uat-3pa.dmotorworks.com/pip-repair-order-mls/services/RepairOrderMLSInsertUpdate?wsdl
|
||||
// Repair Order Parts Insert Update - https://uat-3pa.dmotorworks.com/pip-repair-order-parts/services/RepairOrderPartsInsertUpdate?wsdl
|
||||
// Service History Insert - https://uat-3pa.dmotorworks.com/pip-service-history-insert/services/ServiceHistoryInsert?wsdl
|
||||
// Service Repair Order Update - https://uat-3pa.dmotorworks.com/pip-service-repair-order/services/ServiceRepairOrderUpdate?wsdl
|
||||
// Service Vehicle Insert Update - https://uat-3pa.dmotorworks.com/pip-vehicle/services/VehicleInsertUpdate?wsdl
|
||||
|
||||
@@ -4,7 +4,7 @@ const Dinero = require("dinero.js");
|
||||
const moment = require("moment");
|
||||
var builder = require("xmlbuilder2");
|
||||
const _ = require("lodash");
|
||||
|
||||
const logger = require("../utils/logger");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
@@ -25,7 +25,7 @@ const ftpSetup = {
|
||||
port: process.env.AUTOHOUSE_PORT,
|
||||
username: process.env.AUTOHOUSE_USER,
|
||||
password: process.env.AUTOHOUSE_PASSWORD,
|
||||
debug: console.log,
|
||||
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
|
||||
algorithms: {
|
||||
serverHostKey: ["ssh-rsa", "ssh-dss"],
|
||||
},
|
||||
@@ -33,14 +33,16 @@ const ftpSetup = {
|
||||
|
||||
exports.default = async (req, res) => {
|
||||
//Query for the List of Bodyshop Clients.
|
||||
console.log("Starting Autohouse datapump request.");
|
||||
logger.log("autohouse-start", "DEBUG", "api", null, null);
|
||||
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
|
||||
|
||||
const allxmlsToUpload = [];
|
||||
const allErrors = [];
|
||||
try {
|
||||
for (const bodyshop of bodyshops) {
|
||||
console.log("Starting extract for ", bodyshop.shopname);
|
||||
logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
shopname: bodyshop.shopname,
|
||||
});
|
||||
const erroredJobs = [];
|
||||
try {
|
||||
const { jobs } = await client.request(queries.AUTOHOUSE_QUERY, {
|
||||
@@ -60,12 +62,12 @@ exports.default = async (req, res) => {
|
||||
},
|
||||
};
|
||||
|
||||
console.log(
|
||||
bodyshop.shopname,
|
||||
"***Number of Failed jobs***: ",
|
||||
erroredJobs.length,
|
||||
JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
|
||||
);
|
||||
if (erroredJobs.length > 0) {
|
||||
logger.log("autohouse-failed-jobs", "ERROR", "api", bodyshop.id, {
|
||||
count: erroredJobs.length,
|
||||
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)),
|
||||
});
|
||||
}
|
||||
|
||||
var ret = builder
|
||||
.create(autoHouseObject, {
|
||||
@@ -81,10 +83,15 @@ exports.default = async (req, res) => {
|
||||
)}.xml`,
|
||||
});
|
||||
|
||||
console.log("Finished extract for shop ", bodyshop.shopname);
|
||||
logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
shopname: bodyshop.shopname,
|
||||
});
|
||||
} catch (error) {
|
||||
//Error at the shop level.
|
||||
console.log("Error at shop level", bodyshop.shopname, error);
|
||||
logger.log("autohouse-error-shop", "ERROR", "api", bodyshop.id, {
|
||||
error,
|
||||
});
|
||||
|
||||
allErrors.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
@@ -102,7 +109,9 @@ exports.default = async (req, res) => {
|
||||
|
||||
let sftp = new Client();
|
||||
sftp.on("error", (errors) =>
|
||||
console.log("Error in FTP client", JSON.stringify(errors))
|
||||
logger.log("autohouse-sftp-error", "ERROR", "api", null, {
|
||||
errors,
|
||||
})
|
||||
);
|
||||
try {
|
||||
//Connect to the FTP and upload all.
|
||||
@@ -110,20 +119,24 @@ exports.default = async (req, res) => {
|
||||
await sftp.connect(ftpSetup);
|
||||
|
||||
for (const xmlObj of allxmlsToUpload) {
|
||||
console.log("Uploading", xmlObj.filename);
|
||||
logger.log("autohouse-sftp-upload", "DEBUG", "api", null, {
|
||||
filename: xmlObj.filename,
|
||||
});
|
||||
|
||||
const uploadResult = await sftp.put(
|
||||
Buffer.from(xmlObj.xml),
|
||||
`/${xmlObj.filename}`
|
||||
);
|
||||
console.log(
|
||||
"🚀 ~ file: autohouse.js ~ line 94 ~ uploadResult",
|
||||
uploadResult
|
||||
);
|
||||
logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, {
|
||||
uploadResult,
|
||||
});
|
||||
}
|
||||
|
||||
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
|
||||
} catch (error) {
|
||||
console.log("Error when connecting to FTP", error);
|
||||
logger.log("autohouse-sftp-error", "ERROR", "api", null, {
|
||||
error,
|
||||
});
|
||||
} finally {
|
||||
sftp.end();
|
||||
}
|
||||
@@ -498,7 +511,10 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
||||
};
|
||||
return ret;
|
||||
} catch (error) {
|
||||
console.log("Error calculating job", error);
|
||||
logger.log("autohouse-job-calculate-error", "ERROR", "api", null, {
|
||||
error,
|
||||
});
|
||||
|
||||
errorCallback({ job, error });
|
||||
}
|
||||
};
|
||||
@@ -510,7 +526,7 @@ const CreateCosts = (job) => {
|
||||
//At the bill level.
|
||||
bill_val.billlines.map((line_val) => {
|
||||
//At the bill line level.
|
||||
//console.log("JobCostingPartsTable -> line_val", line_val);
|
||||
|
||||
if (!bill_acc[line_val.cost_center])
|
||||
bill_acc[line_val.cost_center] = Dinero();
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ require("dotenv").config({
|
||||
const axios = require("axios");
|
||||
let nodemailer = require("nodemailer");
|
||||
let aws = require("aws-sdk");
|
||||
|
||||
const logger = require("../utils/logger");
|
||||
const ses = new aws.SES({
|
||||
apiVersion: "2010-12-01",
|
||||
region: "ca-central-1",
|
||||
@@ -19,9 +19,13 @@ let transporter = nodemailer.createTransport({
|
||||
});
|
||||
|
||||
exports.sendEmail = async (req, res) => {
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
console.log("[EMAIL] Incoming Message", req.body.from.name);
|
||||
}
|
||||
logger.log("send-email", "DEBUG", req.user.email, null, {
|
||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||
replyTo: req.body.ReplyTo.Email,
|
||||
to: req.body.to,
|
||||
cc: req.body.cc,
|
||||
subject: req.body.subject,
|
||||
});
|
||||
|
||||
let downloadedMedia = [];
|
||||
if (req.body.media && req.body.media.length > 0) {
|
||||
@@ -30,7 +34,14 @@ exports.sendEmail = async (req, res) => {
|
||||
try {
|
||||
return getImage(m);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
logger.log("send-email-error", "ERROR", req.user.email, null, {
|
||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||
replyTo: req.body.ReplyTo.Email,
|
||||
to: req.body.to,
|
||||
cc: req.body.cc,
|
||||
subject: req.body.subject,
|
||||
error,
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
@@ -73,10 +84,26 @@ exports.sendEmail = async (req, res) => {
|
||||
(err, info) => {
|
||||
console.log(err || info);
|
||||
if (info) {
|
||||
console.log("[EMAIL] Email sent: " + info);
|
||||
logger.log("send-email-success", "DEBUG", req.user.email, null, {
|
||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||
replyTo: req.body.ReplyTo.Email,
|
||||
to: req.body.to,
|
||||
cc: req.body.cc,
|
||||
subject: req.body.subject,
|
||||
info,
|
||||
});
|
||||
|
||||
res.json({ success: true, response: info });
|
||||
} else {
|
||||
console.log("[EMAIL] Email send failed. ", err);
|
||||
logger.log("send-email-failure", "ERROR", req.user.email, null, {
|
||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||
replyTo: req.body.ReplyTo.Email,
|
||||
to: req.body.to,
|
||||
cc: req.body.cc,
|
||||
subject: req.body.subject,
|
||||
error: err,
|
||||
});
|
||||
|
||||
res.json({ success: false, error: err });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
var admin = require("firebase-admin");
|
||||
|
||||
const logger = require("../utils/logger");
|
||||
const path = require("path");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
@@ -26,8 +26,20 @@ const adminEmail = [
|
||||
];
|
||||
|
||||
exports.updateUser = (req, res) => {
|
||||
console.log("USer Requesting", req.user);
|
||||
logger.log("admin-update-user", "WARN", req.user.email, null, {
|
||||
request: req.body,
|
||||
});
|
||||
if (!adminEmail.includes(req.user.email)) {
|
||||
logger.log(
|
||||
"admin-update-user-unauthorized",
|
||||
"ERROR",
|
||||
req.user.email,
|
||||
null,
|
||||
{
|
||||
request: req.body,
|
||||
user: req.user,
|
||||
}
|
||||
);
|
||||
res.sendStatus(404);
|
||||
}
|
||||
|
||||
@@ -48,11 +60,16 @@ exports.updateUser = (req, res) => {
|
||||
)
|
||||
.then((userRecord) => {
|
||||
// See the UserRecord reference doc for the contents of userRecord.
|
||||
console.log("Successfully updated user", userRecord.toJSON());
|
||||
|
||||
logger.log("admin-update-user-success", "DEBUG", req.user.email, null, {
|
||||
userRecord,
|
||||
});
|
||||
res.json(userRecord);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error updating user:", error);
|
||||
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
|
||||
error,
|
||||
});
|
||||
res.status(500).json(error);
|
||||
});
|
||||
};
|
||||
@@ -85,8 +102,6 @@ exports.sendNotification = (req, res) => {
|
||||
};
|
||||
|
||||
exports.validateFirebaseIdToken = async (req, res, next) => {
|
||||
console.log("Check if request is authorized with Firebase ID token");
|
||||
|
||||
if (
|
||||
(!req.headers.authorization ||
|
||||
!req.headers.authorization.startsWith("Bearer ")) &&
|
||||
@@ -112,7 +127,10 @@ exports.validateFirebaseIdToken = async (req, res, next) => {
|
||||
} else {
|
||||
// No cookie
|
||||
console.error("Unauthorized attempt. No cookie provided.");
|
||||
|
||||
logger.log("api-unauthorized-call", "WARN", null, null, {
|
||||
req,
|
||||
type: "no-cookie",
|
||||
});
|
||||
res.status(403).send("Unauthorized");
|
||||
return;
|
||||
}
|
||||
@@ -124,7 +142,12 @@ exports.validateFirebaseIdToken = async (req, res, next) => {
|
||||
next();
|
||||
return;
|
||||
} catch (error) {
|
||||
console.error("Error while verifying Firebase ID token:", error);
|
||||
logger.log("api-unauthorized-call", "WARN", null, null, {
|
||||
req,
|
||||
type: "unauthroized",
|
||||
error,
|
||||
});
|
||||
|
||||
res.status(403).send("Unauthorized");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
|
||||
ownr_zip
|
||||
ownr_city
|
||||
ownr_st
|
||||
ownr_ea
|
||||
ins_co_nm
|
||||
job_totals
|
||||
rate_la1
|
||||
@@ -751,6 +752,7 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
|
||||
ca_customer_gst
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
id
|
||||
db_ref
|
||||
unq_seq
|
||||
line_ind
|
||||
tax_part
|
||||
@@ -790,6 +792,7 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
|
||||
cost_center
|
||||
actualhrs
|
||||
productivehrs
|
||||
flat_rate
|
||||
}
|
||||
bodyshop{
|
||||
id
|
||||
@@ -852,6 +855,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
|
||||
ca_customer_gst
|
||||
joblines(where: {removed: {_eq: false}}) {
|
||||
id
|
||||
db_ref
|
||||
unq_seq
|
||||
line_ind
|
||||
tax_part
|
||||
@@ -891,6 +895,7 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
|
||||
cost_center
|
||||
actualhrs
|
||||
productivehrs
|
||||
flat_rate
|
||||
}
|
||||
bodyshop {
|
||||
id
|
||||
@@ -928,3 +933,105 @@ exports.GET_AUTOHOUSE_SHOPS = `query GET_AUTOHOUSE_SHOPS {
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
exports.GET_CDK_ALLOCATIONS = `
|
||||
query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
|
||||
jobs_by_pk(id: $id) {
|
||||
bodyshop{
|
||||
id
|
||||
md_responsibility_centers
|
||||
}
|
||||
ro_number
|
||||
invoice_allocation
|
||||
ins_co_id
|
||||
id
|
||||
ded_amt
|
||||
ded_status
|
||||
depreciation_taxes
|
||||
other_amount_payable
|
||||
towing_payable
|
||||
storage_payable
|
||||
adjustment_bottom_line
|
||||
federal_tax_rate
|
||||
state_tax_rate
|
||||
local_tax_rate
|
||||
tax_tow_rt
|
||||
tax_str_rt
|
||||
tax_paint_mat_rt
|
||||
tax_sub_rt
|
||||
tax_lbr_rt
|
||||
tax_levies_rt
|
||||
parts_tax_rates
|
||||
job_totals
|
||||
rate_la1
|
||||
rate_la2
|
||||
rate_la3
|
||||
rate_la4
|
||||
rate_laa
|
||||
rate_lab
|
||||
rate_lad
|
||||
rate_lae
|
||||
rate_laf
|
||||
rate_lag
|
||||
rate_lam
|
||||
rate_lar
|
||||
rate_las
|
||||
rate_lau
|
||||
rate_ma2s
|
||||
rate_ma2t
|
||||
rate_ma3s
|
||||
rate_mabl
|
||||
rate_macs
|
||||
rate_mahw
|
||||
rate_mapa
|
||||
rate_mash
|
||||
rate_matd
|
||||
status
|
||||
date_exported
|
||||
date_invoiced
|
||||
voided
|
||||
scheduled_completion
|
||||
actual_completion
|
||||
scheduled_delivery
|
||||
actual_delivery
|
||||
scheduled_in
|
||||
actual_in
|
||||
bills {
|
||||
id
|
||||
federal_tax_rate
|
||||
local_tax_rate
|
||||
state_tax_rate
|
||||
is_credit_memo
|
||||
billlines {
|
||||
actual_cost
|
||||
cost_center
|
||||
id
|
||||
quantity
|
||||
applicable_taxes
|
||||
}
|
||||
}
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
id
|
||||
removed
|
||||
tax_part
|
||||
line_desc
|
||||
prt_dsmk_p
|
||||
prt_dsmk_m
|
||||
part_type
|
||||
oem_partno
|
||||
db_price
|
||||
act_price
|
||||
part_qty
|
||||
mod_lbr_ty
|
||||
db_hrs
|
||||
mod_lb_hrs
|
||||
lbr_op
|
||||
lbr_amt
|
||||
op_code_desc
|
||||
profitcenter_labor
|
||||
profitcenter_part
|
||||
prt_dsmk_p
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -3,16 +3,16 @@ const queries = require("../graphql-client/queries");
|
||||
//const client = require("../graphql-client/graphql-client").client;
|
||||
const _ = require("lodash");
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
|
||||
const logger = require("../utils/logger");
|
||||
// Dinero.defaultCurrency = "USD";
|
||||
// Dinero.globalLocale = "en-CA";
|
||||
Dinero.globalRoundingMode = "HALF_EVEN";
|
||||
|
||||
async function JobCosting(req, res) {
|
||||
const { jobid } = req.body;
|
||||
console.time("Query for Data");
|
||||
const BearerToken = req.headers.authorization;
|
||||
|
||||
const BearerToken = req.headers.authorization;
|
||||
logger.log("job-costing-start", "DEBUG", req.user.email, jobid, null);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||
headers: {
|
||||
Authorization: BearerToken,
|
||||
@@ -25,15 +25,16 @@ async function JobCosting(req, res) {
|
||||
.request(queries.QUERY_JOB_COSTING_DETAILS, {
|
||||
id: jobid,
|
||||
});
|
||||
console.timeEnd("querydata");
|
||||
|
||||
console.time(`generatecostingdata-${resp.jobs_by_pk.id}`);
|
||||
const ret = GenerateCostingData(resp.jobs_by_pk);
|
||||
console.timeEnd(`generatecostingdata-${resp.jobs_by_pk.id}`);
|
||||
|
||||
res.status(200).json(ret);
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
logger.log("job-costing-error", "ERROR", req.user.email, jobid, {
|
||||
jobid,
|
||||
error,
|
||||
});
|
||||
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
}
|
||||
}
|
||||
@@ -41,7 +42,8 @@ async function JobCosting(req, res) {
|
||||
async function JobCostingMulti(req, res) {
|
||||
const { jobids } = req.body;
|
||||
const BearerToken = req.headers.authorization;
|
||||
console.time("JobCostingMultiQueryExecution");
|
||||
logger.log("job-costing-multi-start", "DEBUG", req.user.email, jobids, null);
|
||||
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||
headers: {
|
||||
Authorization: BearerToken,
|
||||
@@ -78,12 +80,8 @@ async function JobCostingMulti(req, res) {
|
||||
|
||||
const ret = {};
|
||||
resp.jobs.map((job) => {
|
||||
console.time(`CostingData-${job.id}`);
|
||||
const costingData = GenerateCostingData(job);
|
||||
ret[job.id] = costingData;
|
||||
console.timeEnd(`CostingData-${job.id}`);
|
||||
|
||||
console.time(`SummaryOfCostingData-${job.id}`);
|
||||
|
||||
//Merge on a cost center basis.
|
||||
|
||||
@@ -165,7 +163,6 @@ async function JobCostingMulti(req, res) {
|
||||
costingData.summaryData.totalPartsGp
|
||||
);
|
||||
|
||||
console.timeEnd(`SummaryOfCostingData-${job.id}`);
|
||||
//Take the summary data & add it to total summary data.
|
||||
});
|
||||
|
||||
@@ -220,15 +217,16 @@ async function JobCostingMulti(req, res) {
|
||||
|
||||
//Calculate thte total gross profit percentages.
|
||||
|
||||
console.timeEnd("JobCostingMultiQueryExecution");
|
||||
|
||||
res.status(200).json({
|
||||
allCostCenterData: finalCostingdata,
|
||||
allSummaryData: multiSummary.summaryData,
|
||||
data: ret,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
logger.log("job-costing-multi-error", "ERROR", req.user.email, [jobids], {
|
||||
jobids,
|
||||
error,
|
||||
});
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
}
|
||||
}
|
||||
@@ -242,12 +240,22 @@ function GenerateCostingData(job) {
|
||||
);
|
||||
|
||||
const materialsHours = { mapaHrs: 0, mashHrs: 0 };
|
||||
let hasMapaLine = false;
|
||||
let hasMashLine = false;
|
||||
|
||||
//Massage the data.
|
||||
const jobLineTotalsByProfitCenter =
|
||||
job &&
|
||||
job.joblines.reduce(
|
||||
(acc, val) => {
|
||||
//Parts Lines
|
||||
if (val.db_ref === "936008") {
|
||||
//If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
|
||||
hasMapaLine = true;
|
||||
}
|
||||
if (val.db_ref === "936007") {
|
||||
hasMashLine = true;
|
||||
}
|
||||
if (val.mod_lbr_ty) {
|
||||
const laborProfitCenter =
|
||||
val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "?";
|
||||
@@ -265,32 +273,11 @@ function GenerateCostingData(job) {
|
||||
acc.labor[laborProfitCenter].add(laborAmount);
|
||||
|
||||
if (val.mod_lbr_ty === "LAR") {
|
||||
if (!acc.parts[defaultProfits["MAPA"]])
|
||||
acc.parts[defaultProfits["MAPA"]] = Dinero();
|
||||
|
||||
materialsHours.mapaHrs += val.mod_lb_hrs || 0;
|
||||
acc.parts[defaultProfits["MAPA"]] = acc.parts[
|
||||
defaultProfits["MAPA"]
|
||||
].add(
|
||||
Dinero({
|
||||
amount: Math.round((job.rate_mapa || 0) * 100),
|
||||
}).multiply(val.mod_lb_hrs || 0)
|
||||
);
|
||||
}
|
||||
if (!acc.parts[defaultProfits["MASH"]])
|
||||
acc.parts[defaultProfits["MASH"]] = Dinero();
|
||||
|
||||
if (val.mod_lbr_ty !== "LAR") {
|
||||
acc.parts[defaultProfits["MASH"]] = acc.parts[
|
||||
defaultProfits["MASH"]
|
||||
].add(
|
||||
Dinero({
|
||||
amount: Math.round((job.rate_mash || 0) * 100),
|
||||
}).multiply(val.mod_lb_hrs || 0)
|
||||
);
|
||||
materialsHours.mashHrs += val.mod_lb_hrs || 0;
|
||||
}
|
||||
//If labor line, add to paint and shop materials.
|
||||
}
|
||||
|
||||
if (val.part_type && val.part_type !== "PAE") {
|
||||
@@ -358,6 +345,27 @@ function GenerateCostingData(job) {
|
||||
{ parts: {}, labor: {} }
|
||||
);
|
||||
|
||||
if (!hasMapaLine) {
|
||||
if (!jobLineTotalsByProfitCenter.parts[defaultProfits["MAPA"]])
|
||||
jobLineTotalsByProfitCenter.parts[defaultProfits["MAPA"]] = Dinero();
|
||||
jobLineTotalsByProfitCenter.parts[defaultProfits["MAPA"]] =
|
||||
jobLineTotalsByProfitCenter.parts[defaultProfits["MAPA"]].add(
|
||||
Dinero({
|
||||
amount: Math.round((job.rate_mapa || 0) * 100),
|
||||
}).multiply(materialsHours.mapaHrs || 0)
|
||||
);
|
||||
}
|
||||
if (!hasMashLine) {
|
||||
if (!jobLineTotalsByProfitCenter.parts[defaultProfits["MASH"]])
|
||||
jobLineTotalsByProfitCenter.parts[defaultProfits["MASH"]] = Dinero();
|
||||
jobLineTotalsByProfitCenter.parts[defaultProfits["MASH"]] =
|
||||
jobLineTotalsByProfitCenter.parts[defaultProfits["MASH"]].add(
|
||||
Dinero({
|
||||
amount: Math.round((job.rate_mash || 0) * 100),
|
||||
}).multiply(materialsHours.mashHrs || 0)
|
||||
);
|
||||
}
|
||||
|
||||
const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
|
||||
//At the bill level.
|
||||
bill_val.billlines.map((line_val) => {
|
||||
@@ -437,7 +445,11 @@ function GenerateCostingData(job) {
|
||||
].add(
|
||||
Dinero({
|
||||
amount: Math.round((ticket_val.rate || 0) * 100),
|
||||
}).multiply(ticket_val.actualhrs || ticket_val.productivehrs || 0)
|
||||
}).multiply(
|
||||
ticket_val.flat_rate
|
||||
? ticket_val.productivehrs || ticket_val.actualhrs || 0
|
||||
: ticket_val.actualhrs || ticket_val.productivehrs || 0
|
||||
) //Should base this on the employee.
|
||||
);
|
||||
|
||||
return ticket_acc;
|
||||
@@ -603,17 +615,14 @@ const formatGpPercent = (gppercent) => {
|
||||
|
||||
//Verify that this stays in line with jobs-close-auto-allocate logic from the application.
|
||||
const getAdditionalCostCenter = (jl, profitCenters) => {
|
||||
console.log("Checking additional cost center", jl.line_desc);
|
||||
if (!jl.part_type && !jl.mod_lbr_ty) {
|
||||
const lineDesc = jl.line_desc.toLowerCase();
|
||||
//This logic is covered prior and assigned based on the labor type of the lines
|
||||
// if (lineDesc.includes("shop materials")) {
|
||||
// return profitCenters["MASH"];
|
||||
// } else if (lineDesc.includes("paint/materials")) {
|
||||
// return profitCenters["MAPA"];
|
||||
// } else
|
||||
//End covered logic
|
||||
if (lineDesc.includes("ats amount")) {
|
||||
|
||||
if (lineDesc.includes("shop mat")) {
|
||||
return profitCenters["MASH"];
|
||||
} else if (lineDesc.includes("paint/mat")) {
|
||||
return profitCenters["MAPA"];
|
||||
} else if (lineDesc.includes("ats amount")) {
|
||||
return profitCenters["ATS"];
|
||||
} else {
|
||||
return null;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const Dinero = require("dinero.js");
|
||||
const queries = require("../graphql-client/queries");
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
|
||||
const logger = require("../utils/logger");
|
||||
// Dinero.defaultCurrency = "USD";
|
||||
// Dinero.globalLocale = "en-CA";
|
||||
Dinero.globalRoundingMode = "HALF_EVEN";
|
||||
@@ -9,7 +9,7 @@ Dinero.globalRoundingMode = "HALF_EVEN";
|
||||
exports.totalsSsu = async function (req, res) {
|
||||
const BearerToken = req.headers.authorization;
|
||||
const { id } = req.body;
|
||||
|
||||
logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||
headers: {
|
||||
Authorization: BearerToken,
|
||||
@@ -43,7 +43,10 @@ exports.totalsSsu = async function (req, res) {
|
||||
|
||||
res.status(200).send();
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
logger.log("job-totals-ssu-error", "ERROR", req.user.email, id, {
|
||||
jobid: id,
|
||||
error,
|
||||
});
|
||||
res.status(503).send();
|
||||
}
|
||||
};
|
||||
@@ -51,9 +54,6 @@ exports.totalsSsu = async function (req, res) {
|
||||
//IMPORTANT*** These two functions MUST be mirrrored.
|
||||
async function TotalsServerSide(req, res) {
|
||||
const { job } = req.body;
|
||||
console.log(
|
||||
`Calculating Job Totals on the server side for ${job.id} - ${job.ro_number}`
|
||||
);
|
||||
try {
|
||||
let ret = {
|
||||
parts: CalculatePartsTotals(job.joblines),
|
||||
@@ -64,14 +64,20 @@ async function TotalsServerSide(req, res) {
|
||||
|
||||
return ret;
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
logger.log("job-totals-ssu-error", "ERROR", req.user.email, job.id, {
|
||||
jobid: job.id,
|
||||
error,
|
||||
});
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
}
|
||||
}
|
||||
|
||||
async function Totals(req, res) {
|
||||
const { job } = req.body;
|
||||
console.log(`Calculating Job Totals for ${job.id} - ${job.ro_number}`);
|
||||
logger.log("job-totals", "DEBUG", req.user.email, job.id, {
|
||||
jobid: job.id,
|
||||
});
|
||||
|
||||
try {
|
||||
let ret = {
|
||||
parts: CalculatePartsTotals(job.joblines),
|
||||
@@ -82,7 +88,10 @@ async function Totals(req, res) {
|
||||
|
||||
res.status(200).json(ret);
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
logger.log("job-totals-error", "ERROR", req.user.email, job.id, {
|
||||
jobid: job.id,
|
||||
error,
|
||||
});
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
}
|
||||
}
|
||||
@@ -157,7 +166,28 @@ function CalculateRatesTotals(ratesList) {
|
||||
},
|
||||
};
|
||||
|
||||
//Determine if there are MAPA and MASH lines already on the estimate.
|
||||
//If there are, don't do anything extra (mitchell estimate)
|
||||
//Otherwise, calculate them and add them to the default MAPA and MASH centers.
|
||||
let hasMapaLine = false;
|
||||
let hasMashLine = false;
|
||||
|
||||
jobLines.forEach((item) => {
|
||||
//IO-1317 Use the lines on the estimate if they exist instead.
|
||||
if (item.db_ref === "936008") {
|
||||
//If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
|
||||
hasMapaLine = true;
|
||||
ret["mapa"].total = Dinero({
|
||||
amount: Math.round((item.act_price || 0) * 100),
|
||||
});
|
||||
}
|
||||
if (item.db_ref === "936007") {
|
||||
hasMashLine = true;
|
||||
ret["mash"].total = Dinero({
|
||||
amount: Math.round((item.act_price || 0) * 100),
|
||||
});
|
||||
}
|
||||
|
||||
if (item.mod_lbr_ty) {
|
||||
//There's a labor type, assign the hours.
|
||||
ret[item.mod_lbr_ty.toLowerCase()].hours =
|
||||
@@ -173,11 +203,22 @@ function CalculateRatesTotals(ratesList) {
|
||||
|
||||
let subtotal = Dinero({ amount: 0 });
|
||||
let rates_subtotal = Dinero({ amount: 0 });
|
||||
|
||||
for (const property in ret) {
|
||||
ret[property].total = Dinero({
|
||||
amount: Math.round((ret[property].rate || 0) * 100),
|
||||
}).multiply(ret[property].hours);
|
||||
//Skip calculating mapa and mash if we got the amounts.
|
||||
if (
|
||||
!(
|
||||
(property === "mapa" && hasMapaLine) ||
|
||||
(property === "mash" && hasMashLine)
|
||||
)
|
||||
) {
|
||||
ret[property].total = Dinero({
|
||||
amount: Math.round((ret[property].rate || 0) * 100),
|
||||
}).multiply(ret[property].hours);
|
||||
}
|
||||
|
||||
subtotal = subtotal.add(ret[property].total);
|
||||
|
||||
if (property !== "mapa" && property !== "mash")
|
||||
rates_subtotal = rates_subtotal.add(ret[property].total);
|
||||
}
|
||||
@@ -363,7 +404,8 @@ function CalculateTaxesTotals(job, otherTotals) {
|
||||
job.joblines
|
||||
.filter((jl) => !jl.removed)
|
||||
.forEach((val) => {
|
||||
if (!val.tax_part || (!val.part_type && IsAdditionalCost(val))) {
|
||||
if (!val.tax_part) return;
|
||||
if (!val.part_type && IsAdditionalCost(val)) {
|
||||
additionalItemsTax = additionalItemsTax.add(
|
||||
Dinero({ amount: Math.round((val.act_price || 0) * 100) })
|
||||
.multiply(val.part_qty || 0)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
const path = require("path");
|
||||
const _ = require("lodash");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
@@ -11,8 +13,7 @@ var cloudinary = require("cloudinary").v2;
|
||||
cloudinary.config(process.env.CLOUDINARY_URL);
|
||||
|
||||
exports.createSignedUploadURL = (req, res) => {
|
||||
console.log("Request to create signed upload URL for Cloudinary.", req.body);
|
||||
|
||||
logger.log("media-signed-upload", "DEBUG", req.user.email, null, null);
|
||||
res.send(
|
||||
cloudinary.utils.api_sign_request(
|
||||
req.body,
|
||||
@@ -23,6 +24,7 @@ exports.createSignedUploadURL = (req, res) => {
|
||||
|
||||
exports.downloadFiles = (req, res) => {
|
||||
const { ids } = req.body;
|
||||
logger.log("media-bulk-download", "DEBUG", req.user.email, ids, null);
|
||||
|
||||
const url = cloudinary.utils.download_zip_url({
|
||||
public_ids: ids,
|
||||
@@ -34,7 +36,8 @@ exports.downloadFiles = (req, res) => {
|
||||
exports.deleteFiles = async (req, res) => {
|
||||
const { ids } = req.body;
|
||||
const types = _.groupBy(ids, (x) => DetermineFileType(x.type));
|
||||
console.log("🚀 ~ file: media.js ~ line 28 ~ types", types);
|
||||
|
||||
logger.log("media-bulk-delete", "DEBUG", req.user.email, ids, null);
|
||||
|
||||
const returns = [];
|
||||
if (types.image) {
|
||||
@@ -65,16 +68,15 @@ exports.deleteFiles = async (req, res) => {
|
||||
)
|
||||
);
|
||||
}
|
||||
console.log("🚀 ~ file: media.js ~ line 40 ~ returns", returns);
|
||||
|
||||
res.send(returns);
|
||||
};
|
||||
|
||||
exports.renameKeys = async (req, res) => {
|
||||
const { documents } = req.body;
|
||||
//{id: "", from: "", to:""}
|
||||
logger.log("media-bulk-rename", "DEBUG", req.user.email, null, documents);
|
||||
|
||||
const proms = [];
|
||||
console.log("Documents", documents);
|
||||
documents.forEach((d) => {
|
||||
proms.push(
|
||||
(async () => {
|
||||
|
||||
@@ -5,23 +5,25 @@ require("dotenv").config({
|
||||
`.env.${process.env.NODE_ENV || "development"}`
|
||||
),
|
||||
});
|
||||
|
||||
const logger = require("../utils/logger");
|
||||
const inlineCssTool = require("inline-css");
|
||||
|
||||
exports.inlinecss = (req, res) => {
|
||||
//Perform request validation
|
||||
|
||||
console.log("[CSS] New Inline CSS Request.");
|
||||
logger.log("email-inline-css", "DEBUG", req.user.email, null, null);
|
||||
|
||||
const { html, url } = req.body;
|
||||
|
||||
inlineCssTool(html, { url: url })
|
||||
.then((inlinedHtml) => {
|
||||
console.log("Inline success.");
|
||||
res.send(inlinedHtml);
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error while inlining CSS", JSON.stringify(error));
|
||||
logger.log("email-inline-css-error", "ERROR", req.user.email, null, {
|
||||
error,
|
||||
});
|
||||
|
||||
res.send(error);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,212 +0,0 @@
|
||||
// const path = require("path");
|
||||
// const moment = require("moment");
|
||||
// require("dotenv").config({
|
||||
// path: path.resolve(
|
||||
// process.cwd(),
|
||||
// `.env.${process.env.NODE_ENV || "development"}`
|
||||
// ),
|
||||
// });
|
||||
// var _ = require("lodash");
|
||||
// const Handlebars = require("handlebars");
|
||||
// const phone = require("phone");
|
||||
// var Dinero = require("dinero.js");
|
||||
// Dinero.defaultCurrency = "CAD";
|
||||
// Dinero.globalLocale = "en-CA";
|
||||
|
||||
// //Usage: {{moment appointments_by_pk.start format="dddd, DD MMMM YYYY"}}
|
||||
|
||||
// Handlebars.registerHelper("round", function (context, block) {
|
||||
// if (context && context.hash) {
|
||||
// block = _.cloneDeep(context);
|
||||
// context = undefined;
|
||||
// }
|
||||
// try {
|
||||
// return context.toFixed(2);
|
||||
// } catch {
|
||||
// return context;
|
||||
// }
|
||||
// });
|
||||
|
||||
// Handlebars.registerHelper("dinerof", function (context, block) {
|
||||
// if (context && context.hash) {
|
||||
// block = _.cloneDeep(context);
|
||||
// context = undefined;
|
||||
// }
|
||||
// var amount = Dinero(context);
|
||||
// if (context) {
|
||||
// return amount.toFormat();
|
||||
// }
|
||||
// return "";
|
||||
// });
|
||||
|
||||
// Handlebars.registerHelper("phonef", function (context, block) {
|
||||
// if (context && context.hash) {
|
||||
// block = _.cloneDeep(context);
|
||||
// context = undefined;
|
||||
// }
|
||||
// var ph = phone(context)[0];
|
||||
// if (context) {
|
||||
// return ph;
|
||||
// }
|
||||
// return "";
|
||||
// });
|
||||
|
||||
// Handlebars.registerHelper("partType", function (context, block) {
|
||||
// if (!context) return "";
|
||||
|
||||
// switch (context.toUpperCase()) {
|
||||
// case "PAA":
|
||||
// return "Aftermarket";
|
||||
// case "PAE":
|
||||
// return "Existing";
|
||||
// case "PAN":
|
||||
// return "OEM";
|
||||
// case "PAO":
|
||||
// return "Other";
|
||||
// case "PAS":
|
||||
// return "Sublet";
|
||||
// case "PASL":
|
||||
// return "Sublet";
|
||||
// case "PAL":
|
||||
// return "LKQ";
|
||||
// case "PAM":
|
||||
// return "Remanufactured";
|
||||
// case "PAC":
|
||||
// return "Chrome";
|
||||
// case "PAP":
|
||||
// return "OEM Partial";
|
||||
// case "PAR":
|
||||
// return "Record";
|
||||
// default:
|
||||
// return context;
|
||||
// }
|
||||
// });
|
||||
|
||||
// Handlebars.registerHelper("lbrType", function (context, block) {
|
||||
// if (!context) return "";
|
||||
|
||||
// switch (context.toUpperCase()) {
|
||||
// case "LAA":
|
||||
// return "Aluminum";
|
||||
// case "LAB":
|
||||
// return "Body";
|
||||
// case "LAD":
|
||||
// return "Diagnostic";
|
||||
// case "LAF":
|
||||
// return "Frame";
|
||||
// case "LAG":
|
||||
// return "Glass";
|
||||
// case "LAM":
|
||||
// return "Mechanical";
|
||||
// case "LAR":
|
||||
// return "Refinish";
|
||||
// case "LAS":
|
||||
// return "Structural";
|
||||
// case "LAU":
|
||||
// return "Detail";
|
||||
|
||||
// default:
|
||||
// return context;
|
||||
// }
|
||||
// });
|
||||
|
||||
// Handlebars.registerHelper("objectKeys", function (obj, block) {
|
||||
// var accum = "";
|
||||
|
||||
// obj &&
|
||||
// Object.keys(obj).map((key) => {
|
||||
// accum += block.fn({ key, value: obj[key] });
|
||||
// });
|
||||
// return accum;
|
||||
// });
|
||||
|
||||
// Handlebars.registerHelper("dinero", function (context, block) {
|
||||
// if (context && context.hash) {
|
||||
// block = _.cloneDeep(context);
|
||||
// context = undefined;
|
||||
// }
|
||||
// var amount = Dinero({
|
||||
// amount: Math.round((context || 0) * 100),
|
||||
// currency: "CAD",
|
||||
// });
|
||||
// return amount.toFormat();
|
||||
// });
|
||||
|
||||
// Handlebars.registerHelper("moment", function (context, block) {
|
||||
// if (context && context.hash) {
|
||||
// block = _.cloneDeep(context);
|
||||
// context = undefined;
|
||||
// }
|
||||
|
||||
// if (!!!context) return "";
|
||||
|
||||
// var date = moment(context);
|
||||
|
||||
// if (block.hash.timezone) {
|
||||
// date.tz(block.hash.timezone);
|
||||
// }
|
||||
|
||||
// var hasFormat = false;
|
||||
|
||||
// // Reset the language back to default before doing anything else
|
||||
// date.locale("en");
|
||||
|
||||
// for (var i in block.hash) {
|
||||
// if (i === "format") {
|
||||
// hasFormat = true;
|
||||
// } else if (date[i]) {
|
||||
// date = date[i](block.hash[i]);
|
||||
// } else {
|
||||
// console.log('moment.js does not support "' + i + '"');
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (hasFormat) {
|
||||
// date = date.format(block.hash.format);
|
||||
// }
|
||||
// return date;
|
||||
// });
|
||||
|
||||
// Handlebars.registerHelper("duration", function (context, block) {
|
||||
// if (context && context.hash) {
|
||||
// block = _.cloneDeep(context);
|
||||
// context = 0;
|
||||
// }
|
||||
// var duration = moment.duration(context);
|
||||
// var hasFormat = false;
|
||||
|
||||
// // Reset the language back to default before doing anything else
|
||||
// duration = duration.lang("en");
|
||||
|
||||
// for (var i in block.hash) {
|
||||
// if (i === "format") {
|
||||
// hasFormat = true;
|
||||
// } else if (duration[i]) {
|
||||
// duration = duration[i](block.hash[i]);
|
||||
// } else {
|
||||
// console.log('moment.js duration does not support "' + i + '"');
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (hasFormat) {
|
||||
// duration = duration.format(block.hash.format);
|
||||
// }
|
||||
// return duration;
|
||||
// });
|
||||
|
||||
exports.render = (req, res) => {
|
||||
// //Perform request validation
|
||||
// let view;
|
||||
// console.log("[HJS Render] New Render Request.");
|
||||
// //console.log("[HJS Render] Context", req.body.context);
|
||||
// if (req.body.context.bodyshop.template_header) {
|
||||
// console.log("[HJS Render] Including Header");
|
||||
// //view = req.body.view;
|
||||
// view = `${req.body.context.bodyshop.template_header}${req.body.view}`;
|
||||
// } else {
|
||||
// console.log("[HJS Render] No header to include.");
|
||||
// view = req.body.view;
|
||||
// }
|
||||
// var template = Handlebars.compile(view);
|
||||
// res.send(template(req.body.context));
|
||||
};
|
||||
@@ -3,7 +3,7 @@ const path = require("path");
|
||||
const queries = require("../graphql-client/queries");
|
||||
const Dinero = require("dinero.js");
|
||||
const moment = require("moment");
|
||||
|
||||
const logger = require("../utils/logger");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
@@ -12,10 +12,10 @@ require("dotenv").config({
|
||||
});
|
||||
|
||||
exports.job = async (req, res) => {
|
||||
const BearerToken = req.headers.authorization;
|
||||
const { jobId } = req.body;
|
||||
try {
|
||||
const BearerToken = req.headers.authorization;
|
||||
const { jobId } = req.body;
|
||||
console.log("exports.job -> jobId", jobId);
|
||||
logger.log("smart-scheduling-start", "DEBUG", req.user.email, jobId, null);
|
||||
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||
headers: {
|
||||
@@ -133,10 +133,12 @@ exports.job = async (req, res) => {
|
||||
)
|
||||
possibleDates.push(new Date(bmkey).toISOString().substr(0, 10));
|
||||
});
|
||||
console.log("possibleDates", possibleDates, "bucketMatrix", bucketMatrix);
|
||||
|
||||
res.json(possibleDates);
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, {
|
||||
error,
|
||||
});
|
||||
res.status(400).send(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -10,16 +10,29 @@ const client = require("../graphql-client/graphql-client").client;
|
||||
const queries = require("../graphql-client/queries");
|
||||
const { phone } = require("phone");
|
||||
const admin = require("../firebase/firebase-handler").admin;
|
||||
|
||||
const logger = require("../utils/logger");
|
||||
exports.receive = (req, res) => {
|
||||
//Perform request validation
|
||||
console.log("[SMS Receive] Inbound Twilio Message.", req.body.SmsMessageSid);
|
||||
console.log("req.body", req.body);
|
||||
|
||||
logger.log("sms-inbound", "DEBUG", "api", null, {
|
||||
msid: req.body.SmsMessageSid,
|
||||
text: req.body.Body,
|
||||
image: !!req.body.MediaUrl0,
|
||||
image_path: generateMediaArray(req.body),
|
||||
});
|
||||
|
||||
if (
|
||||
!!!req.body ||
|
||||
!!!req.body.MessagingServiceSid ||
|
||||
!!!req.body.SmsMessageSid
|
||||
) {
|
||||
logger.log("sms-inbound-error", "ERROR", "api", null, {
|
||||
msid: req.body.SmsMessageSid,
|
||||
text: req.body.Body,
|
||||
image: !!req.body.MediaUrl0,
|
||||
image_path: generateMediaArray(req.body),
|
||||
type: "malformed-request",
|
||||
});
|
||||
res.status(400);
|
||||
res.json({ success: false, error: "Malformed Request" });
|
||||
} else {
|
||||
@@ -29,8 +42,6 @@ exports.receive = (req, res) => {
|
||||
phone: phone(req.body.From).phoneNumber,
|
||||
})
|
||||
.then((response) => {
|
||||
console.log("re", req.body);
|
||||
|
||||
let newMessage = {
|
||||
msid: req.body.SmsMessageSid,
|
||||
text: req.body.Body,
|
||||
@@ -55,10 +66,14 @@ exports.receive = (req, res) => {
|
||||
response.bodyshops[0].conversations[0].id;
|
||||
} else {
|
||||
//We should never get here.
|
||||
console.log(
|
||||
"Massive Error: Duplicate Phone Numbers for MSSID: " +
|
||||
req.body.MessagingServiceSid
|
||||
);
|
||||
logger.log("sms-inbound-error", "ERROR", "api", null, {
|
||||
msid: req.body.SmsMessageSid,
|
||||
text: req.body.Body,
|
||||
image: !!req.body.MediaUrl0,
|
||||
image_path: generateMediaArray(req.body),
|
||||
messagingServiceSid: req.body.MessagingServiceSid,
|
||||
type: "duplicate-phone",
|
||||
});
|
||||
}
|
||||
|
||||
client
|
||||
@@ -67,6 +82,9 @@ exports.receive = (req, res) => {
|
||||
conversationid: response.bodyshops[0].conversations[0].id,
|
||||
})
|
||||
.then((r2) => {
|
||||
logger.log("sms-inbound-success", "DEBUG", "api", null, {
|
||||
newMessage,
|
||||
});
|
||||
res.status(200).send("");
|
||||
|
||||
const arrayOfAllUserFcmTokens =
|
||||
@@ -109,7 +127,15 @@ exports.receive = (req, res) => {
|
||||
// });
|
||||
})
|
||||
.catch((e2) => {
|
||||
console.log("e2", e2);
|
||||
logger.log("sms-inbound-error", "ERROR", "api", null, {
|
||||
msid: req.body.SmsMessageSid,
|
||||
text: req.body.Body,
|
||||
image: !!req.body.MediaUrl0,
|
||||
image_path: generateMediaArray(req.body),
|
||||
messagingServiceSid: req.body.MessagingServiceSid,
|
||||
error: e2,
|
||||
});
|
||||
|
||||
res.sendStatus(500).json(e2);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ require("dotenv").config({
|
||||
const twilio = require("twilio");
|
||||
const { phone } = require("phone");
|
||||
const queries = require("../graphql-client/queries");
|
||||
|
||||
const logger = require("../utils/logger");
|
||||
const client = twilio(
|
||||
process.env.TWILIO_AUTH_TOKEN,
|
||||
process.env.TWILIO_AUTH_KEY
|
||||
@@ -19,9 +19,21 @@ const gqlClient = require("../graphql-client/graphql-client").client;
|
||||
exports.send = (req, res) => {
|
||||
const { to, messagingServiceSid, body, conversationid, selectedMedia } =
|
||||
req.body;
|
||||
console.log("[Sending Sms] " + conversationid + " | " + body);
|
||||
|
||||
logger.log("sms-outbound", "DEBUG", req.user.email, null, {
|
||||
messagingServiceSid: messagingServiceSid,
|
||||
to: phone(to).phoneNumber,
|
||||
mediaUrl: selectedMedia.map((i) => i.src),
|
||||
text: body,
|
||||
conversationid,
|
||||
isoutbound: true,
|
||||
userid: req.user.email,
|
||||
image: req.body.selectedMedia.length > 0,
|
||||
image_path:
|
||||
req.body.selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [],
|
||||
});
|
||||
|
||||
if (!!to && !!messagingServiceSid && !!body && !!conversationid) {
|
||||
console.log(phone(to));
|
||||
client.messages
|
||||
.create({
|
||||
body: body,
|
||||
@@ -46,40 +58,47 @@ exports.send = (req, res) => {
|
||||
.request(queries.INSERT_MESSAGE, { msg: newMessage })
|
||||
.then((r2) => {
|
||||
//console.log("Responding GQL Message ID", JSON.stringify(r2));
|
||||
logger.log("sms-outbound-success", "DEBUG", req.user.email, null, {
|
||||
msid: message.sid,
|
||||
conversationid,
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
})
|
||||
.catch((e2) => {
|
||||
console.log("e2", e2);
|
||||
logger.log("sms-outbound-error", "ERROR", req.user.email, null, {
|
||||
msid: message.sid,
|
||||
conversationid,
|
||||
error: e2,
|
||||
});
|
||||
|
||||
//res.json({ success: false, message: e2 });
|
||||
});
|
||||
})
|
||||
.catch((e1) => {
|
||||
//res.json({ success: false, message: error });
|
||||
console.log("e1", e1);
|
||||
logger.log("sms-outbound-error", "ERROR", req.user.email, null, {
|
||||
conversationid,
|
||||
error: e1,
|
||||
});
|
||||
});
|
||||
} else {
|
||||
logger.log("sms-outbound-error", "ERROR", req.user.email, null, {
|
||||
type: "missing-parameters",
|
||||
messagingServiceSid: messagingServiceSid,
|
||||
to: phone(to).phoneNumber,
|
||||
text: body,
|
||||
conversationid,
|
||||
isoutbound: true,
|
||||
userid: req.user.email,
|
||||
image: req.body.selectedMedia.length > 0,
|
||||
image_path:
|
||||
req.body.selectedMedia.length > 0
|
||||
? selectedMedia.map((i) => i.src)
|
||||
: [],
|
||||
});
|
||||
res
|
||||
.status(400)
|
||||
.json({ success: false, message: "Missing required parameter(s)." });
|
||||
}
|
||||
};
|
||||
|
||||
// //Image
|
||||
// acc.push({
|
||||
// src: `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType(
|
||||
// value.type
|
||||
// )}/upload/${value.key}`,
|
||||
// thumbnail: `${
|
||||
// process.env.REACT_APP_CLOUDINARY_ENDPOINT
|
||||
// }/${DetermineFileType(value.type)}/upload/${
|
||||
// process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
|
||||
// }/${value.key}`,
|
||||
// thumbnailHeight: 225,
|
||||
// thumbnailWidth: 225,
|
||||
// isSelected: false,
|
||||
// key: value.key,
|
||||
// extension: value.extension,
|
||||
// id: value.id,
|
||||
// type: value.type,
|
||||
// tags: [{ value: value.type, title: value.type }],
|
||||
// });
|
||||
|
||||
@@ -9,6 +9,7 @@ require("dotenv").config({
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const queries = require("../graphql-client/queries");
|
||||
const { phone } = require("phone");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
exports.status = (req, res) => {
|
||||
const { SmsSid, SmsStatus } = req.body;
|
||||
@@ -18,10 +19,17 @@ exports.status = (req, res) => {
|
||||
fields: { status: SmsStatus },
|
||||
})
|
||||
.then((response) => {
|
||||
console.log("Message Updated", JSON.stringify(response));
|
||||
logger.log("sms-status-update", "DEBUG", "api", null, {
|
||||
msid: SmsSid,
|
||||
fields: { status: SmsStatus },
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error updating message status", error);
|
||||
logger.log("sms-status-update-error", "ERROR", "api", null, {
|
||||
msid: SmsSid,
|
||||
fields: { status: SmsStatus },
|
||||
error,
|
||||
});
|
||||
});
|
||||
res.sendStatus(200);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const queries = require("../graphql-client/queries");
|
||||
const path = require("path");
|
||||
|
||||
const logger = require("../utils/logger");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
@@ -11,7 +11,7 @@ require("dotenv").config({
|
||||
|
||||
exports.techLogin = async (req, res) => {
|
||||
const { shopid, employeeid, pin } = req.body;
|
||||
|
||||
logger.log("tech-console-login", "DEBUG", req.user.email, null, null);
|
||||
try {
|
||||
const result = await client.request(queries.QUERY_EMPLOYEE_PIN, {
|
||||
shopId: shopid,
|
||||
@@ -28,14 +28,23 @@ exports.techLogin = async (req, res) => {
|
||||
delete dbRecord.pin;
|
||||
technician = dbRecord;
|
||||
} else {
|
||||
logger.log("tech-console-login-error", "DEBUG", req.user.email, null, {
|
||||
type: "wrong-pin",
|
||||
});
|
||||
|
||||
error = "The employee ID and PIN combination are not correct.";
|
||||
}
|
||||
} else {
|
||||
logger.log("tech-console-login-error", "DEBUG", req.user.email, null, {
|
||||
type: "invalid-employee",
|
||||
});
|
||||
error = "The employee ID does not exist.";
|
||||
}
|
||||
res.json({ valid, technician, error });
|
||||
} catch (error) {
|
||||
console.log("error", error);
|
||||
logger.log("tech-console-login-error", "DEBUG", req.user.email, null, {
|
||||
error,
|
||||
});
|
||||
res.status(400).send(error);
|
||||
}
|
||||
};
|
||||
|
||||
24
server/utils/logger.js
Normal file
24
server/utils/logger.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const graylog2 = require("graylog2");
|
||||
|
||||
const logger = new graylog2.graylog({
|
||||
servers: [{ host: "logs.bodyshop.app", port: 12201 }],
|
||||
});
|
||||
|
||||
function log(message, type, user, record, object) {
|
||||
console.log(message, {
|
||||
type,
|
||||
env: process.env.NODE_ENV || "development",
|
||||
user,
|
||||
record,
|
||||
...object,
|
||||
});
|
||||
logger.log(message, {
|
||||
type,
|
||||
env: process.env.NODE_ENV || "development",
|
||||
user,
|
||||
record,
|
||||
...object,
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = { log };
|
||||
@@ -1,5 +1,4 @@
|
||||
const path = require("path");
|
||||
const _ = require("lodash");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
@@ -10,7 +9,11 @@ require("dotenv").config({
|
||||
const { io } = require("../../server");
|
||||
const { admin } = require("../firebase/firebase-handler");
|
||||
const CdkJobExport = require("../cdk/cdk-job-export").default;
|
||||
const CdkGetMakes = require("../cdk/cdk-get-makes").default;
|
||||
const CdkCalculateAllocations =
|
||||
require("../cdk/cdk-calculate-allocations").default;
|
||||
const { isArray } = require("lodash");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
io.use(function (socket, next) {
|
||||
try {
|
||||
@@ -30,6 +33,10 @@ io.use(function (socket, next) {
|
||||
}
|
||||
} catch (error) {
|
||||
console.log("Uncaught connection error:::", error);
|
||||
logger.log("websocket-connection-error", "error", null, null, {
|
||||
token: socket.handshake.auth.token,
|
||||
...error,
|
||||
});
|
||||
next(new Error(`Authentication error ${error}`));
|
||||
}
|
||||
});
|
||||
@@ -46,6 +53,40 @@ io.on("connection", (socket) => {
|
||||
socket.on("cdk-export-job", (jobid) => {
|
||||
CdkJobExport(socket, jobid);
|
||||
});
|
||||
socket.on("cdk-selected-customer", (selectedCustomerId) => {
|
||||
createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`User selected customer ID ${selectedCustomerId}`
|
||||
);
|
||||
socket.selectedCustomerId = selectedCustomerId;
|
||||
//CdkJobExport(socket, jobid);
|
||||
});
|
||||
|
||||
socket.on("cdk-get-makes", async (cdk_dealerid, callback) => {
|
||||
try {
|
||||
const makes = await CdkGetMakes(socket, cdk_dealerid);
|
||||
callback(makes);
|
||||
} catch (error) {
|
||||
createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error in cdk-get-makes WS call. ${JSON.stringify(error, null, 2)}`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("cdk-calculate-allocations", async (jobid, callback) => {
|
||||
const allocations = await CdkCalculateAllocations(socket, jobid);
|
||||
createLogEvent(socket, "DEBUG", `Allocations calculated.`);
|
||||
createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
`Allocations calculated. ${JSON.stringify(allocations, null, 2)}`
|
||||
);
|
||||
|
||||
callback(allocations);
|
||||
});
|
||||
|
||||
socket.on("disconnect", () => {
|
||||
createLogEvent(socket, "DEBUG", `User disconnected.`);
|
||||
@@ -55,7 +96,7 @@ io.on("connection", (socket) => {
|
||||
function createLogEvent(socket, level, message) {
|
||||
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) {
|
||||
console.log(
|
||||
`[CDK LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${
|
||||
`[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${
|
||||
socket.id
|
||||
} - ${message}`
|
||||
);
|
||||
@@ -65,6 +106,10 @@ function createLogEvent(socket, level, message) {
|
||||
message,
|
||||
});
|
||||
|
||||
logger.log("ws-log-event", level, socket.user.email, socket.recordid, {
|
||||
wsmessage: message,
|
||||
});
|
||||
|
||||
if (socket.logEvents && isArray(socket.logEvents)) {
|
||||
socket.logEvents.push({
|
||||
timestamp: new Date(),
|
||||
@@ -72,6 +117,9 @@ function createLogEvent(socket, level, message) {
|
||||
message,
|
||||
});
|
||||
}
|
||||
// if (level === "ERROR") {
|
||||
// throw new Error(message);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user