Compare commits
114 Commits
release/20
...
feature/pb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
09187bdb7f | ||
|
|
2aed392fbe | ||
|
|
1a0054a911 | ||
|
|
6e6f3d3d3e | ||
|
|
29df140680 | ||
|
|
f37c67a122 | ||
|
|
e53c9aab72 | ||
|
|
385ad06adc | ||
|
|
d1e2d943a9 | ||
|
|
90aa3557b7 | ||
|
|
8891167183 | ||
|
|
6631e645df | ||
|
|
b7f202969b | ||
|
|
1dc3353ecc | ||
|
|
b1a3f1a7b8 | ||
|
|
89f3a26635 | ||
|
|
506fe9b1af | ||
|
|
37c898d3ce | ||
|
|
e4eac5714a | ||
|
|
9d4a59ca16 | ||
|
|
8ec524061a | ||
|
|
434ed46b5a | ||
|
|
3ece5e0ba2 | ||
|
|
52a383ffb7 | ||
|
|
8ad1d5929a | ||
|
|
445c01499b | ||
|
|
0b05be841d | ||
|
|
6bcb5f2af5 | ||
|
|
7482751c5b | ||
|
|
7a025fff42 | ||
|
|
602fe36638 | ||
|
|
da08fc74f1 | ||
|
|
14af45baf0 | ||
|
|
420a88c505 | ||
|
|
f3c44f8dd1 | ||
|
|
c72111e18b | ||
|
|
1ca2870912 | ||
|
|
db9744e1e5 | ||
|
|
e700095551 | ||
|
|
99196a77ed | ||
|
|
289a8222a0 | ||
|
|
dc10f8d35b | ||
|
|
f448232fe7 | ||
|
|
4baf4b4afa | ||
|
|
1785093023 | ||
|
|
0d65f8d894 | ||
|
|
3d8c390291 | ||
|
|
f0a13856bc | ||
|
|
ad6394783d | ||
|
|
16e9843298 | ||
|
|
a4c949c376 | ||
|
|
0e0d5316b7 | ||
|
|
60cb6ee8fb | ||
|
|
7d3279d21a | ||
|
|
402d13ad99 | ||
|
|
e803f5a2d4 | ||
|
|
3989d0f1e2 | ||
|
|
161d476ab3 | ||
|
|
ce84a89cf3 | ||
|
|
1d210a9e52 | ||
|
|
660f463aea | ||
|
|
ad9f01111c | ||
|
|
1b885e4114 | ||
|
|
b56742bcb2 | ||
|
|
40d4d69a9a | ||
|
|
b54d5beb76 | ||
|
|
5cbcd440f5 | ||
|
|
1cdc34249a | ||
|
|
8bbb218777 | ||
|
|
755ac7f657 | ||
|
|
d7b884ff86 | ||
|
|
fda3620ed0 | ||
|
|
61ad9f0d58 | ||
|
|
804c8ad40a | ||
|
|
0ddf009f8f | ||
|
|
14309b5c96 | ||
|
|
57d9de469a | ||
|
|
404ade396c | ||
|
|
bdad6da6d9 | ||
|
|
e7ef3b94c1 | ||
|
|
c19c92ab7e | ||
|
|
944229bae3 | ||
|
|
661b05d9e3 | ||
|
|
4d1d471a66 | ||
|
|
3e84fbbaf4 | ||
|
|
c4e59c1a5e | ||
|
|
9ee8e9007a | ||
|
|
4d52a5c44a | ||
|
|
fff9073f9d | ||
|
|
71f0b8a005 | ||
|
|
d2b965f79e | ||
|
|
a02aa71a95 | ||
|
|
7562bf5c95 | ||
|
|
f9521483e2 | ||
|
|
ca85858885 | ||
|
|
c7b3a94533 | ||
|
|
b010c9ecb0 | ||
|
|
0b98d04bac | ||
|
|
e25e388e59 | ||
|
|
10654b7916 | ||
|
|
ff049ad3e8 | ||
|
|
3572fff2c1 | ||
|
|
851f1c265f | ||
|
|
c104ee4fd9 | ||
|
|
e550baf59d | ||
|
|
ca2ded047b | ||
|
|
fd43d7d56d | ||
|
|
e899e4545f | ||
|
|
2e28a4a790 | ||
|
|
891aa649e8 | ||
|
|
a8a0167123 | ||
|
|
4b55719f86 | ||
|
|
5387ff207c | ||
|
|
e3804b103b |
@@ -1,4 +1,4 @@
|
||||
<babeledit_project be_version="2.7.1" version="1.2">
|
||||
<babeledit_project version="1.2" be_version="2.7.1">
|
||||
<!--
|
||||
|
||||
BabelEdit project file
|
||||
@@ -4283,6 +4283,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>md_ded_notes</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>
|
||||
<folder_node>
|
||||
<name>md_hour_split</name>
|
||||
<children>
|
||||
@@ -7120,6 +7141,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>color</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>default_arrived</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -7456,6 +7498,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>production_colors</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>production_statuses</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -8019,6 +8082,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>estimators</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>insurancecos</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -16878,6 +16962,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>prt_dsmk_m</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>prt_dsmk_p</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -17428,6 +17533,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>changestimator</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>convert</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -19674,6 +19800,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>ded_note</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>ded_status</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -21138,6 +21285,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>loss_of_use</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>ma2s</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -25960,6 +26128,27 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>savebeforeconversion</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>scheduledinchange</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -33168,6 +33357,69 @@
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>mpi_animal_checklist</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>mpi_eglass_auth</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
<description></description>
|
||||
<comment></comment>
|
||||
<default_text></default_text>
|
||||
<translations>
|
||||
<translation>
|
||||
<language>en-US</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>es-MX</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
<translation>
|
||||
<language>fr-CA</language>
|
||||
<approved>false</approved>
|
||||
</translation>
|
||||
</translations>
|
||||
</concept_node>
|
||||
<concept_node>
|
||||
<name>mpi_final_acct_sheet</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>paint_grid</name>
|
||||
<definition_loaded>false</definition_loaded>
|
||||
@@ -36826,6 +37078,37 @@
|
||||
</folder_node>
|
||||
</children>
|
||||
</folder_node>
|
||||
<folder_node>
|
||||
<name>schedule</name>
|
||||
<children>
|
||||
<folder_node>
|
||||
<name>labels</name>
|
||||
<children>
|
||||
<concept_node>
|
||||
<name>manualevent</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>scoreboard</name>
|
||||
<children>
|
||||
|
||||
@@ -5,36 +5,37 @@
|
||||
"proxy": "http://localhost:5000",
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.4.16",
|
||||
"@craco/craco": "^6.3.0",
|
||||
"@craco/craco": "^6.4.0",
|
||||
"@fingerprintjs/fingerprintjs": "^3.3.0",
|
||||
"@lourenci/react-kanban": "^2.1.0",
|
||||
"@openreplay/tracker": "^3.4.4",
|
||||
"@openreplay/tracker-assist": "^3.4.3",
|
||||
"@openreplay/tracker-assist": "^3.4.4",
|
||||
"@openreplay/tracker-graphql": "^3.0.0",
|
||||
"@openreplay/tracker-redux": "^3.0.0",
|
||||
"@sentry/react": "^6.13.2",
|
||||
"@sentry/tracing": "^6.13.2",
|
||||
"@stripe/react-stripe-js": "^1.5.0",
|
||||
"@stripe/stripe-js": "^1.19.1",
|
||||
"@tanem/react-nprogress": "^3.0.79",
|
||||
"@sentry/react": "^6.13.3",
|
||||
"@sentry/tracing": "^6.13.3",
|
||||
"@splitsoftware/splitio-react": "^1.3.0",
|
||||
"@stripe/react-stripe-js": "^1.6.0",
|
||||
"@stripe/stripe-js": "^1.20.2",
|
||||
"@tanem/react-nprogress": "^3.0.81",
|
||||
"antd": "^4.16.13",
|
||||
"apollo-link-logger": "^2.0.0",
|
||||
"axios": "^0.22.0",
|
||||
"axios": "^0.23.0",
|
||||
"craco-less": "^1.20.0",
|
||||
"dinero.js": "^1.9.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "^10.0.0",
|
||||
"enquire-js": "^0.2.1",
|
||||
"env-cmd": "^10.1.0",
|
||||
"exifr": "^7.1.3",
|
||||
"firebase": "^9.1.1",
|
||||
"graphql": "^15.6.0",
|
||||
"i18next": "^21.2.4",
|
||||
"firebase": "^9.1.3",
|
||||
"graphql": "^15.6.1",
|
||||
"i18next": "^21.3.3",
|
||||
"i18next-browser-languagedetector": "^6.1.2",
|
||||
"jsoneditor": "^9.5.6",
|
||||
"jsreport-browser-client-dist": "^1.3.0",
|
||||
"libphonenumber-js": "^1.9.36",
|
||||
"logrocket": "^2.0.0",
|
||||
"markerjs2": "^2.13.0",
|
||||
"libphonenumber-js": "^1.9.38",
|
||||
"logrocket": "^2.1.1",
|
||||
"markerjs2": "^2.15.0",
|
||||
"moment-business-days": "^1.2.0",
|
||||
"phone": "^3.1.8",
|
||||
"preval.macro": "^5.0.0",
|
||||
@@ -43,7 +44,7 @@
|
||||
"rc-queue-anim": "^2.0.0",
|
||||
"rc-scroll-anim": "^2.7.6",
|
||||
"react": "^17.0.1",
|
||||
"react-big-calendar": "^0.36.1",
|
||||
"react-big-calendar": "^0.38.0",
|
||||
"react-color": "^2.19.3",
|
||||
"react-cookie": "^4.1.1",
|
||||
"react-dom": "^17.0.1",
|
||||
@@ -59,17 +60,17 @@
|
||||
"react-scripts": "^4.0.3",
|
||||
"react-sublime-video": "^0.2.5",
|
||||
"react-virtualized": "^9.22.3",
|
||||
"recharts": "^2.1.4",
|
||||
"recharts": "^2.1.5",
|
||||
"redux": "^4.1.1",
|
||||
"redux-persist": "^6.0.0",
|
||||
"redux-saga": "^1.1.3",
|
||||
"redux-state-sync": "^3.1.2",
|
||||
"reselect": "^4.0.0",
|
||||
"sass": "^1.42.1",
|
||||
"socket.io-client": "^4.2.0",
|
||||
"styled-components": "^5.3.1",
|
||||
"sass": "^1.43.3",
|
||||
"socket.io-client": "^4.3.2",
|
||||
"styled-components": "^5.3.3",
|
||||
"subscriptions-transport-ws": "^0.9.18",
|
||||
"web-vitals": "^2.1.0",
|
||||
"web-vitals": "^2.1.2",
|
||||
"workbox-background-sync": "^6.3.0",
|
||||
"workbox-broadcast-update": "^6.3.0",
|
||||
"workbox-cacheable-response": "^6.3.0",
|
||||
@@ -114,7 +115,7 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@sentry/webpack-plugin": "^1.17.2",
|
||||
"@sentry/webpack-plugin": "^1.18.3",
|
||||
"patch-package": "^6.4.7",
|
||||
"redux-logger": "^3.0.6",
|
||||
"source-map-explorer": "^2.5.2"
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { ApolloProvider } from "@apollo/client";
|
||||
//import trackerRedux from "@openreplay/tracker-redux";
|
||||
import Tracker from "@openreplay/tracker";
|
||||
import trackerGraphQL from "@openreplay/tracker-graphql";
|
||||
import { SplitFactory, SplitSdk } from "@splitsoftware/splitio-react";
|
||||
import { ConfigProvider } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import LogRocket from "logrocket";
|
||||
@@ -6,13 +10,11 @@ import moment from "moment";
|
||||
import React from "react";
|
||||
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";
|
||||
import { getCurrentUser } from "../firebase/firebase.utils";
|
||||
import client from "../utils/GraphQLClient";
|
||||
import App from "./App";
|
||||
|
||||
moment.locale("en-US");
|
||||
|
||||
export const tracker = new Tracker({
|
||||
@@ -36,6 +38,13 @@ export const recordGraphQL = tracker.use(trackerGraphQL());
|
||||
tracker.start();
|
||||
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
|
||||
|
||||
export const factory = SplitSdk({
|
||||
core: {
|
||||
authorizationKey: process.env.REACT_APP_SPLIT_API,
|
||||
key: "anon",
|
||||
},
|
||||
});
|
||||
|
||||
export default function AppContainer() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
@@ -53,7 +62,9 @@ export default function AppContainer() {
|
||||
}}
|
||||
>
|
||||
<GlobalLoadingBar />
|
||||
<App />
|
||||
<SplitFactory factory={factory}>
|
||||
<App />
|
||||
</SplitFactory>
|
||||
</ConfigProvider>
|
||||
</ApolloProvider>
|
||||
);
|
||||
|
||||
@@ -128,3 +128,9 @@
|
||||
.react-kanban-column {
|
||||
background-color: #ddd !important;
|
||||
}
|
||||
|
||||
.production-list-table {
|
||||
td.ant-table-column-sort {
|
||||
background: unset;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,26 @@ import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import PaymentExportButton from "../payment-export-button/payment-export-button.component";
|
||||
import PaymentsExportAllButton from "../payments-export-all-button/payments-export-all-button.component";
|
||||
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
|
||||
export default function AccountingPayablesTableComponent({
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
});
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(AccountingPayablesTableComponent);
|
||||
|
||||
export function AccountingPayablesTableComponent({
|
||||
bodyshop,
|
||||
loading,
|
||||
payments,
|
||||
}) {
|
||||
@@ -163,6 +181,9 @@ export default function AccountingPayablesTableComponent({
|
||||
loadingCallback={setTransInProgress}
|
||||
completedCallback={setSelectedPayments}
|
||||
/>
|
||||
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
|
||||
<QboAuthorizeComponent />
|
||||
)}
|
||||
<Input
|
||||
value={state.search}
|
||||
onChange={handleSearch}
|
||||
|
||||
@@ -199,7 +199,7 @@ export function AccountingReceivablesTableComponent({
|
||||
<Card
|
||||
extra={
|
||||
<Space wrap>
|
||||
{!bodyshop.cdk_dealerid && (
|
||||
{!bodyshop.cdk_dealerid && !bodyshop.pbs_serialnumber && (
|
||||
<JobsExportAllButton
|
||||
jobIds={selectedJobs}
|
||||
disabled={transInProgress || selectedJobs.length === 0}
|
||||
|
||||
@@ -216,7 +216,7 @@ function BillEnterModalContainer({
|
||||
|
||||
if (enterAgain) {
|
||||
form.resetFields();
|
||||
form.setFieldsValue(formValues);
|
||||
form.setFieldsValue({ ...form.getFieldsValue(), billlines: [] });
|
||||
} else {
|
||||
toggleModalVisible();
|
||||
}
|
||||
|
||||
@@ -156,7 +156,6 @@ export function BillEnterModalLinesComponent({
|
||||
setFieldsValue({
|
||||
billlines: getFieldsValue("billlines").billlines.map(
|
||||
(item, idx) => {
|
||||
console.log("Checking", index, idx);
|
||||
if (idx === index) {
|
||||
console.log(
|
||||
"Found and setting.",
|
||||
@@ -502,9 +501,9 @@ const EditableCell = ({
|
||||
labelCol={{ span: 0 }}
|
||||
{...(formItemProps && formItemProps(record))}
|
||||
>
|
||||
{(formInput && formInput(record, record.key)) || children}
|
||||
{(formInput && formInput(record, record.name)) || children}
|
||||
</Form.Item>
|
||||
{additional && additional(record, record.key)}
|
||||
{additional && additional(record, record.name)}
|
||||
</Space>
|
||||
</td>
|
||||
);
|
||||
@@ -516,7 +515,7 @@ const EditableCell = ({
|
||||
name={dataIndex}
|
||||
{...(formItemProps && formItemProps(record))}
|
||||
>
|
||||
{(formInput && formInput(record, record.key)) || children}
|
||||
{(formInput && formInput(record, record.name)) || children}
|
||||
</Form.Item>
|
||||
</td>
|
||||
);
|
||||
|
||||
@@ -53,6 +53,7 @@ export function ContractConvertToRo({
|
||||
const billingLines = [];
|
||||
if (contractLength > 0)
|
||||
billingLines.push({
|
||||
manual_line:true,
|
||||
unq_seq: 1,
|
||||
line_no: 1,
|
||||
line_ref: 1,
|
||||
@@ -70,6 +71,7 @@ export function ContractConvertToRo({
|
||||
contract.kmend - contract.kmstart - contract.dailyfreekm * contractLength;
|
||||
if (mileageDiff > 0) {
|
||||
billingLines.push({
|
||||
manual_line:true,
|
||||
unq_seq: 2,
|
||||
line_no: 2,
|
||||
line_ref: 2,
|
||||
@@ -86,6 +88,7 @@ export function ContractConvertToRo({
|
||||
|
||||
if (values.refuelqty > 0) {
|
||||
billingLines.push({
|
||||
manual_line:true,
|
||||
unq_seq: 3,
|
||||
line_no: 3,
|
||||
line_ref: 3,
|
||||
@@ -101,6 +104,7 @@ export function ContractConvertToRo({
|
||||
}
|
||||
if (values.applyCleanupCharge) {
|
||||
billingLines.push({
|
||||
manual_line:true,
|
||||
unq_seq: 4,
|
||||
line_no: 4,
|
||||
line_ref: 4,
|
||||
@@ -117,6 +121,7 @@ export function ContractConvertToRo({
|
||||
if (contract.damagewaiver) {
|
||||
//Add for cleanup fee.
|
||||
billingLines.push({
|
||||
manual_line:true,
|
||||
unq_seq: 5,
|
||||
line_no: 5,
|
||||
line_ref: 5,
|
||||
|
||||
@@ -259,7 +259,6 @@ export function DmsPostForm({ bodyshop, socket, job }) {
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
disabled={!(fields.length < 3)}
|
||||
onClick={() => {
|
||||
if (fields.length < 3) add();
|
||||
|
||||
@@ -51,7 +51,9 @@ export function EmailOverlayContainer({
|
||||
|
||||
const defaultEmailFrom = {
|
||||
from: {
|
||||
name: `${currentUser.displayName} @ ${bodyshop.shopname}`,
|
||||
name: currentUser.displayName
|
||||
? `${currentUser.displayName} @ ${bodyshop.shopname}`
|
||||
: bodyshop.shopname,
|
||||
address: EmailSettings.fromAddress,
|
||||
},
|
||||
ReplyTo: {
|
||||
|
||||
@@ -21,7 +21,7 @@ export const PhoneItemFormatterValidation = (getFieldValue, name) => ({
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
const p = parsePhoneNumber(value, "CA");
|
||||
if (p.isValid()) {
|
||||
if (p && p.isValid()) {
|
||||
return Promise.resolve();
|
||||
} else {
|
||||
return Promise.reject(i18n.t("general.validation.invalidphone"));
|
||||
|
||||
@@ -209,6 +209,9 @@ export function ScheduleEventComponent({
|
||||
jobId: event.job.id,
|
||||
job: event.job,
|
||||
previousEvent: event.id,
|
||||
color: event.color,
|
||||
alt_transport: event.job && event.job.alt_transport,
|
||||
note: event.note,
|
||||
},
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -186,12 +186,14 @@ export function JobChecklistForm({
|
||||
allow_text_message: job.owner && job.owner.allow_text_message,
|
||||
scheduled_completion:
|
||||
(job && job.scheduled_completion) ||
|
||||
moment().businessAdd(
|
||||
(job.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
job.larhrs.aggregate.sum.mod_lb_hrs) /
|
||||
bodyshop.target_touchtime,
|
||||
"days"
|
||||
),
|
||||
(job.labbrs && job.larhrs
|
||||
? moment().businessAdd(
|
||||
(job.labhrs.aggregate.sum.mod_lb_hrs +
|
||||
job.larhrs.aggregate.sum.mod_lb_hrs) /
|
||||
bodyshop.target_touchtime,
|
||||
"days"
|
||||
)
|
||||
: null),
|
||||
scheduled_delivery: job && job.scheduled_delivery,
|
||||
}),
|
||||
...(type === "deliver" && {
|
||||
|
||||
@@ -162,7 +162,11 @@ export function JobLinesComponent({
|
||||
ellipsis: true,
|
||||
render: (text, record) => (
|
||||
<>
|
||||
<CurrencyFormatter>{record.act_price}</CurrencyFormatter>
|
||||
<CurrencyFormatter>
|
||||
{record.db_ref === "900510" || record.db_ref === "900511"
|
||||
? record.prt_dsmk_m
|
||||
: record.act_price}
|
||||
</CurrencyFormatter>
|
||||
{record.prt_dsmk_p && record.prt_dsmk_p !== 0 ? (
|
||||
<span
|
||||
style={{ marginLeft: ".2rem" }}
|
||||
|
||||
@@ -498,6 +498,12 @@ async function CheckTaxRates(estData, bodyshop) {
|
||||
) {
|
||||
estData.joblines.data[index].tax_part = jl.lbr_tax;
|
||||
}
|
||||
|
||||
//Set markup lines and tax lines as taxable.
|
||||
//900510 is a mark up. 900510 is a discount.
|
||||
if (jl.db_ref === "900510") {
|
||||
estData.joblines.data[index].tax_part = true;
|
||||
}
|
||||
});
|
||||
//}
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { useHistory } from "react-router-dom";
|
||||
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
currentUser: selectCurrentUser,
|
||||
@@ -35,7 +36,7 @@ export function JobsCloseExportButton({
|
||||
|
||||
const handleQbxml = async () => {
|
||||
//Check if it's a CDK setup.
|
||||
if (bodyshop.cdk_dealerid) {
|
||||
if (bodyshop.cdk_dealerid || bodyshop.pbs_serialnumber) {
|
||||
history.push(`/manage/dms?jobId=${jobId}`);
|
||||
return;
|
||||
}
|
||||
@@ -45,10 +46,13 @@ export function JobsCloseExportButton({
|
||||
//Check if it's a QBO Setup.
|
||||
let PartnerResponse;
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
||||
withCredentials: true,
|
||||
jobIds: [jobId],
|
||||
});
|
||||
PartnerResponse = await axios.post(
|
||||
`/qbo/receivables`,
|
||||
{
|
||||
jobIds: [jobId],
|
||||
},
|
||||
|
||||
);
|
||||
} else {
|
||||
//Default is QBD
|
||||
|
||||
|
||||
@@ -31,6 +31,7 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
||||
<th>{t("joblines.fields.line_desc")}</th>
|
||||
<th>{t("joblines.fields.part_type")}</th>
|
||||
<th>{t("joblines.fields.act_price")}</th>
|
||||
<th>{t("joblines.fields.prt_dsmk_m")}</th>
|
||||
<th>{t("joblines.fields.op_code_desc")}</th>
|
||||
<th>{t("joblines.fields.mod_lbr_ty")}</th>
|
||||
<th>{t("joblines.fields.mod_lb_hrs")}</th>
|
||||
@@ -70,6 +71,16 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
||||
<ReadOnlyFormItem type="currency" />
|
||||
</Form.Item>
|
||||
</td>
|
||||
<td>
|
||||
<Form.Item
|
||||
span={2}
|
||||
// label={t("joblines.fields.prt_dsmk_m")}
|
||||
key={`${index}prt_dsmk_m`}
|
||||
name={[field.name, "prt_dsmk_m"]}
|
||||
>
|
||||
<ReadOnlyFormItem type="currency" />
|
||||
</Form.Item>
|
||||
</td>
|
||||
<td>
|
||||
<Form.Item
|
||||
span={2}
|
||||
@@ -108,7 +119,9 @@ export function JobsCloseLines({ bodyshop, job, jobRO }) {
|
||||
labelCol={{ span: 0 }}
|
||||
rules={[
|
||||
{
|
||||
required: !!job.joblines[index].act_price,
|
||||
required:
|
||||
!!job.joblines[index].act_price ||
|
||||
!!job.joblines[index].prt_dsmk_m,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -35,6 +35,7 @@ export function JobsConvertButton({
|
||||
refetch,
|
||||
jobRO,
|
||||
insertAuditTrail,
|
||||
parentFormIsFieldsTouched,
|
||||
}) {
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [loading, setLoading] = useState(false);
|
||||
@@ -43,6 +44,10 @@ export function JobsConvertButton({
|
||||
const [form] = Form.useForm();
|
||||
|
||||
const handleConvert = async (values) => {
|
||||
if (parentFormIsFieldsTouched()) {
|
||||
alert(t("jobs.labels.savebeforeconversion"));
|
||||
return;
|
||||
}
|
||||
setLoading(true);
|
||||
const res = await mutationConvertJob({
|
||||
variables: { jobId: job.id, ...values },
|
||||
|
||||
@@ -150,6 +150,9 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
<Form.Item label={t("jobs.fields.loss_desc")} name="loss_desc">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ponumber")} name="po_number">
|
||||
<Input />
|
||||
</Form.Item>
|
||||
@@ -197,7 +200,7 @@ export function JobsCreateJobsInfo({ bodyshop, form, selected }) {
|
||||
<JobsMarkPstExempt form={form} />
|
||||
<LayoutFormRow>
|
||||
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
|
||||
<CurrencyInput />
|
||||
<CurrencyInput min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ded_status")} name="ded_status">
|
||||
<Select>
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
import { DownOutlined } from "@ant-design/icons";
|
||||
import { Dropdown, Menu } 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";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
|
||||
export function JobsDetailChangeEstimator({ disabled, form, bodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleClick = ({ item, key, keyPath }) => {
|
||||
const est = item.props.value;
|
||||
form.setFieldsValue(est);
|
||||
};
|
||||
|
||||
const menu = (
|
||||
<div>
|
||||
<Menu onClick={handleClick}>
|
||||
{bodyshop.md_estimators.map((est, idx) => (
|
||||
<Menu.Item value={est} key={idx}>
|
||||
{`${est.est_ct_fn} ${est.est_ct_ln}`}
|
||||
</Menu.Item>
|
||||
))}
|
||||
</Menu>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<Dropdown overlay={menu} disabled={disabled}>
|
||||
<a
|
||||
className="ant-dropdown-link"
|
||||
href=" #"
|
||||
onClick={(e) => e.preventDefault()}
|
||||
>
|
||||
{t("jobs.actions.changestimator")} <DownOutlined />
|
||||
</a>
|
||||
</Dropdown>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null)(JobsDetailChangeEstimator);
|
||||
@@ -1,4 +1,13 @@
|
||||
import { Col, Form, Input, InputNumber, Row, Select, Switch } from "antd";
|
||||
import {
|
||||
Col,
|
||||
Divider,
|
||||
Form,
|
||||
Input,
|
||||
InputNumber,
|
||||
Row,
|
||||
Select,
|
||||
Switch,
|
||||
} from "antd";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -12,6 +21,7 @@ import FormItemPhone, {
|
||||
PhoneItemFormatterValidation,
|
||||
} from "../form-items-formatted/phone-form-item.component";
|
||||
import Car from "../job-damage-visual/job-damage-visual.component";
|
||||
import JobsDetailChangeEstimator from "../jobs-detail-change-estimator/jobs-detail-change-estimator.component";
|
||||
import FormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
@@ -44,7 +54,14 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ded_amt")} name="ded_amt">
|
||||
<CurrencyInput disabled={jobRO} />
|
||||
<CurrencyInput disabled={jobRO} min={0} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.ded_note")} name="ded_note">
|
||||
<Select disabled={jobRO}>
|
||||
{bodyshop.md_ded_notes.map((n, index) => (
|
||||
<Select.Option key={index}>{n}</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.policy_no")} name="policy_no">
|
||||
<Input disabled={jobRO} />
|
||||
@@ -139,6 +156,9 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
<Form.Item label={t("jobs.fields.loss_date")} name="loss_date">
|
||||
<FormDatePicker disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.loss_of_use")} name="loss_of_use">
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
<Form.Item label={t("jobs.fields.kmin")} name="kmin">
|
||||
<InputNumber precision={0} min={0} disabled={jobRO} />
|
||||
</Form.Item>
|
||||
@@ -191,8 +211,16 @@ export function JobsDetailGeneral({ bodyshop, jobRO, job, form }) {
|
||||
)}
|
||||
</Col>
|
||||
</Row>
|
||||
<Divider
|
||||
orientation="left"
|
||||
type="horizontal"
|
||||
style={{ marginTop: ".8rem", float: "right" }}
|
||||
>
|
||||
{t("jobs.forms.appraiserinfo")}
|
||||
</Divider>
|
||||
|
||||
<FormRow header={t("jobs.forms.appraiserinfo")}>
|
||||
<JobsDetailChangeEstimator form={form} disabled={jobRO} />
|
||||
<FormRow noDivider>
|
||||
<Form.Item label={t("jobs.fields.est_co_nm")} name="est_co_nm">
|
||||
<Input disabled={jobRO} />
|
||||
</Form.Item>
|
||||
|
||||
@@ -101,6 +101,7 @@ export function JobsDetailHeaderActions({
|
||||
context: {
|
||||
jobId: job.id,
|
||||
job: job,
|
||||
alt_transport: job.alt_transport,
|
||||
},
|
||||
});
|
||||
}}
|
||||
|
||||
@@ -147,7 +147,7 @@ export function JobsDetailHeaderCsi({
|
||||
replyTo: bodyshop.email,
|
||||
},
|
||||
template: {
|
||||
name: TemplateList("job").csi_invitation.key,
|
||||
name: TemplateList("job_special").csi_invitation_action.key,
|
||||
variables: {
|
||||
id: job.csiinvites[0].id,
|
||||
},
|
||||
|
||||
@@ -21,47 +21,57 @@ export function JobsDetailHeaderActionexportCustomerData({
|
||||
|
||||
const handleExportCustData = async (e) => {
|
||||
logImEXEvent("job_export_cust_data");
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{ jobIds: [job.id], custDataOnly: true },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("handle -> XML", QbXmlResponse);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let PartnerResponse;
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
QbXmlResponse.data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting-partner"),
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
||||
jobIds: [job.id],
|
||||
custDataOnly: true,
|
||||
});
|
||||
} else {
|
||||
//Default is QBD
|
||||
|
||||
return;
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/receivables",
|
||||
{ jobIds: [job.id], custDataOnly: true },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("handle -> XML", QbXmlResponse);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
//let PartnerResponse;
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
QbXmlResponse.data,
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("jobs.errors.exporting-partner"),
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
//Check to see if any of them failed. If they didn't don't execute the update.
|
||||
const failedTransactions = PartnerResponse.data.filter((r) => !r.success);
|
||||
|
||||
@@ -16,6 +16,7 @@ import JobEmployeeAssignments from "../job-employee-assignments/job-employee-ass
|
||||
import ProductionListColumnProductionNote from "../production-list-columns/production-list-columns.productionnote.component";
|
||||
import "./jobs-detail-header.styles.scss";
|
||||
import JobsRelatedRos from "../jobs-related-ros/jobs-related-ros.component";
|
||||
import { DateTimeFormatter } from "../../utils/DateFormatter";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
jobRO: selectJobReadOnly,
|
||||
@@ -70,6 +71,12 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
|
||||
{job.production_vars && job.production_vars.alert ? (
|
||||
<ExclamationCircleFilled className="production-alert" />
|
||||
) : null}
|
||||
{job.status === bodyshop.md_ro_statuses.default_scheduled &&
|
||||
job.scheduled_in ? (
|
||||
<Tag>
|
||||
<DateTimeFormatter>{job.scheduled_in}</DateTimeFormatter>
|
||||
</Tag>
|
||||
) : null}
|
||||
</Space>
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.ins_co_nm_short")}>
|
||||
|
||||
@@ -124,7 +124,6 @@ export function JobsDetailRates({ jobRO, form, job, bodyshop }) {
|
||||
{t("jobs.forms.laborrates")}
|
||||
</Divider>
|
||||
<Space>
|
||||
<div></div>
|
||||
<JobsDetailRatesChangeButton form={form} disabled={jobRO} />
|
||||
<JobsMarkPstExempt form={form} />
|
||||
</Space>
|
||||
|
||||
@@ -38,7 +38,6 @@ export function JobsExportAllButton({
|
||||
setLoading(true);
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
||||
withCredentials: true,
|
||||
jobIds: jobIds,
|
||||
});
|
||||
} else {
|
||||
@@ -170,12 +169,7 @@ export function JobsExportAllButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleQbxml}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
type="dashed"
|
||||
>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
{t("jobs.actions.export")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -74,7 +74,6 @@ export default function OwnerFindModalComponent({
|
||||
return (
|
||||
<div>
|
||||
<Table
|
||||
title={() => t("owners.labels.existing_owners")}
|
||||
pagination={{ position: "bottom" }}
|
||||
columns={columns}
|
||||
rowKey="id"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Modal } from "antd";
|
||||
import React from "react";
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { useLazyQuery } from "@apollo/client";
|
||||
import { Input, Modal } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { QUERY_SEARCH_OWNER_BY_IDX } from "../../graphql/owners.queries";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
@@ -17,14 +17,27 @@ export default function OwnerFindModalContainer({
|
||||
}) {
|
||||
//use owner object to run query and find what possible owners there are.
|
||||
const { t } = useTranslation();
|
||||
const [searchText, setSearchText] = useState(null);
|
||||
|
||||
const ownersList = useQuery(QUERY_SEARCH_OWNER_BY_IDX, {
|
||||
variables: {
|
||||
search: owner ? `${owner.ownr_fn || ""} ${owner.ownr_ln || ""}` : null,
|
||||
},
|
||||
skip: !owner,
|
||||
fetchPolicy: "network-only",
|
||||
});
|
||||
const [callSearchowners, ownersList] = useLazyQuery(
|
||||
QUERY_SEARCH_OWNER_BY_IDX,
|
||||
{
|
||||
variables: {
|
||||
search: searchText,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (modalProps.visible && owner) {
|
||||
const s = `${owner.ownr_fn || ""} ${owner.ownr_ln || ""} ${
|
||||
owner.ownr_co_nm || ""
|
||||
}`;
|
||||
|
||||
setSearchText(s.trim());
|
||||
callSearchowners({ variables: { search: s.trim() } });
|
||||
}
|
||||
}, [callSearchowners, modalProps.visible, owner]);
|
||||
|
||||
return (
|
||||
<Modal
|
||||
@@ -35,16 +48,25 @@ export default function OwnerFindModalContainer({
|
||||
{loading ? <LoadingSpinner /> : null}
|
||||
{error ? <AlertComponent message={error.message} type="error" /> : null}
|
||||
{owner ? (
|
||||
<OwnerFindModalComponent
|
||||
selectedOwner={selectedOwner}
|
||||
setSelectedOwner={setSelectedOwner}
|
||||
ownersListLoading={ownersList.loading}
|
||||
ownersList={
|
||||
ownersList.data && ownersList.data.search_owners
|
||||
? ownersList.data.search_owners
|
||||
: null
|
||||
}
|
||||
/>
|
||||
<>
|
||||
<Input.Search
|
||||
value={searchText}
|
||||
onChange={(e) => setSearchText(e.target.value)}
|
||||
onSearch={(val) =>
|
||||
callSearchowners({ variables: { search: val.trim() } })
|
||||
}
|
||||
/>
|
||||
<OwnerFindModalComponent
|
||||
selectedOwner={selectedOwner}
|
||||
setSelectedOwner={setSelectedOwner}
|
||||
ownersListLoading={ownersList.loading}
|
||||
ownersList={
|
||||
ownersList.data && ownersList.data.search_owners
|
||||
? ownersList.data.search_owners
|
||||
: null
|
||||
}
|
||||
/>
|
||||
</>
|
||||
) : null}
|
||||
</Modal>
|
||||
);
|
||||
|
||||
@@ -40,8 +40,7 @@ export function PayableExportAll({
|
||||
setLoading(true);
|
||||
if (!!loadingCallback) loadingCallback(true);
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/receivables`, {
|
||||
withCredentials: true,
|
||||
PartnerResponse = await axios.post(`/qbo/payables`, {
|
||||
bills: billids,
|
||||
});
|
||||
} else {
|
||||
@@ -169,12 +168,7 @@ export function PayableExportAll({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleQbxml}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
type="dashed"
|
||||
>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -42,7 +42,6 @@ export function PayableExportButton({
|
||||
let PartnerResponse;
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/payables`, {
|
||||
withCredentials: true,
|
||||
bills: [billId],
|
||||
});
|
||||
} else {
|
||||
@@ -171,12 +170,7 @@ export function PayableExportButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleQbxml}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
type="dashed"
|
||||
>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
{t("jobs.actions.export")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { createStructuredSelector } from "reselect";
|
||||
import { auth, logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { UPDATE_PAYMENTS } from "../../graphql/payments.queries";
|
||||
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
@@ -33,55 +34,63 @@ export function PaymentExportButton({
|
||||
|
||||
const handleQbxml = async () => {
|
||||
logImEXEvent("accounting_payment_export");
|
||||
|
||||
setLoading(true);
|
||||
if (!!loadingCallback) loadingCallback(true);
|
||||
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/payments",
|
||||
{ payments: [paymentId] },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("handle -> XML", QbXmlResponse);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("payments.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
if (loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
//Check if it's a QBO Setup.
|
||||
let PartnerResponse;
|
||||
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
//"http://609feaeae986.ngrok.io/qb/",
|
||||
QbXmlResponse.data
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("payments.errors.exporting-partner"),
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/payments`, {
|
||||
payments: [paymentId],
|
||||
});
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
} else {
|
||||
//Default is QBD
|
||||
|
||||
if (!!loadingCallback) loadingCallback(true);
|
||||
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post(
|
||||
"/accounting/qbxml/payments",
|
||||
{ payments: [paymentId] },
|
||||
{
|
||||
headers: {
|
||||
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
|
||||
},
|
||||
}
|
||||
);
|
||||
console.log("handle -> XML", QbXmlResponse);
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("payments.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
if (loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
QbXmlResponse.data
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("payments.errors.exporting-partner"),
|
||||
});
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log("handleQbxml -> PartnerResponse", PartnerResponse);
|
||||
const failedTransactions = PartnerResponse.data.filter((r) => !r.success);
|
||||
|
||||
const successfulTransactions = PartnerResponse.data.filter(
|
||||
(r) => r.success
|
||||
);
|
||||
if (failedTransactions.length > 0) {
|
||||
//Uh oh. At least one was no good.
|
||||
failedTransactions.map((ft) =>
|
||||
@@ -123,7 +132,14 @@ export function PaymentExportButton({
|
||||
|
||||
const paymentUpdateResponse = await updatePayment({
|
||||
variables: {
|
||||
paymentIdList: [paymentId],
|
||||
paymentIdList: successfulTransactions.map(
|
||||
(st) =>
|
||||
st[
|
||||
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
|
||||
? "paymentid"
|
||||
: "id"
|
||||
]
|
||||
),
|
||||
payment: {
|
||||
exportedat: new Date(),
|
||||
},
|
||||
@@ -155,12 +171,7 @@ export function PaymentExportButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleQbxml}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
type="dashed"
|
||||
>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
{t("jobs.actions.export")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -8,6 +8,7 @@ import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
|
||||
import { UPDATE_PAYMENTS } from "../../graphql/payments.queries";
|
||||
|
||||
import {
|
||||
selectBodyshop,
|
||||
selectCurrentUser,
|
||||
@@ -33,42 +34,49 @@ export function PaymentsExportAllButton({
|
||||
const handleQbxml = async () => {
|
||||
setLoading(true);
|
||||
if (!!loadingCallback) loadingCallback(true);
|
||||
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post("/accounting/qbxml/payments", {
|
||||
let PartnerResponse;
|
||||
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
|
||||
PartnerResponse = await axios.post(`/qbo/payments`, {
|
||||
payments: paymentIds,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("payments.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
if (loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
} else {
|
||||
let QbXmlResponse;
|
||||
try {
|
||||
QbXmlResponse = await axios.post("/accounting/qbxml/payments", {
|
||||
payments: paymentIds,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("Error getting QBXML from Server.", error);
|
||||
notification["error"]({
|
||||
message: t("payments.errors.exporting", {
|
||||
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
|
||||
}),
|
||||
});
|
||||
if (loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
QbXmlResponse.data
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("payments.errors.exporting-partner"),
|
||||
});
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let PartnerResponse;
|
||||
|
||||
try {
|
||||
PartnerResponse = await axios.post(
|
||||
"http://localhost:1337/qb/",
|
||||
QbXmlResponse.data
|
||||
);
|
||||
} catch (error) {
|
||||
console.log("Error connecting to quickbooks or partner.", error);
|
||||
notification["error"]({
|
||||
message: t("payments.errors.exporting-partner"),
|
||||
});
|
||||
if (!!loadingCallback) loadingCallback(false);
|
||||
setLoading(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const groupedData = _.groupBy(PartnerResponse.data, "id");
|
||||
const groupedData = _.groupBy(
|
||||
PartnerResponse.data,
|
||||
bodyshop.accountingconfig.qbo ? "paymentid" : "id"
|
||||
);
|
||||
const proms = [];
|
||||
Object.keys(groupedData).forEach((key) => {
|
||||
proms.push(
|
||||
@@ -144,12 +152,7 @@ export function PaymentsExportAllButton({
|
||||
};
|
||||
|
||||
return (
|
||||
<Button
|
||||
onClick={handleQbxml}
|
||||
loading={loading}
|
||||
disabled={disabled}
|
||||
type="dashed"
|
||||
>
|
||||
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
|
||||
{t("jobs.actions.exportselected")}
|
||||
</Button>
|
||||
);
|
||||
|
||||
@@ -6,9 +6,11 @@ import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectTechnician } from "../../redux/tech/tech.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
technician: selectTechnician,
|
||||
bodyshop: selectBodyshop,
|
||||
});
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
//setUserLanguage: language => dispatch(setUserLanguage(language))
|
||||
@@ -21,6 +23,7 @@ export default connect(
|
||||
export function ProductionColumnsComponent({
|
||||
columnState,
|
||||
technician,
|
||||
bodyshop,
|
||||
tableState,
|
||||
}) {
|
||||
const [columns, setColumns] = columnState;
|
||||
@@ -29,9 +32,11 @@ export function ProductionColumnsComponent({
|
||||
const handleAdd = (e) => {
|
||||
setColumns([
|
||||
...columns,
|
||||
...dataSource({ technician, state: tableState }).filter(
|
||||
(i) => i.key === e.key
|
||||
),
|
||||
...dataSource({
|
||||
technician,
|
||||
state: tableState,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
}).filter((i) => i.key === e.key),
|
||||
]);
|
||||
};
|
||||
|
||||
@@ -39,7 +44,11 @@ export function ProductionColumnsComponent({
|
||||
|
||||
const menu = (
|
||||
<Menu onClick={handleAdd}>
|
||||
{dataSource({ technician, state: tableState })
|
||||
{dataSource({
|
||||
technician,
|
||||
state: tableState,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
})
|
||||
.filter((i) => !columnKeys.includes(i.key))
|
||||
.map((item) => (
|
||||
<Menu.Item key={item.key}>{item.title}</Menu.Item>
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function ProductionListColumnBodyPriority({ record }) {
|
||||
key="set"
|
||||
title={t("production.actions.bodypriority-set")}
|
||||
>
|
||||
{new Array(9).fill().map((value, index) => (
|
||||
{new Array(15).fill().map((value, index) => (
|
||||
<Menu.Item key={index + 1}>{index + 1}</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
|
||||
@@ -3,7 +3,7 @@ import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
import CurrencyFormatter from "../../utils/CurrencyFormatter";
|
||||
import PhoneFormatter from "../../utils/PhoneFormatter";
|
||||
import { alphaSort, dateSort } from "../../utils/sorters";
|
||||
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
|
||||
import JobAltTransportChange from "../job-at-change/job-at-change.component";
|
||||
import ProductionSubletsManageComponent from "../production-sublets-manage/production-sublets-manage.component";
|
||||
import ProductionListColumnAlert from "./production-list-columns.alert.component";
|
||||
@@ -17,7 +17,7 @@ import ProductionListColumnStatus from "./production-list-columns.status.compone
|
||||
import ProductionlistColumnTouchTime from "./prodution-list-columns.touchtime.component";
|
||||
import ProductionListLastContacted from "./production-list-columns.lastcontacted.component";
|
||||
|
||||
const r = ({ technician, state }) => {
|
||||
const r = ({ technician, state, activeStatuses }) => {
|
||||
return [
|
||||
{
|
||||
title: i18n.t("jobs.actions.viewdetail"),
|
||||
@@ -210,7 +210,7 @@ const r = ({ technician, state }) => {
|
||||
dataIndex: "status",
|
||||
key: "status",
|
||||
ellipsis: true,
|
||||
sorter: (a, b) => alphaSort(a.status, b.status),
|
||||
sorter: (a, b) => statusSort(a.status, b.status, activeStatuses),
|
||||
sortOrder:
|
||||
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
|
||||
render: (text, record) => <ProductionListColumnStatus record={record} />,
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function ProductionListColumnDetailPriority({ record }) {
|
||||
key="set"
|
||||
title={t("production.actions.detailpriority-set")}
|
||||
>
|
||||
{new Array(9).fill().map((value, index) => (
|
||||
{new Array(15).fill().map((value, index) => (
|
||||
<Menu.Item key={index + 1}>{index + 1}</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
|
||||
@@ -40,7 +40,7 @@ export default function ProductionListColumnPaintPriority({ record }) {
|
||||
key="set"
|
||||
title={t("production.actions.paintpriority-set")}
|
||||
>
|
||||
{new Array(9).fill().map((value, index) => (
|
||||
{new Array(15).fill().map((value, index) => (
|
||||
<Menu.Item key={index + 1}>{index + 1}</Menu.Item>
|
||||
))}
|
||||
</Menu.SubMenu>
|
||||
|
||||
@@ -37,9 +37,11 @@ export function ProductionListTable({
|
||||
.filter((pc) => pc.name === value)[0]
|
||||
.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({ technician, state }).find(
|
||||
(e) => e.key === k.key
|
||||
),
|
||||
...ProductionListColumns({
|
||||
technician,
|
||||
state,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width,
|
||||
};
|
||||
})
|
||||
@@ -88,9 +90,11 @@ export function ProductionListTable({
|
||||
setColumns(
|
||||
bodyshop.production_config[0].columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({ technician, state }).find(
|
||||
(e) => e.key === k.key
|
||||
),
|
||||
...ProductionListColumns({
|
||||
technician,
|
||||
state,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width,
|
||||
};
|
||||
})
|
||||
|
||||
@@ -1,4 +1,12 @@
|
||||
import { Dropdown, Input, Menu, PageHeader, Space, Table } from "antd";
|
||||
import {
|
||||
Dropdown,
|
||||
Input,
|
||||
Menu,
|
||||
PageHeader,
|
||||
Space,
|
||||
Statistic,
|
||||
Table,
|
||||
} from "antd";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import ReactDragListView from "react-drag-listview";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -16,6 +24,7 @@ import ProductionListSaveConfigButton from "../production-list-save-config-butto
|
||||
import ProductionListPrint from "./production-list-print.component";
|
||||
import ProductionListTableViewSelect from "./production-list-table-view-select.component";
|
||||
import ResizeableTitle from "./production-list-table.resizeable.component";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
@@ -31,7 +40,9 @@ export function ProductionListTable({
|
||||
currentUser,
|
||||
}) {
|
||||
const [searchText, setSearchText] = useState("");
|
||||
|
||||
const { Production_List_Status_Colors } = useTreatments([
|
||||
"Production_List_Status_Colors",
|
||||
]);
|
||||
const assoc = bodyshop.associations.find(
|
||||
(a) => a.useremail === currentUser.email
|
||||
);
|
||||
@@ -59,9 +70,11 @@ export function ProductionListTable({
|
||||
matchingColumnConfig &&
|
||||
matchingColumnConfig.columns.columnKeys.map((k) => {
|
||||
return {
|
||||
...ProductionListColumns({ technician, state }).find(
|
||||
(e) => e.key === k.key
|
||||
),
|
||||
...ProductionListColumns({
|
||||
technician,
|
||||
state,
|
||||
activeStatuses: bodyshop.md_ro_statuses.active_statuses,
|
||||
}).find((e) => e.key === k.key),
|
||||
width: k.width,
|
||||
};
|
||||
})) ||
|
||||
@@ -156,9 +169,24 @@ export function ProductionListTable({
|
||||
|
||||
if (!!!columns) return <div>No columns found.</div>;
|
||||
|
||||
const totalHrs = data
|
||||
.reduce(
|
||||
(acc, val) =>
|
||||
acc +
|
||||
(val.labhrs?.aggregate?.sum?.mod_lb_hrs || 0) +
|
||||
(val.larhrs?.aggregate?.sum?.mod_lb_hrs || 0),
|
||||
0
|
||||
)
|
||||
.toFixed(1);
|
||||
return (
|
||||
<div>
|
||||
<PageHeader
|
||||
title={
|
||||
<Statistic
|
||||
title={t("dashboard.titles.productionhours")}
|
||||
value={totalHrs}
|
||||
/>
|
||||
}
|
||||
extra={
|
||||
<Space wrap>
|
||||
<ProductionListColumnsAdd
|
||||
@@ -192,8 +220,28 @@ export function ProductionListTable({
|
||||
handleSelector=".prod-header-dropdown"
|
||||
>
|
||||
<Table
|
||||
sticky
|
||||
pagination={false}
|
||||
size="small"
|
||||
className="production-list-table"
|
||||
onRow={
|
||||
Production_List_Status_Colors.treatment === "on" &&
|
||||
((record, index) => {
|
||||
if (!bodyshop.md_ro_statuses.production_colors) return null;
|
||||
|
||||
const color = bodyshop.md_ro_statuses.production_colors.find(
|
||||
(x) => x.status === record.status
|
||||
);
|
||||
|
||||
if (!color) return null;
|
||||
|
||||
return {
|
||||
style: {
|
||||
backgroundColor: `rgb(${color.color.r},${color.color.g},${color.color.b},${color.color.a})`,
|
||||
},
|
||||
};
|
||||
})
|
||||
}
|
||||
components={{
|
||||
header: {
|
||||
cell: ResizeableTitle,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Space, Tag } from "antd";
|
||||
import { Space } from "antd";
|
||||
import Axios from "axios";
|
||||
import queryString from "query-string";
|
||||
import React, { useEffect } from "react";
|
||||
@@ -9,7 +9,7 @@ import QboSignIn from "../../assets/qbo/C2QB_green_btn_med_default.svg";
|
||||
export default function QboAuthorizeComponent() {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const [cookies, setCookie] = useCookies(["access_token", "refresh_token"]);
|
||||
const [, setCookie] = useCookies(["access_token", "refresh_token"]);
|
||||
|
||||
const handleQbSignIn = async () => {
|
||||
const result = await Axios.post("/qbo/authorize");
|
||||
@@ -24,16 +24,20 @@ export default function QboAuthorizeComponent() {
|
||||
const hasBeenCalledBack = code && realmId && state;
|
||||
|
||||
if (hasBeenCalledBack) {
|
||||
setCookie("qbo_code", code, { path: "/" });
|
||||
setCookie("qbo_state", state, { path: "/" });
|
||||
// setCookie("qbo_code", code, { path: "/" });
|
||||
// setCookie("qbo_state", state, { path: "/" });
|
||||
|
||||
let expires = new Date();
|
||||
expires.setTime(expires.getTime() + 8726400 * 1000);
|
||||
// let expires = new Date();
|
||||
// expires.setTime(expires.getTime() + 8726400 * 1000);
|
||||
|
||||
setCookie("qbo_realmId", realmId, {
|
||||
path: "/",
|
||||
expires,
|
||||
});
|
||||
// setCookie("qbo_realmId", realmId, {
|
||||
// path: "/",
|
||||
// expires,
|
||||
|
||||
// ...(process.env.NODE_ENV !== "development"
|
||||
// ? { domain: `.${window.location.host}` }
|
||||
// : {}),
|
||||
// });
|
||||
|
||||
history.push({ pathname: `/manage/accounting/receivables` });
|
||||
}
|
||||
@@ -48,9 +52,7 @@ export default function QboAuthorizeComponent() {
|
||||
src={QboSignIn}
|
||||
style={{ cursor: "pointer" }}
|
||||
/>
|
||||
{!cookies.qbo_realmId && (
|
||||
<Tag color="red">No QuickBooks company has been connected.</Tag>
|
||||
)}
|
||||
|
||||
{error && JSON.parse(decodeURIComponent(error)).error_description}
|
||||
</Space>
|
||||
);
|
||||
|
||||
@@ -3,6 +3,7 @@ import { Button, Card, Col, PageHeader, Row, Space } from "antd";
|
||||
import React from "react";
|
||||
import ScheduleCalendarWrapperComponent from "../schedule-calendar-wrapper/scheduler-calendar-wrapper.component";
|
||||
import ScheduleModal from "../schedule-job-modal/schedule-job-modal.container";
|
||||
//import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
|
||||
import ScheduleProductionList from "../schedule-production-list/schedule-production-list.component";
|
||||
|
||||
export default function ScheduleCalendarComponent({ data, refetch }) {
|
||||
@@ -21,8 +22,10 @@ export default function ScheduleCalendarComponent({ data, refetch }) {
|
||||
>
|
||||
<SyncOutlined />
|
||||
</Button>
|
||||
|
||||
<ScheduleProductionList />
|
||||
{
|
||||
// <ScheduleManualEvent />
|
||||
}
|
||||
</Space>
|
||||
}
|
||||
/>
|
||||
|
||||
@@ -63,6 +63,20 @@ export function ScheduleJobModalContainer({
|
||||
skip: !visible || !!!jobId,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (
|
||||
existingAppointments.data &&
|
||||
existingAppointments.data.appointments.length > 0 &&
|
||||
!existingAppointments.data.appointments[0].canceled
|
||||
) {
|
||||
form.setFieldsValue({
|
||||
color: existingAppointments.data.appointments[0].color,
|
||||
|
||||
note: existingAppointments.data.appointments[0].note,
|
||||
});
|
||||
}
|
||||
}, [existingAppointments.data, form]);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
logImEXEvent("schedule_new_appointment");
|
||||
|
||||
@@ -105,7 +119,7 @@ export function ScheduleJobModalContainer({
|
||||
start: moment(values.start),
|
||||
end: moment(values.start).add(bodyshop.appt_length || 60, "minutes"),
|
||||
color: values.color,
|
||||
note:values.note
|
||||
note: values.note,
|
||||
},
|
||||
jobId: jobId,
|
||||
altTransport: values.alt_transport,
|
||||
@@ -188,6 +202,9 @@ export function ScheduleJobModalContainer({
|
||||
start: null,
|
||||
// smartDates: [],
|
||||
scheduled_completion: null,
|
||||
color: context.color,
|
||||
alt_transport: context.alt_transport,
|
||||
note: context.note,
|
||||
}}
|
||||
>
|
||||
<ScheduleJobModalComponent
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Form, Input, Popover, Space } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { logImEXEvent } from "../../firebase/firebase.utils";
|
||||
import {
|
||||
INSERT_APPOINTMENT,
|
||||
UPDATE_APPOINTMENT,
|
||||
} from "../../graphql/appointments.queries";
|
||||
import FormDateTimePickerComponent from "../form-date-time-picker/form-date-time-picker.component";
|
||||
|
||||
export default function ScheduleManualEvent({ event }) {
|
||||
const { t } = useTranslation();
|
||||
const [insertAppointment] = useMutation(INSERT_APPOINTMENT);
|
||||
const [updateAppointment] = useMutation(UPDATE_APPOINTMENT);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [form] = Form.useForm();
|
||||
const [visibility, setVisibility] = useState(false);
|
||||
// const [callQuery, { loading: entryLoading, data: entryData }] = useLazyQuery(
|
||||
// QUERY_SCOREBOARD_ENTRY
|
||||
// );
|
||||
|
||||
useEffect(() => {
|
||||
if (visibility && event) {
|
||||
form.setFieldsValue({ event });
|
||||
}
|
||||
}, [visibility, form, event]);
|
||||
|
||||
useEffect(() => {
|
||||
// if (entryData && entryData.scoreboard && entryData.scoreboard[0]) {
|
||||
// console.log("Setting FOrm");
|
||||
// // form.setFieldsValue(entryData.scoreboard[0]);
|
||||
// }
|
||||
}, [form]);
|
||||
|
||||
const handleFinish = async (values) => {
|
||||
logImEXEvent("job_close_add_to_scoreboard");
|
||||
|
||||
setLoading(true);
|
||||
try {
|
||||
if (event && event.id) {
|
||||
updateAppointment();
|
||||
} else {
|
||||
insertAppointment();
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
setVisibility(false);
|
||||
}
|
||||
};
|
||||
|
||||
const overlay = (
|
||||
<Card>
|
||||
<div>
|
||||
<Form form={form} layout="vertical" onFinish={handleFinish}>
|
||||
<Form.Item
|
||||
label={t("schedule.fields.note")}
|
||||
name="note"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("schedule.fields.start")}
|
||||
name="start"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDateTimePickerComponent />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("schedule.fields.end")}
|
||||
name="end"
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormDateTimePickerComponent />
|
||||
</Form.Item>
|
||||
|
||||
<Space wrap>
|
||||
<Button type="primary" htmlType="submit">
|
||||
{t("general.actions.save")}
|
||||
</Button>
|
||||
<Button onClick={() => setVisibility(false)}>
|
||||
{t("general.actions.cancel")}
|
||||
</Button>
|
||||
</Space>
|
||||
</Form>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
|
||||
const handleClick = (e) => {
|
||||
setVisibility(true);
|
||||
};
|
||||
|
||||
return (
|
||||
<Popover content={overlay} visible={visibility}>
|
||||
<Button loading={loading} onClick={handleClick}>
|
||||
{t("schedule.labels.manualevent")}
|
||||
</Button>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
@@ -17,6 +17,8 @@ import PhoneFormItem, {
|
||||
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
import CurrencyInput from "../form-items-formatted/currency-form-item.component";
|
||||
import FormItemEmail from "../form-items-formatted/email-form-item.component";
|
||||
|
||||
export default function ShopInfoGeneral({ form }) {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
@@ -472,6 +474,19 @@ export default function ShopInfoGeneral({ form }) {
|
||||
>
|
||||
<Switch />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["md_ded_notes"]}
|
||||
label={t("bodyshop.fields.md_ded_notes")}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
type: "array",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select mode="tags" />
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.messagingpresets")}>
|
||||
<Form.List name={["md_messaging_presets"]}>
|
||||
@@ -706,7 +721,7 @@ export default function ShopInfoGeneral({ form }) {
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Space wrap>
|
||||
<Space>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.md_ins_co.zip")}
|
||||
key={`${index}zip`}
|
||||
@@ -744,6 +759,95 @@ export default function ShopInfoGeneral({ form }) {
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.labels.estimators")}>
|
||||
<Form.List name={["md_estimators"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
<div>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<LayoutFormRow noDivider>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.est_co_nm")}
|
||||
key={`${index}est_co_nm`}
|
||||
name={[field.name, "est_co_nm"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.est_ct_fn")}
|
||||
key={`${index}est_ct_fn`}
|
||||
name={[field.name, "est_ct_fn"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.est_ct_ln")}
|
||||
key={`${index}est_ct_ln`}
|
||||
name={[field.name, "est_ct_ln"]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.est_ph1")}
|
||||
key={`${index}est_ph1`}
|
||||
name={[field.name, "est_ph1"]}
|
||||
rules={[
|
||||
({ getFieldValue }) =>
|
||||
PhoneItemFormatterValidation(getFieldValue, [
|
||||
field.name,
|
||||
"est_ph",
|
||||
]),
|
||||
]}
|
||||
>
|
||||
<Input />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("jobs.fields.est_ea")}
|
||||
key={`${index}est_ea`}
|
||||
name={[field.name, "est_ea"]}
|
||||
rules={[
|
||||
{
|
||||
type: "email",
|
||||
message: "This is not a valid email address.",
|
||||
},
|
||||
]}
|
||||
>
|
||||
<FormItemEmail
|
||||
email={form.getFieldValue([field.name, "est_ea"])}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Space>
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
<FormListMoveArrows
|
||||
move={move}
|
||||
index={index}
|
||||
total={fields.length}
|
||||
/>
|
||||
</Space>
|
||||
</LayoutFormRow>
|
||||
</Form.Item>
|
||||
))}
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{t("general.actions.add")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
<LayoutFormRow grow header={t("bodyshop.fields.md_ccc_rates")}>
|
||||
<Form.List name={["md_ccc_rates"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
|
||||
@@ -203,6 +203,14 @@ export function ShopInfoResponsibilityCenterComponent({ bodyshop, form }) {
|
||||
</LayoutFormRow>
|
||||
</>
|
||||
)}
|
||||
|
||||
{bodyshop.pbs_serialnumber && (
|
||||
<>
|
||||
<DataLabel label={t("bodyshop.labels.dms.pbs_serialnumber")}>
|
||||
{form.getFieldValue("pbs_serialnumber")}
|
||||
</DataLabel>
|
||||
</>
|
||||
)}
|
||||
<LayoutFormRow header={t("bodyshop.labels.responsibilitycenters.costs")}>
|
||||
<Form.List name={["md_responsibility_centers", "costs"]}>
|
||||
{(fields, { add, remove }) => {
|
||||
|
||||
@@ -1,8 +1,12 @@
|
||||
import { Form, Select } from "antd";
|
||||
import { DeleteFilled } from "@ant-design/icons";
|
||||
import { useTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Form, Select, Space } from "antd";
|
||||
import React, { useState } from "react";
|
||||
import { ChromePicker } from "react-color";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import styled from "styled-components";
|
||||
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
|
||||
|
||||
const SelectorDiv = styled.div`
|
||||
.ant-form-item .ant-select {
|
||||
width: 200px;
|
||||
@@ -11,6 +15,9 @@ const SelectorDiv = styled.div`
|
||||
|
||||
export default function ShopInfoROStatusComponent({ form }) {
|
||||
const { t } = useTranslation();
|
||||
const { Production_List_Status_Colors } = useTreatments([
|
||||
"Production_List_Status_Colors",
|
||||
]);
|
||||
|
||||
const [options, setOptions] = useState(
|
||||
form.getFieldValue(["md_ro_statuses", "statuses"]) || []
|
||||
@@ -257,6 +264,97 @@ export default function ShopInfoROStatusComponent({ form }) {
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</LayoutFormRow>
|
||||
{Production_List_Status_Colors.treatment === "on" && (
|
||||
<LayoutFormRow
|
||||
grow
|
||||
header={t("bodyshop.fields.statuses.production_colors")}
|
||||
>
|
||||
<Form.List name={["md_ro_statuses", "production_colors"]}>
|
||||
{(fields, { add, remove, move }) => {
|
||||
return (
|
||||
<div>
|
||||
<LayoutFormRow>
|
||||
{fields.map((field, index) => (
|
||||
<Form.Item key={field.key}>
|
||||
<Space direction="vertical">
|
||||
<div style={{ display: "flex" }}>
|
||||
<Form.Item
|
||||
style={{ flex: 1 }}
|
||||
label={t("jobs.fields.status")}
|
||||
key={`${index}status`}
|
||||
name={[field.name, "status"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Select>
|
||||
{options.map((item, idx) => (
|
||||
<Select.Option key={idx} value={item}>
|
||||
{item}
|
||||
</Select.Option>
|
||||
))}
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<DeleteFilled
|
||||
onClick={() => {
|
||||
remove(field.name);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.statuses.color")}
|
||||
key={`${index}color`}
|
||||
name={[field.name, "color"]}
|
||||
rules={[
|
||||
{
|
||||
required: true,
|
||||
//message: t("general.validation.required"),
|
||||
},
|
||||
]}
|
||||
>
|
||||
<ColorPicker />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Form.Item>
|
||||
))}
|
||||
</LayoutFormRow>
|
||||
<Form.Item>
|
||||
<Button
|
||||
type="dashed"
|
||||
onClick={() => {
|
||||
add();
|
||||
}}
|
||||
style={{ width: "100%" }}
|
||||
>
|
||||
{t("general.actions.add")}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Form.List>
|
||||
</LayoutFormRow>
|
||||
)}
|
||||
</SelectorDiv>
|
||||
);
|
||||
}
|
||||
|
||||
const ColorPicker = ({ value, onChange, style, ...restProps }) => {
|
||||
const handleChange = (color) => {
|
||||
console.log(
|
||||
"🚀 ~ file: shop-info.rostatus.component.jsx ~ line 345 ~ color",
|
||||
color
|
||||
);
|
||||
if (onChange) onChange(color.rgb);
|
||||
};
|
||||
return (
|
||||
<ChromePicker
|
||||
{...restProps}
|
||||
color={value}
|
||||
onChangeComplete={handleChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -44,7 +44,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TimePicker showSecond={false} format="hh:mm" />
|
||||
<TimePicker showSecond={false} format="HH:mm" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
label={t("bodyshop.fields.schedule_end_time")}
|
||||
@@ -56,7 +56,7 @@ export default function ShopInfoSchedulingComponent({ form }) {
|
||||
},
|
||||
]}
|
||||
>
|
||||
<TimePicker showSecond={false} format="hh:mm" />
|
||||
<TimePicker showSecond={false} format="HH:mm" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name={["appt_alt_transport"]}
|
||||
|
||||
@@ -196,10 +196,11 @@ export const CANCEL_APPOINTMENT_BY_ID = gql`
|
||||
|
||||
export const QUERY_APPOINTMENTS_BY_JOBID = gql`
|
||||
query QUERY_APPOINTMENTS_BY_JOBID($jobid: uuid!) {
|
||||
appointments(where: { jobid: { _eq: $jobid } }) {
|
||||
appointments(where: { jobid: { _eq: $jobid } }, order_by: { start: desc }) {
|
||||
start
|
||||
id
|
||||
end
|
||||
color
|
||||
isintake
|
||||
arrived
|
||||
canceled
|
||||
|
||||
@@ -94,6 +94,10 @@ export const QUERY_BODYSHOP = gql`
|
||||
attach_pdf_to_email
|
||||
tt_allow_post_to_invoiced
|
||||
cdk_configuration
|
||||
md_estimators
|
||||
md_ded_notes
|
||||
pbs_configuration
|
||||
pbs_serialnumber
|
||||
employees {
|
||||
id
|
||||
active
|
||||
@@ -184,6 +188,10 @@ export const UPDATE_SHOP = gql`
|
||||
attach_pdf_to_email
|
||||
tt_allow_post_to_invoiced
|
||||
cdk_configuration
|
||||
md_estimators
|
||||
md_ded_notes
|
||||
pbs_configuration
|
||||
pbs_serialnumber
|
||||
employees {
|
||||
id
|
||||
first_name
|
||||
|
||||
@@ -385,6 +385,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
vehicleid
|
||||
driveable
|
||||
towin
|
||||
loss_of_use
|
||||
vehicle {
|
||||
id
|
||||
plate_no
|
||||
@@ -463,6 +464,7 @@ export const GET_JOB_BY_PK = gql`
|
||||
production_vars
|
||||
ca_gst_registrant
|
||||
ownerid
|
||||
ded_note
|
||||
owner {
|
||||
id
|
||||
ownr_fn
|
||||
@@ -735,6 +737,7 @@ export const QUERY_JOB_CARD_DETAILS = gql`
|
||||
actual_completion
|
||||
actual_delivery
|
||||
actual_in
|
||||
scheduled_in
|
||||
po_number
|
||||
id
|
||||
ins_co_nm
|
||||
@@ -1769,7 +1772,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
|
||||
actual_in
|
||||
kmin
|
||||
kmout
|
||||
joblines(where: { removed: { _eq: false } }) {
|
||||
joblines(where: { removed: { _eq: false } }, order_by: { line_no: asc }) {
|
||||
id
|
||||
removed
|
||||
tax_part
|
||||
|
||||
@@ -2,7 +2,7 @@ import { gql } from "@apollo/client";
|
||||
|
||||
export const QUERY_SEARCH_OWNER_BY_IDX = gql`
|
||||
query QUERY_SEARCH_OWNER_BY_IDX($search: String!) {
|
||||
search_owners(args: { search: $search }) {
|
||||
search_owners(args: { search: $search }, limit: 100) {
|
||||
ownr_fn
|
||||
ownr_ln
|
||||
ownr_ph1
|
||||
|
||||
@@ -23,6 +23,11 @@ Dinero.globalRoundingMode = "HALF_EVEN";
|
||||
if (process.env.NODE_ENV !== "development") {
|
||||
Sentry.init({
|
||||
dsn: "https://fd7e89369b6b4bdc9c6c4c9f22fa4ee4@o492140.ingest.sentry.io/5651027",
|
||||
ignoreErrors: [
|
||||
"ResizeObserver loop",
|
||||
"Module specifier, 'fs' does not start",
|
||||
"Module specifier, 'zlib' does not start with",
|
||||
],
|
||||
integrations: [
|
||||
// new Integrations.BrowserTracing(),
|
||||
// new Sentry.Integrations.Breadcrumbs({ console: true }),
|
||||
|
||||
@@ -196,7 +196,11 @@ export function JobsDetailPage({
|
||||
<PrinterFilled />
|
||||
{t("jobs.actions.printCenter")}
|
||||
</Button>
|
||||
<JobsConvertButton job={job} refetch={refetch} />
|
||||
<JobsConvertButton
|
||||
job={job}
|
||||
refetch={refetch}
|
||||
parentFormIsFieldsTouched={form.isFieldsTouched}
|
||||
/>
|
||||
<JobsDetailHeaderActions key="actions" job={job} refetch={refetch} />
|
||||
<Button
|
||||
type="primary"
|
||||
|
||||
@@ -5,12 +5,12 @@ import preval from "preval.macro";
|
||||
import React, { lazy, Suspense, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
|
||||
import { Link, Route, Switch } from "react-router-dom";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import BreadCrumbs from "../../components/breadcrumbs/breadcrumbs.component";
|
||||
import ChatAffixContainer from "../../components/chat-affix/chat-affix.container";
|
||||
import ConflictComponent from "../../components/conflict/conflict.component";
|
||||
import ErrorBoundary from "../../components/error-boundary/error-boundary.component";
|
||||
import FcmNotification from "../../components/fcm-notification/fcm-notification.component";
|
||||
//import FooterComponent from "../../components/footer/footer.component";
|
||||
//Component Imports
|
||||
|
||||
@@ -6,8 +6,8 @@ import AlertComponent from "../../components/alert/alert.component";
|
||||
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
|
||||
import { QUERY_BODYSHOP } from "../../graphql/bodyshop.queries";
|
||||
import { setBodyshop } from "../../redux/user/user.actions";
|
||||
import ManagePage from "./manage.page.component";
|
||||
import "../../utils/RegisterSw";
|
||||
import ManagePage from "./manage.page.component";
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setBodyshop: (bs) => dispatch(setBodyshop(bs)),
|
||||
@@ -21,7 +21,9 @@ function ManagePageContainer({ match, setBodyshop }) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (data) setBodyshop(data.bodyshops[0] || { notfound: true });
|
||||
if (data) {
|
||||
setBodyshop(data.bodyshops[0] || { notfound: true });
|
||||
}
|
||||
}, [data, setBodyshop]);
|
||||
|
||||
if (loading)
|
||||
|
||||
@@ -13,7 +13,7 @@ import { doc } from "firebase/firestore";
|
||||
import i18next from "i18next";
|
||||
import LogRocket from "logrocket";
|
||||
import { all, call, delay, put, select, takeLatest } from "redux-saga/effects";
|
||||
import { tracker } from "../../App/App.container";
|
||||
import { factory, tracker } from "../../App/App.container";
|
||||
import {
|
||||
getCurrentUser,
|
||||
logImEXEvent,
|
||||
@@ -250,6 +250,8 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
try {
|
||||
const userEmail = yield select((state) => state.user.currentUser.email);
|
||||
|
||||
factory.client(payload.imexshopid);
|
||||
|
||||
const authRecord = payload.associations.filter(
|
||||
(a) => a.useremail === userEmail
|
||||
);
|
||||
|
||||
@@ -270,6 +270,7 @@
|
||||
"md_categories": "Categories",
|
||||
"md_ccc_rates": "Courtesy Car Contract Rate Presets",
|
||||
"md_classes": "Classes",
|
||||
"md_ded_notes": "Deductible Notes",
|
||||
"md_hour_split": {
|
||||
"paint": "Paint Hour Split",
|
||||
"prep": "Prep Hour Split"
|
||||
@@ -450,6 +451,7 @@
|
||||
"status": "Status Label",
|
||||
"statuses": {
|
||||
"active_statuses": "Active Statuses (Filtering for Active Jobs throughout system)",
|
||||
"color": "Color",
|
||||
"default_arrived": "Default Arrived Status (Transition to Production)",
|
||||
"default_bo": "Default Backordered Status",
|
||||
"default_canceled": "Default Canceled Status",
|
||||
@@ -466,6 +468,7 @@
|
||||
"open_statuses": "Open Statuses",
|
||||
"post_production_statuses": "Post-Production Statuses",
|
||||
"pre_production_statuses": "Pre-Production Statuses",
|
||||
"production_colors": "Production Status Colors",
|
||||
"production_statuses": "Production Statuses"
|
||||
},
|
||||
"target_touchtime": "Target Touch Time",
|
||||
@@ -499,6 +502,7 @@
|
||||
},
|
||||
"emaillater": "Email Later",
|
||||
"employees": "Employees",
|
||||
"estimators": "Estimators",
|
||||
"insurancecos": "Insurance Companies",
|
||||
"intakechecklist": "Intake Checklist",
|
||||
"jobstatuses": "Job Statuses",
|
||||
@@ -1053,7 +1057,8 @@
|
||||
},
|
||||
"profitcenter_labor": "Profit Center: Labor",
|
||||
"profitcenter_part": "Profit Center: Part",
|
||||
"prt_dsmk_p": "Line Markup %",
|
||||
"prt_dsmk_m": "Line Discount/Markup $",
|
||||
"prt_dsmk_p": "Line Discount/Markup %",
|
||||
"status": "Status",
|
||||
"tax_part": "Tax Part",
|
||||
"total": "Total",
|
||||
@@ -1088,6 +1093,7 @@
|
||||
"autoallocate": "Auto Allocate",
|
||||
"changelaborrate": "Change Labor Rate",
|
||||
"changestatus": "Change Status",
|
||||
"changestimator": "Change Estimator",
|
||||
"convert": "Convert",
|
||||
"deliver": "Deliver",
|
||||
"dms": {
|
||||
@@ -1202,6 +1208,7 @@
|
||||
"date_open": "Open",
|
||||
"date_scheduled": "Scheduled",
|
||||
"ded_amt": "Deductible",
|
||||
"ded_note": "Deductible Note",
|
||||
"ded_status": "Deductible Status",
|
||||
"depreciation_taxes": "Depreciation/Taxes",
|
||||
"dms": {
|
||||
@@ -1277,6 +1284,7 @@
|
||||
"local_tax_rate": "Local Tax Rate",
|
||||
"loss_date": "Loss Date",
|
||||
"loss_desc": "Loss Description",
|
||||
"loss_of_use": "Loss of Use",
|
||||
"ma2s": "2 Stage Paint",
|
||||
"ma3s": "3 Stage Pain",
|
||||
"mabl": "MABL?",
|
||||
@@ -1526,6 +1534,7 @@
|
||||
"sale_labor": "Sales - Labor",
|
||||
"sale_parts": "Sales - Parts & Sublet",
|
||||
"sales": "Sales",
|
||||
"savebeforeconversion": "You have unsaved changes on the job. Please save them before converting it. ",
|
||||
"scheduledinchange": "The scheduled in is based off the latest appointment. To change this date, please schedule or reschedule the job. ",
|
||||
"specialcoveragepolicy": "Special Coverage Policy Applies",
|
||||
"state_tax_amt": "Provincial/State Taxes",
|
||||
@@ -1982,6 +1991,9 @@
|
||||
"job_costing_ro": "Job Costing",
|
||||
"job_notes": "Job Notes",
|
||||
"key_tag": "Key Tag",
|
||||
"mpi_animal_checklist": "MPI - Animal Checklist",
|
||||
"mpi_eglass_auth": "MPI - eGlass Auth",
|
||||
"mpi_final_acct_sheet": "MPI - Final Accounting Sheet",
|
||||
"paint_grid": "Paint Grid",
|
||||
"parts_label_single": "Parts Label - Single",
|
||||
"parts_list": "Parts List",
|
||||
@@ -2195,6 +2207,11 @@
|
||||
"work_in_progress_payables": "Work in Progress - Payables"
|
||||
}
|
||||
},
|
||||
"schedule": {
|
||||
"labels": {
|
||||
"manualevent": "Add Manual Event"
|
||||
}
|
||||
},
|
||||
"scoreboard": {
|
||||
"actions": {
|
||||
"edit": "Edit"
|
||||
|
||||
@@ -270,6 +270,7 @@
|
||||
"md_categories": "",
|
||||
"md_ccc_rates": "",
|
||||
"md_classes": "",
|
||||
"md_ded_notes": "",
|
||||
"md_hour_split": {
|
||||
"paint": "",
|
||||
"prep": ""
|
||||
@@ -450,6 +451,7 @@
|
||||
"status": "",
|
||||
"statuses": {
|
||||
"active_statuses": "",
|
||||
"color": "",
|
||||
"default_arrived": "",
|
||||
"default_bo": "",
|
||||
"default_canceled": "",
|
||||
@@ -466,6 +468,7 @@
|
||||
"open_statuses": "",
|
||||
"post_production_statuses": "",
|
||||
"pre_production_statuses": "",
|
||||
"production_colors": "",
|
||||
"production_statuses": ""
|
||||
},
|
||||
"target_touchtime": "",
|
||||
@@ -499,6 +502,7 @@
|
||||
},
|
||||
"emaillater": "",
|
||||
"employees": "",
|
||||
"estimators": "",
|
||||
"insurancecos": "",
|
||||
"intakechecklist": "",
|
||||
"jobstatuses": "",
|
||||
@@ -1053,6 +1057,7 @@
|
||||
},
|
||||
"profitcenter_labor": "",
|
||||
"profitcenter_part": "",
|
||||
"prt_dsmk_m": "",
|
||||
"prt_dsmk_p": "",
|
||||
"status": "Estado",
|
||||
"tax_part": "",
|
||||
@@ -1088,6 +1093,7 @@
|
||||
"autoallocate": "",
|
||||
"changelaborrate": "",
|
||||
"changestatus": "Cambiar Estado",
|
||||
"changestimator": "",
|
||||
"convert": "Convertir",
|
||||
"deliver": "",
|
||||
"dms": {
|
||||
@@ -1202,6 +1208,7 @@
|
||||
"date_open": "Abierto",
|
||||
"date_scheduled": "Programado",
|
||||
"ded_amt": "Deducible",
|
||||
"ded_note": "",
|
||||
"ded_status": "Estado deducible",
|
||||
"depreciation_taxes": "Depreciación / Impuestos",
|
||||
"dms": {
|
||||
@@ -1277,6 +1284,7 @@
|
||||
"local_tax_rate": "",
|
||||
"loss_date": "Fecha de pérdida",
|
||||
"loss_desc": "",
|
||||
"loss_of_use": "",
|
||||
"ma2s": "",
|
||||
"ma3s": "",
|
||||
"mabl": "",
|
||||
@@ -1526,6 +1534,7 @@
|
||||
"sale_labor": "",
|
||||
"sale_parts": "",
|
||||
"sales": "",
|
||||
"savebeforeconversion": "",
|
||||
"scheduledinchange": "",
|
||||
"specialcoveragepolicy": "",
|
||||
"state_tax_amt": "",
|
||||
@@ -1982,6 +1991,9 @@
|
||||
"job_costing_ro": "",
|
||||
"job_notes": "",
|
||||
"key_tag": "",
|
||||
"mpi_animal_checklist": "",
|
||||
"mpi_eglass_auth": "",
|
||||
"mpi_final_acct_sheet": "",
|
||||
"paint_grid": "",
|
||||
"parts_label_single": "",
|
||||
"parts_list": "",
|
||||
@@ -2195,6 +2207,11 @@
|
||||
"work_in_progress_payables": ""
|
||||
}
|
||||
},
|
||||
"schedule": {
|
||||
"labels": {
|
||||
"manualevent": ""
|
||||
}
|
||||
},
|
||||
"scoreboard": {
|
||||
"actions": {
|
||||
"edit": ""
|
||||
|
||||
@@ -270,6 +270,7 @@
|
||||
"md_categories": "",
|
||||
"md_ccc_rates": "",
|
||||
"md_classes": "",
|
||||
"md_ded_notes": "",
|
||||
"md_hour_split": {
|
||||
"paint": "",
|
||||
"prep": ""
|
||||
@@ -450,6 +451,7 @@
|
||||
"status": "",
|
||||
"statuses": {
|
||||
"active_statuses": "",
|
||||
"color": "",
|
||||
"default_arrived": "",
|
||||
"default_bo": "",
|
||||
"default_canceled": "",
|
||||
@@ -466,6 +468,7 @@
|
||||
"open_statuses": "",
|
||||
"post_production_statuses": "",
|
||||
"pre_production_statuses": "",
|
||||
"production_colors": "",
|
||||
"production_statuses": ""
|
||||
},
|
||||
"target_touchtime": "",
|
||||
@@ -499,6 +502,7 @@
|
||||
},
|
||||
"emaillater": "",
|
||||
"employees": "",
|
||||
"estimators": "",
|
||||
"insurancecos": "",
|
||||
"intakechecklist": "",
|
||||
"jobstatuses": "",
|
||||
@@ -1053,6 +1057,7 @@
|
||||
},
|
||||
"profitcenter_labor": "",
|
||||
"profitcenter_part": "",
|
||||
"prt_dsmk_m": "",
|
||||
"prt_dsmk_p": "",
|
||||
"status": "Statut",
|
||||
"tax_part": "",
|
||||
@@ -1088,6 +1093,7 @@
|
||||
"autoallocate": "",
|
||||
"changelaborrate": "",
|
||||
"changestatus": "Changer le statut",
|
||||
"changestimator": "",
|
||||
"convert": "Convertir",
|
||||
"deliver": "",
|
||||
"dms": {
|
||||
@@ -1202,6 +1208,7 @@
|
||||
"date_open": "Ouvrir",
|
||||
"date_scheduled": "Prévu",
|
||||
"ded_amt": "Déductible",
|
||||
"ded_note": "",
|
||||
"ded_status": "Statut de franchise",
|
||||
"depreciation_taxes": "Amortissement / taxes",
|
||||
"dms": {
|
||||
@@ -1277,6 +1284,7 @@
|
||||
"local_tax_rate": "",
|
||||
"loss_date": "Date de perte",
|
||||
"loss_desc": "",
|
||||
"loss_of_use": "",
|
||||
"ma2s": "",
|
||||
"ma3s": "",
|
||||
"mabl": "",
|
||||
@@ -1526,6 +1534,7 @@
|
||||
"sale_labor": "",
|
||||
"sale_parts": "",
|
||||
"sales": "",
|
||||
"savebeforeconversion": "",
|
||||
"scheduledinchange": "",
|
||||
"specialcoveragepolicy": "",
|
||||
"state_tax_amt": "",
|
||||
@@ -1982,6 +1991,9 @@
|
||||
"job_costing_ro": "",
|
||||
"job_notes": "",
|
||||
"key_tag": "",
|
||||
"mpi_animal_checklist": "",
|
||||
"mpi_eglass_auth": "",
|
||||
"mpi_final_acct_sheet": "",
|
||||
"paint_grid": "",
|
||||
"parts_label_single": "",
|
||||
"parts_list": "",
|
||||
@@ -2195,6 +2207,11 @@
|
||||
"work_in_progress_payables": ""
|
||||
}
|
||||
},
|
||||
"schedule": {
|
||||
"labels": {
|
||||
"manualevent": ""
|
||||
}
|
||||
},
|
||||
"scoreboard": {
|
||||
"actions": {
|
||||
"edit": ""
|
||||
|
||||
@@ -374,6 +374,39 @@ export const TemplateList = (type, context) => {
|
||||
CA_SK: true,
|
||||
},
|
||||
},
|
||||
mpi_final_acct_sheet: {
|
||||
title: i18n.t("printcenter.jobs.mpi_final_acct_sheet"),
|
||||
description: "Thank You Letter by RO",
|
||||
key: "mpi_final_acct_sheet",
|
||||
subject: i18n.t("printcenter.jobs.mpi_final_acct_sheet"),
|
||||
disabled: false,
|
||||
group: "post",
|
||||
regions: {
|
||||
CA_MB: true,
|
||||
},
|
||||
},
|
||||
mpi_eglass_auth: {
|
||||
title: i18n.t("printcenter.jobs.mpi_eglass_auth"),
|
||||
description: "Thank You Letter by RO",
|
||||
key: "mpi_eglass_auth",
|
||||
subject: i18n.t("printcenter.jobs.mpi_eglass_auth"),
|
||||
disabled: false,
|
||||
group: "pre",
|
||||
regions: {
|
||||
CA_MB: true,
|
||||
},
|
||||
},
|
||||
mpi_animal_checklist: {
|
||||
title: i18n.t("printcenter.jobs.mpi_animal_checklist"),
|
||||
description: "Thank You Letter by RO",
|
||||
key: "mpi_animal_checklist",
|
||||
subject: i18n.t("printcenter.jobs.mpi_animal_checklist"),
|
||||
disabled: false,
|
||||
group: "pre",
|
||||
regions: {
|
||||
CA_MB: true,
|
||||
},
|
||||
},
|
||||
// parts_label_multi: {
|
||||
// title: i18n.t("printcenter.jobs.parts_label_multi"),
|
||||
// description: "Thank You Letter by RO",
|
||||
|
||||
@@ -9,5 +9,11 @@ export function alphaSort(a, b) {
|
||||
}
|
||||
|
||||
export function dateSort(a, b) {
|
||||
return new Date(b) - new Date(a);
|
||||
return new Date(a) - new Date(b);
|
||||
}
|
||||
|
||||
export function statusSort(a, b, statusList) {
|
||||
return (
|
||||
statusList.findIndex((x) => x === a) - statusList.findIndex((x) => x === b)
|
||||
);
|
||||
}
|
||||
|
||||
1847
client/yarn.lock
1847
client/yarn.lock
File diff suppressed because it is too large
Load Diff
@@ -203,6 +203,7 @@
|
||||
- authlevel
|
||||
- default_prod_list_view
|
||||
- id
|
||||
- qbo_realmId
|
||||
- shopid
|
||||
- useremail
|
||||
filter:
|
||||
@@ -216,6 +217,7 @@
|
||||
- active
|
||||
- authlevel
|
||||
- default_prod_list_view
|
||||
- qbo_realmId
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -824,6 +826,8 @@
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
- md_classes
|
||||
- md_ded_notes
|
||||
- md_estimators
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_jobline_presets
|
||||
@@ -838,6 +842,8 @@
|
||||
- md_responsibility_centers
|
||||
- md_ro_statuses
|
||||
- messagingservicesid
|
||||
- pbs_configuration
|
||||
- pbs_serialnumber
|
||||
- phone
|
||||
- prodtargethrs
|
||||
- production_config
|
||||
@@ -898,6 +904,8 @@
|
||||
- md_categories
|
||||
- md_ccc_rates
|
||||
- md_classes
|
||||
- md_ded_notes
|
||||
- md_estimators
|
||||
- md_hour_split
|
||||
- md_ins_cos
|
||||
- md_jobline_presets
|
||||
@@ -2585,6 +2593,7 @@
|
||||
- date_open
|
||||
- date_scheduled
|
||||
- ded_amt
|
||||
- ded_note
|
||||
- ded_status
|
||||
- deliverchecklist
|
||||
- depreciation_taxes
|
||||
@@ -2660,6 +2669,7 @@
|
||||
- loss_cat
|
||||
- loss_date
|
||||
- loss_desc
|
||||
- loss_of_use
|
||||
- loss_type
|
||||
- other_amount_payable
|
||||
- owner_owing
|
||||
@@ -2834,6 +2844,7 @@
|
||||
- date_open
|
||||
- date_scheduled
|
||||
- ded_amt
|
||||
- ded_note
|
||||
- ded_status
|
||||
- deliverchecklist
|
||||
- depreciation_taxes
|
||||
@@ -2909,6 +2920,7 @@
|
||||
- loss_cat
|
||||
- loss_date
|
||||
- loss_desc
|
||||
- loss_of_use
|
||||
- loss_type
|
||||
- other_amount_payable
|
||||
- owner_owing
|
||||
@@ -3093,6 +3105,7 @@
|
||||
- date_open
|
||||
- date_scheduled
|
||||
- ded_amt
|
||||
- ded_note
|
||||
- ded_status
|
||||
- deliverchecklist
|
||||
- depreciation_taxes
|
||||
@@ -3168,6 +3181,7 @@
|
||||
- loss_cat
|
||||
- loss_date
|
||||
- loss_desc
|
||||
- loss_of_use
|
||||
- loss_type
|
||||
- other_amount_payable
|
||||
- owner_owing
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "pbs_configuration" jsonb
|
||||
-- null default jsonb_build_object();
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "pbs_configuration" jsonb
|
||||
null default jsonb_build_object();
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "pbs_serialnumber" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "pbs_serialnumber" text
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "entegral_id" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "entegral_id" text
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."jobs" add column "loss_of_use" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "loss_of_use" text
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "md_estimators" jsonb
|
||||
-- null default jsonb_build_array();
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "md_estimators" jsonb
|
||||
null default jsonb_build_array();
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."jobs" add column "ded_note" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."jobs" add column "ded_note" text
|
||||
null;
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."bodyshops" add column "md_ded_notes" jsonb
|
||||
-- null default jsonb_build_array();
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."bodyshops" add column "md_ded_notes" jsonb
|
||||
null default jsonb_build_array();
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- alter table "public"."associations" add column "qbo_realmId" text
|
||||
-- null;
|
||||
@@ -0,0 +1,2 @@
|
||||
alter table "public"."associations" add column "qbo_realmId" text
|
||||
null;
|
||||
28166
logs/oAuthClient-log.log
Normal file
28166
logs/oAuthClient-log.log
Normal file
File diff suppressed because one or more lines are too long
23
package.json
23
package.json
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.1",
|
||||
"license": "UNLICENSED",
|
||||
"engines": {
|
||||
"node": "12.18.3",
|
||||
"node": "12.22.6",
|
||||
"npm": "7.17.0"
|
||||
},
|
||||
"scripts": {
|
||||
@@ -17,20 +17,20 @@
|
||||
"start": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"aws-sdk": "^2.1000.0",
|
||||
"aws-sdk": "^2.1013.0",
|
||||
"bluebird": "^3.7.2",
|
||||
"body-parser": "^1.18.3",
|
||||
"cloudinary": "^1.27.0",
|
||||
"cloudinary": "^1.27.1",
|
||||
"compression": "^1.7.4",
|
||||
"cookie-parser": "^1.4.5",
|
||||
"cors": "2.8.5",
|
||||
"csrf": "^3.1.0",
|
||||
"dinero.js": "^1.9.0",
|
||||
"dinero.js": "^1.9.1",
|
||||
"dotenv": "10.0.0",
|
||||
"express": "^4.16.4",
|
||||
"firebase-admin": "^9.12.0",
|
||||
"graphql": "^15.6.0",
|
||||
"graphql-request": "^3.4.0",
|
||||
"graphql": "^15.6.1",
|
||||
"graphql-request": "^3.6.1",
|
||||
"graylog2": "^0.2.1",
|
||||
"inline-css": "^3.0.0",
|
||||
"intuit-oauth": "^4.0.0",
|
||||
@@ -39,14 +39,15 @@
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-mailjet": "^3.3.4",
|
||||
"node-quickbooks": "^2.0.39",
|
||||
"nodemailer": "^6.6.5",
|
||||
"nodemailer": "^6.7.0",
|
||||
"phone": "^3.1.8",
|
||||
"query-string": "^7.0.1",
|
||||
"soap": "^0.42.0",
|
||||
"socket.io": "^4.2.0",
|
||||
"ssh2-sftp-client": "^7.0.4",
|
||||
"stripe": "^8.178.0",
|
||||
"twilio": "^3.68.0",
|
||||
"socket.io": "^4.3.1",
|
||||
"ssh2-sftp-client": "^7.1.0",
|
||||
"stripe": "^8.184.0",
|
||||
"twilio": "^3.70.0",
|
||||
"uuid": "^8.3.2",
|
||||
"xmlbuilder2": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
14
server.js
14
server.js
@@ -45,7 +45,17 @@ app.use(cookieParser());
|
||||
app.use(bodyParser.json({ limit: "50mb" }));
|
||||
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
|
||||
//app.use(enforce.HTTPS({ trustProtoHeader: true }));
|
||||
app.use(cors());
|
||||
app.use(
|
||||
cors()
|
||||
// cors({
|
||||
// credentials: true,
|
||||
// origin: [
|
||||
// "https://test.imex.online",
|
||||
// "http://localhost:3000",
|
||||
// "https://imex.online",
|
||||
// ],
|
||||
// })
|
||||
);
|
||||
|
||||
//Email Based Paths.
|
||||
var sendEmail = require("./server/email/sendemail.js");
|
||||
@@ -150,9 +160,11 @@ app.post("/qbo/authorize", fb.validateFirebaseIdToken, qbo.authorize);
|
||||
app.get("/qbo/callback", qbo.callback);
|
||||
app.post("/qbo/receivables", fb.validateFirebaseIdToken, qbo.receivables);
|
||||
app.post("/qbo/payables", fb.validateFirebaseIdToken, qbo.payables);
|
||||
app.post("/qbo/payments", fb.validateFirebaseIdToken, qbo.payments);
|
||||
|
||||
var data = require("./server/data/data");
|
||||
app.post("/data/ah", data.autohouse);
|
||||
app.post("/data/arms", data.arms);
|
||||
|
||||
var ioevent = require("./server/ioevent/ioevent");
|
||||
app.post("/ioevent", ioevent.default);
|
||||
|
||||
21
server/accounting/pbs/pbs-constants.js
Normal file
21
server/accounting/pbs/pbs-constants.js
Normal file
@@ -0,0 +1,21 @@
|
||||
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_PBS_USER = process.env.IMEX_PBS_USER,
|
||||
IMEX_PBS_PASSWORD = process.env.IMEX_PBS_PASSWORD;
|
||||
const PBS_CREDENTIALS = {
|
||||
password: IMEX_PBS_PASSWORD,
|
||||
username: IMEX_PBS_USER,
|
||||
};
|
||||
|
||||
exports.PBS_CREDENTIALS = PBS_CREDENTIALS;
|
||||
// const cdkDomain =
|
||||
// process.env.NODE_ENV === "production"
|
||||
// ? "https://3pa.dmotorworks.com"
|
||||
// : "https://uat-3pa.dmotorworks.com";
|
||||
35
server/accounting/pbs/pbs-job-export.js
Normal file
35
server/accounting/pbs/pbs-job-export.js
Normal file
@@ -0,0 +1,35 @@
|
||||
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 { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
|
||||
//const CalcualteAllocations = require("./cdk-calculate-allocations").default;
|
||||
|
||||
const moment = require("moment");
|
||||
|
||||
exports.default = async function (socket, jobid) {
|
||||
socket.logEvents = [];
|
||||
socket.recordid = jobid;
|
||||
|
||||
try {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
`Received Job export request for id ${jobid}`
|
||||
);
|
||||
} catch (error) {
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"ERROR",
|
||||
`Error encountered in PbsJobExport. ${error}`
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -31,14 +31,16 @@ exports.default = function ({
|
||||
hasMashLine = true;
|
||||
}
|
||||
//Parts Lines Mappings.
|
||||
if (jobline.profitcenter_part && jobline.act_price) {
|
||||
if (jobline.profitcenter_part) {
|
||||
let DineroAmount = Dinero({
|
||||
amount: Math.round(jobline.act_price * 100),
|
||||
amount: Math.round((jobline.act_price || 0) * 100),
|
||||
}).multiply(jobline.part_qty || 1);
|
||||
|
||||
if (
|
||||
(jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0) ||
|
||||
jobline.prt_dsmk_m !== 0
|
||||
((jobline.db_ref === "900511" || jobline.db_ref === "900510") &&
|
||||
jobline.prt_dsmk_m &&
|
||||
jobline.prt_dsmk_m !== 0)
|
||||
) {
|
||||
// console.log("Have a part discount", jobline);
|
||||
DineroAmount = DineroAmount.add(
|
||||
@@ -315,8 +317,12 @@ exports.default = function ({
|
||||
if (qbo) {
|
||||
Object.keys(invoiceLineHash).forEach((key) => {
|
||||
Object.keys(invoiceLineHash[key]).forEach((key2) => {
|
||||
const account = responsibilityCenters.profits.find(
|
||||
(p) => p.name === key
|
||||
);
|
||||
InvoiceLineAdd.push({
|
||||
...invoiceLineHash[key][key2],
|
||||
...(account ? { Description: account.accountdesc } : {}),
|
||||
Amount: invoiceLineHash[key][key2].Amount.toFormat(DineroQbFormat),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ const OAuthClient = require("intuit-oauth");
|
||||
const client = require("../../graphql-client/graphql-client").client;
|
||||
const queries = require("../../graphql-client/queries");
|
||||
const queryString = require("query-string");
|
||||
|
||||
const oauthClient = new OAuthClient({
|
||||
clientId: process.env.QBO_CLIENT_ID,
|
||||
clientSecret: process.env.QBO_SECRET,
|
||||
@@ -18,6 +19,16 @@ const oauthClient = new OAuthClient({
|
||||
logging: true,
|
||||
});
|
||||
|
||||
let url;
|
||||
|
||||
if (process.env.NODE_ENV === "production") {
|
||||
url = `https://imex.online`;
|
||||
} else if (process.env.NODE_ENV === "test") {
|
||||
url = `https://test.imex.online`;
|
||||
} else {
|
||||
url = `http://localhost:3000`;
|
||||
}
|
||||
|
||||
exports.default = async (req, res) => {
|
||||
const params = queryString.parse(req.url.split("?").reverse()[0]);
|
||||
try {
|
||||
@@ -28,14 +39,15 @@ exports.default = async (req, res) => {
|
||||
error: authResponse.json,
|
||||
});
|
||||
res.redirect(
|
||||
`http://localhost:3000/manage/accounting/qbo?error=${encodeURIComponent(
|
||||
`${url}/manage/accounting/qbo?error=${encodeURIComponent(
|
||||
JSON.stringify(authResponse.json)
|
||||
)}`
|
||||
);
|
||||
} else {
|
||||
await client.request(queries.SET_QBO_AUTH, {
|
||||
await client.request(queries.SET_QBO_AUTH_WITH_REALM, {
|
||||
email: params.state,
|
||||
qbo_auth: { ...authResponse.json, createdAt: Date.now() },
|
||||
qbo_realmId: params.realmId,
|
||||
});
|
||||
logger.log(
|
||||
"qbo-callback-create-token-success",
|
||||
@@ -46,9 +58,7 @@ exports.default = async (req, res) => {
|
||||
);
|
||||
|
||||
res.redirect(
|
||||
`http://localhost:3000/manage/accounting/qbo?${queryString.stringify(
|
||||
params
|
||||
)}`
|
||||
`${url}/manage/accounting/qbo?${queryString.stringify(params)}`
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
@@ -34,9 +34,13 @@ exports.default = async (req, res) => {
|
||||
const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
|
||||
email: req.user.email,
|
||||
});
|
||||
const { qbo_realmId } = response.associations[0];
|
||||
|
||||
oauthClient.setToken(response.associations[0].qbo_auth);
|
||||
|
||||
if (!qbo_realmId) {
|
||||
res.status(401).json({ error: "No company associated." });
|
||||
return;
|
||||
}
|
||||
await refreshOauthToken(oauthClient, req);
|
||||
|
||||
const BearerToken = req.headers.authorization;
|
||||
@@ -60,24 +64,38 @@ exports.default = async (req, res) => {
|
||||
for (const bill of bills) {
|
||||
try {
|
||||
let vendorRecord;
|
||||
vendorRecord = await QueryVendorRecord(oauthClient, req, bill);
|
||||
vendorRecord = await QueryVendorRecord(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
bill
|
||||
);
|
||||
|
||||
if (!vendorRecord) {
|
||||
vendorRecord = await InsertVendorRecord(oauthClient, req, bill);
|
||||
vendorRecord = await InsertVendorRecord(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
bill
|
||||
);
|
||||
}
|
||||
|
||||
const insertResults = await InsertBill(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
bill,
|
||||
vendorRecord
|
||||
);
|
||||
|
||||
ret.push({ billid: bill.id, success: true });
|
||||
} catch (error) {
|
||||
ret.push({
|
||||
billid: bill.id,
|
||||
success: false,
|
||||
errorMessage: error.message,
|
||||
errorMessage:
|
||||
(error && error.authResponse && error.authResponse.body) ||
|
||||
(error && error.message),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -90,11 +108,11 @@ exports.default = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
async function QueryVendorRecord(oauthClient, req, bill) {
|
||||
async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) {
|
||||
try {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(
|
||||
req.cookies.qbo_realmId,
|
||||
qbo_realmId,
|
||||
"query",
|
||||
`select * From vendor where DisplayName = '${bill.vendor.name}'`
|
||||
),
|
||||
@@ -112,19 +130,21 @@ async function QueryVendorRecord(oauthClient, req, bill) {
|
||||
);
|
||||
} catch (error) {
|
||||
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
|
||||
error,
|
||||
error:
|
||||
(error && error.authResponse && error.authResponse.body) ||
|
||||
(error && error.message),
|
||||
method: "QueryVendorRecord",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async function InsertVendorRecord(oauthClient, req, bill) {
|
||||
async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) {
|
||||
const Vendor = {
|
||||
DisplayName: bill.vendor.name,
|
||||
};
|
||||
try {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(req.cookies.qbo_realmId, "vendor"),
|
||||
url: urlBuilder(qbo_realmId, "vendor"),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -135,22 +155,28 @@ async function InsertVendorRecord(oauthClient, req, bill) {
|
||||
return result && result.Vendor;
|
||||
} catch (error) {
|
||||
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
|
||||
error,
|
||||
error:
|
||||
(error && error.authResponse && error.authResponse.body) ||
|
||||
(error && error.message),
|
||||
method: "InsertVendorRecord",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function InsertBill(oauthClient, req, bill, vendor) {
|
||||
const { accounts, taxCodes, classes } = await QueryMetaData(oauthClient, req);
|
||||
async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor) {
|
||||
const { accounts, taxCodes, classes } = await QueryMetaData(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req
|
||||
);
|
||||
|
||||
const billQbo = {
|
||||
VendorRef: {
|
||||
value: vendor.Id,
|
||||
},
|
||||
TxnDate: moment(bill.date).format("YYYY-MM-DD"),
|
||||
DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
|
||||
//DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
|
||||
DocNumber: bill.invoice_number,
|
||||
...(bill.job.class ? { ClassRef: { Id: classes[bill.job.class] } } : {}),
|
||||
|
||||
@@ -164,13 +190,20 @@ async function InsertBill(oauthClient, req, bill, vendor) {
|
||||
bill.job.class,
|
||||
bill.job.bodyshop.md_responsibility_centers.sales_tax_codes,
|
||||
classes,
|
||||
taxCodes
|
||||
taxCodes,
|
||||
bill.job.bodyshop.md_responsibility_centers.costs
|
||||
)
|
||||
),
|
||||
};
|
||||
logger.log("qbo-payable-objectlog", "DEBUG", req.user.email, bill.id, {
|
||||
billQbo,
|
||||
});
|
||||
try {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(req.cookies.qbo_realmId, "bill"),
|
||||
url: urlBuilder(
|
||||
qbo_realmId,
|
||||
bill.is_credit_memo ? "vendorcredit" : "bill"
|
||||
),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -181,7 +214,9 @@ async function InsertBill(oauthClient, req, bill, vendor) {
|
||||
return result && result.Bill;
|
||||
} catch (error) {
|
||||
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
|
||||
error,
|
||||
error:
|
||||
(error && error.authResponse && error.authResponse.body) ||
|
||||
(error && error.message),
|
||||
method: "InsertBill",
|
||||
});
|
||||
throw error;
|
||||
@@ -204,10 +239,12 @@ const generateBillLine = (
|
||||
billLine,
|
||||
accounts,
|
||||
jobClass,
|
||||
responsibilityCenters,
|
||||
ioSalesTaxCodes,
|
||||
classes,
|
||||
taxCodes
|
||||
taxCodes,
|
||||
costCenters
|
||||
) => {
|
||||
const account = costCenters.find((c) => c.name === billLine.cost_center);
|
||||
return {
|
||||
DetailType: "AccountBasedExpenseLineDetail",
|
||||
|
||||
@@ -215,12 +252,10 @@ const generateBillLine = (
|
||||
...(jobClass ? { ClassRef: { Id: classes[jobClass] } } : {}),
|
||||
TaxCodeRef: {
|
||||
value:
|
||||
taxCodes[
|
||||
findTaxCode(billLine.applicable_taxes, responsibilityCenters)
|
||||
],
|
||||
taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)],
|
||||
},
|
||||
AccountRef: {
|
||||
value: accounts[billLine.cost_center],
|
||||
value: accounts[account.accountname],
|
||||
},
|
||||
},
|
||||
|
||||
@@ -231,10 +266,11 @@ const generateBillLine = (
|
||||
.toFormat(DineroQbFormat),
|
||||
};
|
||||
};
|
||||
async function QueryMetaData(oauthClient, req) {
|
||||
|
||||
async function QueryMetaData(oauthClient, qbo_realmId, req) {
|
||||
const accounts = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(
|
||||
req.cookies.qbo_realmId,
|
||||
qbo_realmId,
|
||||
"query",
|
||||
`select * From Account where AccountType = 'Cost of Goods Sold'`
|
||||
),
|
||||
@@ -245,7 +281,7 @@ async function QueryMetaData(oauthClient, req) {
|
||||
});
|
||||
setNewRefreshToken(req.user.email, accounts);
|
||||
const taxCodes = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From TaxCode`),
|
||||
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -253,7 +289,7 @@ async function QueryMetaData(oauthClient, req) {
|
||||
});
|
||||
|
||||
const classes = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Class`),
|
||||
url: urlBuilder(qbo_realmId, "query", `select * From Class`),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -264,6 +300,7 @@ async function QueryMetaData(oauthClient, req) {
|
||||
|
||||
taxCodes.json &&
|
||||
taxCodes.json.QueryResponse &&
|
||||
taxCodes.json.QueryResponse.TaxCode &&
|
||||
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
|
||||
taxCodeMapping[t.Name] = t.Id;
|
||||
});
|
||||
@@ -272,13 +309,15 @@ async function QueryMetaData(oauthClient, req) {
|
||||
|
||||
accounts.json &&
|
||||
accounts.json.QueryResponse &&
|
||||
accounts.json.QueryResponse.Account &&
|
||||
accounts.json.QueryResponse.Account.forEach((t) => {
|
||||
accountMapping[t.Name] = t.Id;
|
||||
accountMapping[t.FullyQualifiedName] = t.Id;
|
||||
});
|
||||
|
||||
const classMapping = {};
|
||||
classes.json &&
|
||||
classes.json.QueryResponse &&
|
||||
classes.json.QueryResponse.Class &&
|
||||
classes.json.QueryResponse.Class.forEach((t) => {
|
||||
accountMapping[t.Name] = t.Id;
|
||||
});
|
||||
|
||||
@@ -0,0 +1,299 @@
|
||||
const path = require("path");
|
||||
require("dotenv").config({
|
||||
path: path.resolve(
|
||||
process.cwd(),
|
||||
`.env.${process.env.NODE_ENV || "development"}`
|
||||
),
|
||||
});
|
||||
const logger = require("../../utils/logger");
|
||||
const Dinero = require("dinero.js");
|
||||
|
||||
const apiGqlClient = require("../../graphql-client/graphql-client").client;
|
||||
const queries = require("../../graphql-client/queries");
|
||||
const {
|
||||
refresh: refreshOauthToken,
|
||||
setNewRefreshToken,
|
||||
} = require("./qbo-callback");
|
||||
const OAuthClient = require("intuit-oauth");
|
||||
const moment = require("moment");
|
||||
const GraphQLClient = require("graphql-request").GraphQLClient;
|
||||
const {
|
||||
QueryInsuranceCo,
|
||||
InsertInsuranceCo,
|
||||
InsertJob,
|
||||
InsertOwner,
|
||||
QueryJob,
|
||||
QueryOwner,
|
||||
} = require("../qbo/qbo-receivables");
|
||||
const { urlBuilder } = require("./qbo");
|
||||
const { DineroQbFormat } = require("../accounting-constants");
|
||||
|
||||
exports.default = async (req, res) => {
|
||||
const oauthClient = new OAuthClient({
|
||||
clientId: process.env.QBO_CLIENT_ID,
|
||||
clientSecret: process.env.QBO_SECRET,
|
||||
environment:
|
||||
process.env.NODE_ENV === "production" ? "production" : "sandbox",
|
||||
redirectUri: process.env.QBO_REDIRECT_URI,
|
||||
logging: true,
|
||||
});
|
||||
try {
|
||||
//Fetch the API Access Tokens & Set them for the session.
|
||||
const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
|
||||
email: req.user.email,
|
||||
});
|
||||
const { qbo_realmId } = response.associations[0];
|
||||
oauthClient.setToken(response.associations[0].qbo_auth);
|
||||
if (!qbo_realmId) {
|
||||
res.status(401).json({ error: "No company associated." });
|
||||
return;
|
||||
}
|
||||
await refreshOauthToken(oauthClient, req);
|
||||
|
||||
const BearerToken = req.headers.authorization;
|
||||
const { payments: paymentsToQuery } = req.body;
|
||||
//Query Job Info
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
|
||||
headers: {
|
||||
Authorization: BearerToken,
|
||||
},
|
||||
});
|
||||
logger.log("qbo-payment-create", "DEBUG", req.user.email, paymentsToQuery);
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: BearerToken })
|
||||
.request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
|
||||
payments: paymentsToQuery,
|
||||
});
|
||||
|
||||
const { payments, bodyshops } = result;
|
||||
const bodyshop = bodyshops[0];
|
||||
|
||||
const ret = [];
|
||||
|
||||
for (const payment of payments) {
|
||||
try {
|
||||
const isThreeTier = bodyshop.accountingconfig.tiers === 3;
|
||||
const twoTierPref = bodyshop.accountingconfig.twotierpref;
|
||||
|
||||
//Replace this with a for-each loop to check every single Job that's included in the list.
|
||||
|
||||
let insCoCustomerTier, ownerCustomerTier, jobTier;
|
||||
if (isThreeTier || (!isThreeTier && twoTierPref === "source")) {
|
||||
//Insert the insurance company tier.
|
||||
//Query for top level customer, the insurance company name.
|
||||
insCoCustomerTier = await QueryInsuranceCo(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment.job
|
||||
);
|
||||
if (!insCoCustomerTier) {
|
||||
//Creating the Insurance Customer.
|
||||
insCoCustomerTier = await InsertInsuranceCo(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment.job,
|
||||
bodyshop
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (isThreeTier || (!isThreeTier && twoTierPref === "name")) {
|
||||
//Insert the name/owner and account for whether the source should be the ins co in 3 tier..
|
||||
ownerCustomerTier = await QueryOwner(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment.job
|
||||
);
|
||||
//Query for the owner itself.
|
||||
if (!ownerCustomerTier) {
|
||||
ownerCustomerTier = await InsertOwner(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment.job,
|
||||
isThreeTier,
|
||||
insCoCustomerTier
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
//Query for the Job or Create it.
|
||||
jobTier = await QueryJob(oauthClient, qbo_realmId, req, payment.job);
|
||||
|
||||
// Need to validate that the job tier is associated to the right individual?
|
||||
|
||||
if (!jobTier) {
|
||||
jobTier = await InsertJob(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment.job,
|
||||
ownerCustomerTier || insCoCustomerTier
|
||||
);
|
||||
}
|
||||
|
||||
await InsertPayment(oauthClient, qbo_realmId, req, payment, jobTier);
|
||||
ret.push({ paymentid: payment.id, success: true });
|
||||
} catch (error) {
|
||||
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
|
||||
error:
|
||||
(error && error.authResponse && error.authResponse.body) ||
|
||||
(error && error.message),
|
||||
});
|
||||
|
||||
ret.push({
|
||||
paymentid: payment.id,
|
||||
success: false,
|
||||
errorMessage:
|
||||
(error && error.authResponse && error.authResponse.body) ||
|
||||
(error && error.message),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json(ret);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
logger.log("qbo-payment-create-error", "ERROR", req.user.email, { error });
|
||||
res.status(400).json(error);
|
||||
}
|
||||
};
|
||||
|
||||
async function InsertPayment(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment,
|
||||
parentRef
|
||||
) {
|
||||
const { paymentMethods, invoices } = await QueryMetaData(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
payment.job.ro_number
|
||||
);
|
||||
|
||||
if (invoices && invoices.length !== 1) {
|
||||
throw new Error(
|
||||
`More than 1 invoice with DocNumber ${payment.ro_number} found.`
|
||||
);
|
||||
}
|
||||
|
||||
const paymentQbo = {
|
||||
CustomerRef: {
|
||||
value: parentRef.Id,
|
||||
},
|
||||
TxnDate: moment(payment.date).format("YYYY-MM-DD"),
|
||||
//DueDate: bill.due_date && moment(bill.due_date).format("YYYY-MM-DD"),
|
||||
DocNumber: payment.paymentnum,
|
||||
TotalAmt: Dinero({
|
||||
amount: Math.round(payment.amount * 100),
|
||||
}).toFormat(DineroQbFormat),
|
||||
PaymentMethodRef: {
|
||||
value: paymentMethods[payment.type],
|
||||
},
|
||||
...(invoices && invoices.length === 1
|
||||
? {
|
||||
Line: [
|
||||
{
|
||||
Amount: Dinero({
|
||||
amount: Math.round(payment.amount * 100),
|
||||
}).toFormat(DineroQbFormat),
|
||||
LinkedTxn: [
|
||||
{
|
||||
TxnId: invoices[0].Id,
|
||||
TxnType: "Invoice",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
logger.log("qbo-payments-objectlog", "DEBUG", req.user.email, payment.id, {
|
||||
paymentQbo,
|
||||
});
|
||||
try {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(qbo_realmId, "payment"),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(paymentQbo),
|
||||
});
|
||||
setNewRefreshToken(req.user.email, result);
|
||||
return result && result.Bill;
|
||||
} catch (error) {
|
||||
logger.log("qbo-payables-error", "DEBUG", req.user.email, payment.id, {
|
||||
error: error && error.message,
|
||||
method: "InsertPayment",
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async function QueryMetaData(oauthClient, qbo_realmId, req, ro_number) {
|
||||
const invoice = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(
|
||||
qbo_realmId,
|
||||
"query",
|
||||
`select * From Invoice where DocNumber = '${ro_number}'`
|
||||
),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
const paymentMethods = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(qbo_realmId, "query", `select * From PaymentMethod`),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
setNewRefreshToken(req.user.email, paymentMethods);
|
||||
|
||||
// const classes = await oauthClient.makeApiCall({
|
||||
// url: urlBuilder(qbo_realmId, "query", `select * From Class`),
|
||||
// method: "POST",
|
||||
// headers: {
|
||||
// "Content-Type": "application/json",
|
||||
// },
|
||||
// });
|
||||
|
||||
const paymentMethodMapping = {};
|
||||
|
||||
paymentMethods.json &&
|
||||
paymentMethods.json.QueryResponse &&
|
||||
paymentMethods.json.QueryResponse.PaymentMethod &&
|
||||
paymentMethods.json.QueryResponse.PaymentMethod.forEach((t) => {
|
||||
paymentMethodMapping[t.Name] = t.Id;
|
||||
});
|
||||
|
||||
// const accountMapping = {};
|
||||
|
||||
// accounts.json &&
|
||||
// accounts.json.QueryResponse &&
|
||||
// accounts.json.QueryResponse.Account.forEach((t) => {
|
||||
// accountMapping[t.Name] = t.Id;
|
||||
// });
|
||||
|
||||
// const classMapping = {};
|
||||
// classes.json &&
|
||||
// classes.json.QueryResponse &&
|
||||
// classes.json.QueryResponse.Class.forEach((t) => {
|
||||
// accountMapping[t.Name] = t.Id;
|
||||
// });
|
||||
|
||||
return {
|
||||
paymentMethods: paymentMethodMapping,
|
||||
invoices:
|
||||
invoice.json &&
|
||||
invoice.json.QueryResponse &&
|
||||
invoice.json.QueryResponse.Invoice,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -34,7 +34,11 @@ exports.default = async (req, res) => {
|
||||
const response = await apiGqlClient.request(queries.GET_QBO_AUTH, {
|
||||
email: req.user.email,
|
||||
});
|
||||
|
||||
const { qbo_realmId } = response.associations[0];
|
||||
if (!qbo_realmId) {
|
||||
res.status(401).json({ error: "No company associated." });
|
||||
return;
|
||||
}
|
||||
oauthClient.setToken(response.associations[0].qbo_auth);
|
||||
|
||||
await refreshOauthToken(oauthClient, req);
|
||||
@@ -66,28 +70,40 @@ exports.default = async (req, res) => {
|
||||
//Replace this with a for-each loop to check every single Job that's included in the list.
|
||||
|
||||
let insCoCustomerTier, ownerCustomerTier, jobTier;
|
||||
if (isThreeTier || twoTierPref === "source") {
|
||||
if (isThreeTier || (!isThreeTier && twoTierPref === "source")) {
|
||||
//Insert the insurance company tier.
|
||||
//Query for top level customer, the insurance company name.
|
||||
insCoCustomerTier = await QueryInsuranceCo(oauthClient, req, job);
|
||||
insCoCustomerTier = await QueryInsuranceCo(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job
|
||||
);
|
||||
if (!insCoCustomerTier) {
|
||||
//Creating the Insurance Customer.
|
||||
insCoCustomerTier = await InsertInsuranceCo(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job,
|
||||
bodyshop
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log(insCoCustomerTier);
|
||||
if (isThreeTier || twoTierPref === "name") {
|
||||
|
||||
if (isThreeTier || (!isThreeTier && twoTierPref === "name")) {
|
||||
//Insert the name/owner and account for whether the source should be the ins co in 3 tier..
|
||||
ownerCustomerTier = await QueryOwner(oauthClient, req, job);
|
||||
ownerCustomerTier = await QueryOwner(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job
|
||||
);
|
||||
//Query for the owner itself.
|
||||
if (!ownerCustomerTier) {
|
||||
ownerCustomerTier = await InsertOwner(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job,
|
||||
isThreeTier,
|
||||
@@ -95,29 +111,41 @@ exports.default = async (req, res) => {
|
||||
);
|
||||
}
|
||||
}
|
||||
console.log(ownerCustomerTier);
|
||||
|
||||
//Query for the Job or Create it.
|
||||
jobTier = await QueryJob(oauthClient, req, job);
|
||||
jobTier = await QueryJob(oauthClient, qbo_realmId, req, job);
|
||||
|
||||
// Need to validate that the job tier is associated to the right individual?
|
||||
|
||||
if (!jobTier) {
|
||||
jobTier = await InsertJob(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job,
|
||||
isThreeTier,
|
||||
ownerCustomerTier
|
||||
|
||||
ownerCustomerTier || insCoCustomerTier
|
||||
);
|
||||
}
|
||||
|
||||
if (!req.body.custDataOnly) {
|
||||
await InsertInvoice(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job,
|
||||
bodyshop,
|
||||
jobTier
|
||||
);
|
||||
}
|
||||
console.log(jobTier);
|
||||
await InsertInvoice(oauthClient, req, job, bodyshop, jobTier);
|
||||
ret.push({ jobid: job.id, success: true });
|
||||
} catch (error) {
|
||||
ret.push({
|
||||
jobid: job.id,
|
||||
success: false,
|
||||
errorMessage: error.message,
|
||||
errorMessage:
|
||||
(error && error.authResponse && error.authResponse.body) ||
|
||||
(error && error.message),
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -132,11 +160,11 @@ exports.default = async (req, res) => {
|
||||
}
|
||||
};
|
||||
|
||||
async function QueryInsuranceCo(oauthClient, req, job) {
|
||||
async function QueryInsuranceCo(oauthClient, qbo_realmId, req, job) {
|
||||
try {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(
|
||||
req.cookies.qbo_realmId,
|
||||
qbo_realmId,
|
||||
"query",
|
||||
`select * From Customer where DisplayName = '${job.ins_co_nm}'`
|
||||
),
|
||||
@@ -160,7 +188,8 @@ async function QueryInsuranceCo(oauthClient, req, job) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
async function InsertInsuranceCo(oauthClient, req, job, bodyshop) {
|
||||
exports.QueryInsuranceCo = QueryInsuranceCo;
|
||||
async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
|
||||
const insCo = bodyshop.md_ins_cos.find((i) => i.name === job.ins_co_nm);
|
||||
|
||||
const Customer = {
|
||||
@@ -175,7 +204,7 @@ async function InsertInsuranceCo(oauthClient, req, job, bodyshop) {
|
||||
};
|
||||
try {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(req.cookies.qbo_realmId, "customer"),
|
||||
url: urlBuilder(qbo_realmId, "customer"),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -192,12 +221,12 @@ async function InsertInsuranceCo(oauthClient, req, job, bodyshop) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function QueryOwner(oauthClient, req, job) {
|
||||
exports.InsertInsuranceCo = InsertInsuranceCo;
|
||||
async function QueryOwner(oauthClient, qbo_realmId, req, job) {
|
||||
const ownerName = generateOwnerTier(job, true, null);
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(
|
||||
req.cookies.qbo_realmId,
|
||||
qbo_realmId,
|
||||
"query",
|
||||
`select * From Customer where DisplayName = '${ownerName}'`
|
||||
),
|
||||
@@ -214,8 +243,15 @@ async function QueryOwner(oauthClient, req, job) {
|
||||
result.json.QueryResponse.Customer[0]
|
||||
);
|
||||
}
|
||||
|
||||
async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) {
|
||||
exports.QueryOwner = QueryOwner;
|
||||
async function InsertOwner(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job,
|
||||
isThreeTier,
|
||||
parentTierRef
|
||||
) {
|
||||
const ownerName = generateOwnerTier(job, true, null);
|
||||
const Customer = {
|
||||
DisplayName: ownerName,
|
||||
@@ -237,7 +273,7 @@ async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) {
|
||||
};
|
||||
try {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(req.cookies.qbo_realmId, "customer"),
|
||||
url: urlBuilder(qbo_realmId, "customer"),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -254,11 +290,11 @@ async function InsertOwner(oauthClient, req, job, isThreeTier, parentTierRef) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function QueryJob(oauthClient, req, job) {
|
||||
exports.InsertOwner = InsertOwner;
|
||||
async function QueryJob(oauthClient, qbo_realmId, req, job) {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(
|
||||
req.cookies.qbo_realmId,
|
||||
qbo_realmId,
|
||||
"query",
|
||||
`select * From Customer where DisplayName = '${job.ro_number}'`
|
||||
),
|
||||
@@ -275,8 +311,8 @@ async function QueryJob(oauthClient, req, job) {
|
||||
result.json.QueryResponse.Customer[0]
|
||||
);
|
||||
}
|
||||
|
||||
async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) {
|
||||
exports.QueryJob = QueryJob;
|
||||
async function InsertJob(oauthClient, qbo_realmId, req, job, parentTierRef) {
|
||||
const Customer = {
|
||||
DisplayName: job.ro_number,
|
||||
BillAddr: {
|
||||
@@ -286,18 +322,15 @@ async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) {
|
||||
PostalCode: job.ownr_zip,
|
||||
CountrySubDivisionCode: job.ownr_st,
|
||||
},
|
||||
...(isThreeTier
|
||||
? {
|
||||
Job: true,
|
||||
ParentRef: {
|
||||
value: parentTierRef.Id,
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
|
||||
Job: true,
|
||||
ParentRef: {
|
||||
value: parentTierRef.Id,
|
||||
},
|
||||
};
|
||||
try {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(req.cookies.qbo_realmId, "customer"),
|
||||
url: urlBuilder(qbo_realmId, "customer"),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -314,10 +347,10 @@ async function InsertJob(oauthClient, req, job, isThreeTier, parentTierRef) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function QueryMetaData(oauthClient, req) {
|
||||
exports.InsertJob = InsertJob;
|
||||
async function QueryMetaData(oauthClient, qbo_realmId, req) {
|
||||
const items = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Item`),
|
||||
url: urlBuilder(qbo_realmId, "query", `select * From Item`),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -325,7 +358,7 @@ async function QueryMetaData(oauthClient, req) {
|
||||
});
|
||||
setNewRefreshToken(req.user.email, items);
|
||||
const taxCodes = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From TaxCode`),
|
||||
url: urlBuilder(qbo_realmId, "query", `select * From TaxCode`),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -333,7 +366,7 @@ async function QueryMetaData(oauthClient, req) {
|
||||
});
|
||||
|
||||
const classes = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(req.cookies.qbo_realmId, "query", `select * From Class`),
|
||||
url: urlBuilder(qbo_realmId, "query", `select * From Class`),
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
@@ -344,6 +377,7 @@ async function QueryMetaData(oauthClient, req) {
|
||||
|
||||
taxCodes.json &&
|
||||
taxCodes.json.QueryResponse &&
|
||||
taxCodes.json.QueryResponse.TaxCode &&
|
||||
taxCodes.json.QueryResponse.TaxCode.forEach((t) => {
|
||||
taxCodeMapping[t.Name] = t.Id;
|
||||
});
|
||||
@@ -352,6 +386,7 @@ async function QueryMetaData(oauthClient, req) {
|
||||
|
||||
items.json &&
|
||||
items.json.QueryResponse &&
|
||||
items.json.QueryResponse.Item &&
|
||||
items.json.QueryResponse.Item.forEach((t) => {
|
||||
itemMapping[t.Name] = t.Id;
|
||||
});
|
||||
@@ -359,6 +394,7 @@ async function QueryMetaData(oauthClient, req) {
|
||||
const classMapping = {};
|
||||
classes.json &&
|
||||
classes.json.QueryResponse &&
|
||||
classes.json.QueryResponse.Class &&
|
||||
classes.json.QueryResponse.Class.forEach((t) => {
|
||||
itemMapping[t.Name] = t.Id;
|
||||
});
|
||||
@@ -370,8 +406,19 @@ async function QueryMetaData(oauthClient, req) {
|
||||
};
|
||||
}
|
||||
|
||||
async function InsertInvoice(oauthClient, req, job, bodyshop, parentTierRef) {
|
||||
const { items, taxCodes, classes } = await QueryMetaData(oauthClient, req);
|
||||
async function InsertInvoice(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req,
|
||||
job,
|
||||
bodyshop,
|
||||
parentTierRef
|
||||
) {
|
||||
const { items, taxCodes, classes } = await QueryMetaData(
|
||||
oauthClient,
|
||||
qbo_realmId,
|
||||
req
|
||||
);
|
||||
const InvoiceLineAdd = CreateInvoiceLines({
|
||||
bodyshop,
|
||||
jobs_by_pk: job,
|
||||
@@ -391,15 +438,20 @@ async function InsertInvoice(oauthClient, req, job, bodyshop, parentTierRef) {
|
||||
...(bodyshop.accountingconfig.printlater
|
||||
? { PrintStatus: "NeedToPrint" }
|
||||
: {}),
|
||||
...(bodyshop.accountingconfig.emaillater
|
||||
...(bodyshop.accountingconfig.emaillater && job.ownr_ea
|
||||
? { EmailStatus: "NeedToSend" }
|
||||
: {}),
|
||||
};
|
||||
|
||||
logger.log("qbo-receivable-objectlog", "DEBUG", req.user.email, job.id, {
|
||||
invoiceObj,
|
||||
});
|
||||
|
||||
try {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
url: urlBuilder(req.cookies.qbo_realmId, "invoice"),
|
||||
url: urlBuilder(qbo_realmId, "invoice"),
|
||||
method: "POST",
|
||||
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
|
||||
@@ -8,9 +8,7 @@ require("dotenv").config({
|
||||
|
||||
function urlBuilder(realmId, object, query = null) {
|
||||
return `https://${
|
||||
process.env.NODE_ENV === "development" || !process.env.NODE_ENV
|
||||
? "sandbox-"
|
||||
: ""
|
||||
process.env.NODE_ENV === "production" ? "" : "sandbox-"
|
||||
}quickbooks.api.intuit.com/v3/company/${realmId}/${object}${
|
||||
query ? `?query=${encodeURIComponent(query)}` : ""
|
||||
}`;
|
||||
@@ -22,3 +20,4 @@ exports.authorize = require("./qbo-authorize").default;
|
||||
exports.refresh = require("./qbo-callback").refresh;
|
||||
exports.receivables = require("./qbo-receivables").default;
|
||||
exports.payables = require("./qbo-payables").default;
|
||||
exports.payments = require("./qbo-payments").default;
|
||||
|
||||
@@ -111,18 +111,18 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => {
|
||||
ReceivePaymentAddRq: {
|
||||
ReceivePaymentAdd: {
|
||||
CustomerRef: {
|
||||
FullName:
|
||||
payment.job.bodyshop.accountingconfig.tiers === 3
|
||||
? `${generateSourceTier(payment.job)}:${generateOwnerTier(
|
||||
payment.job,
|
||||
isThreeTier,
|
||||
twoTierPref
|
||||
)}:${generateJobTier(payment.job)}`
|
||||
: `${generateOwnerTier(
|
||||
payment.job,
|
||||
isThreeTier,
|
||||
twoTierPref
|
||||
)}:${generateJobTier(payment.job)}`,
|
||||
FullName: (payment.job.bodyshop.accountingconfig.tiers === 3
|
||||
? `${generateSourceTier(payment.job)}:${generateOwnerTier(
|
||||
payment.job,
|
||||
isThreeTier,
|
||||
twoTierPref
|
||||
)}:${generateJobTier(payment.job)}`
|
||||
: `${generateOwnerTier(
|
||||
payment.job,
|
||||
isThreeTier,
|
||||
twoTierPref
|
||||
)}:${generateJobTier(payment.job)}`
|
||||
).trim(),
|
||||
},
|
||||
ARAccountRef: {
|
||||
FullName:
|
||||
@@ -142,9 +142,6 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => {
|
||||
payment.stripeid || ""
|
||||
} ${payment.payer ? ` PAID BY ${payment.payer}` : ""}`,
|
||||
IsAutoApply: true,
|
||||
// AppliedToTxnAdd:{
|
||||
// T
|
||||
// }
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -158,18 +155,18 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => {
|
||||
CreditMemoAddRq: {
|
||||
CreditMemoAdd: {
|
||||
CustomerRef: {
|
||||
FullName:
|
||||
payment.job.bodyshop.accountingconfig.tiers === 3
|
||||
? `${generateSourceTier(payment.job)}:${generateOwnerTier(
|
||||
payment.job,
|
||||
isThreeTier,
|
||||
twoTierPref
|
||||
)}:${generateJobTier(payment.job)}`
|
||||
: `${generateOwnerTier(
|
||||
payment.job,
|
||||
isThreeTier,
|
||||
twoTierPref
|
||||
)}:${generateJobTier(payment.job)}`,
|
||||
FullName: (payment.job.bodyshop.accountingconfig.tiers === 3
|
||||
? `${generateSourceTier(payment.job)}:${generateOwnerTier(
|
||||
payment.job,
|
||||
isThreeTier,
|
||||
twoTierPref
|
||||
)}:${generateJobTier(payment.job)}`
|
||||
: `${generateOwnerTier(
|
||||
payment.job,
|
||||
isThreeTier,
|
||||
twoTierPref
|
||||
)}:${generateJobTier(payment.job)}`
|
||||
).trim(),
|
||||
},
|
||||
ARAccountRef: {
|
||||
FullName:
|
||||
|
||||
@@ -122,7 +122,7 @@ const generateSourceCustomerQbxml = (jobs_by_pk, bodyshop) => {
|
||||
"@onError": "continueOnError",
|
||||
CustomerAddRq: {
|
||||
CustomerAdd: {
|
||||
Name: jobs_by_pk.ins_co_nm,
|
||||
Name: jobs_by_pk.ins_co_nm.trim(),
|
||||
// BillAddress: {
|
||||
// Addr1: jobs_by_pk.ownr_addr1,
|
||||
// Addr2: jobs_by_pk.ownr_addr2,
|
||||
@@ -238,16 +238,16 @@ const generateInvoiceQbxml = (
|
||||
InvoiceAddRq: {
|
||||
InvoiceAdd: {
|
||||
CustomerRef: {
|
||||
FullName:
|
||||
bodyshop.accountingconfig.tiers === 3
|
||||
? `${generateSourceTier(jobs_by_pk)}:${generateOwnerTier(
|
||||
jobs_by_pk
|
||||
)}:${generateJobTier(jobs_by_pk)}`
|
||||
: `${generateOwnerTier(
|
||||
jobs_by_pk,
|
||||
isThreeTier,
|
||||
twoTierPref
|
||||
)}:${generateJobTier(jobs_by_pk)}`,
|
||||
FullName: (bodyshop.accountingconfig.tiers === 3
|
||||
? `${generateSourceTier(jobs_by_pk)}:${generateOwnerTier(
|
||||
jobs_by_pk
|
||||
)}:${generateJobTier(jobs_by_pk)}`
|
||||
: `${generateOwnerTier(
|
||||
jobs_by_pk,
|
||||
isThreeTier,
|
||||
twoTierPref
|
||||
)}:${generateJobTier(jobs_by_pk)}`
|
||||
).trim(),
|
||||
},
|
||||
|
||||
...(jobs_by_pk.class
|
||||
|
||||
@@ -6,24 +6,25 @@ exports.addQbxmlHeader = addQbxmlHeader = (xml) => {
|
||||
};
|
||||
|
||||
exports.generateSourceTier = (jobs_by_pk) => {
|
||||
return jobs_by_pk.ins_co_nm;
|
||||
return jobs_by_pk.ins_co_nm && jobs_by_pk.ins_co_nm.trim();
|
||||
};
|
||||
|
||||
exports.generateJobTier = (jobs_by_pk) => {
|
||||
return jobs_by_pk.ro_number;
|
||||
return jobs_by_pk.ro_number && jobs_by_pk.ro_number.trim();
|
||||
};
|
||||
|
||||
exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => {
|
||||
if (isThreeTier) {
|
||||
//It's always gonna be the owner now. Same as 2 tier by name
|
||||
return jobs_by_pk.ownr_co_nm
|
||||
? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${
|
||||
jobs_by_pk.owner.accountingid || ""
|
||||
}`
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${jobs_by_pk.ownr_fn || ""}`.substring(
|
||||
0,
|
||||
30
|
||||
)} #${jobs_by_pk.owner.accountingid || ""}`;
|
||||
return (
|
||||
jobs_by_pk.ownr_co_nm
|
||||
? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${
|
||||
jobs_by_pk.owner.accountingid || ""
|
||||
}`
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${
|
||||
jobs_by_pk.ownr_fn || ""
|
||||
}`.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}`
|
||||
).trim();
|
||||
} else {
|
||||
//What's the 2 tier pref?
|
||||
if (twotierpref === "source") {
|
||||
@@ -31,13 +32,15 @@ exports.generateOwnerTier = (jobs_by_pk, isThreeTier, twotierpref) => {
|
||||
//It should be the insurance co.
|
||||
} else {
|
||||
//Same as 3 tier
|
||||
return jobs_by_pk.ownr_co_nm
|
||||
? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${
|
||||
jobs_by_pk.owner.accountingid || ""
|
||||
}`
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${
|
||||
jobs_by_pk.ownr_fn || ""
|
||||
}`.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}`;
|
||||
return (
|
||||
jobs_by_pk.ownr_co_nm
|
||||
? `${jobs_by_pk.ownr_co_nm.substring(0, 30)} #${
|
||||
jobs_by_pk.owner.accountingid || ""
|
||||
}`
|
||||
: `${`${jobs_by_pk.ownr_ln || ""} ${
|
||||
jobs_by_pk.ownr_fn || ""
|
||||
}`.substring(0, 30)} #${jobs_by_pk.owner.accountingid || ""}`
|
||||
).trim();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -70,7 +70,12 @@ exports.default = async function (socket, jobid) {
|
||||
amount: Math.round(val.act_price * 100),
|
||||
}).multiply(val.part_qty || 1);
|
||||
|
||||
if ((val.prt_dsmk_p && val.prt_dsmk_p !== 0) || val.prt_dsmk_m !== 0) {
|
||||
if (
|
||||
(val.prt_dsmk_p && val.prt_dsmk_p !== 0) ||
|
||||
((val.db_ref === "900511" || val.db_ref === "900510") &&
|
||||
val.prt_dsmk_m &&
|
||||
val.prt_dsmk_m !== 0)
|
||||
) {
|
||||
// console.log("Have a part discount", val);
|
||||
DineroAmount = DineroAmount.add(
|
||||
val.prt_dsmk_m && val.prt_dsmk_m !== 0
|
||||
|
||||
@@ -15,7 +15,7 @@ const CalcualteAllocations = require("./cdk-calculate-allocations").default;
|
||||
|
||||
const moment = require("moment");
|
||||
|
||||
const replaceSpecialRegex = `[^a-zA-Z0-9 .,\n #]+`;
|
||||
const replaceSpecialRegex = `[^a-zA-Z0-9 .,\n #]+/g`;
|
||||
|
||||
exports.default = async function (socket, { txEnvelope, jobid }) {
|
||||
socket.logEvents = [];
|
||||
@@ -422,7 +422,12 @@ async function QueryDmsCustomerById(socket, JobData, CustomerId) {
|
||||
}
|
||||
|
||||
async function QueryDmsCustomerByName(socket, JobData) {
|
||||
const ownerName = `${JobData.ownr_ln},${JobData.ownr_fn}`;
|
||||
const ownerName = (
|
||||
JobData.ownr_co_nm
|
||||
? JobData.ownr_co_nm
|
||||
: `${JobData.ownr_ln},${JobData.ownr_fn}`
|
||||
).replace(replaceSpecialRegex, "");
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"DEBUG",
|
||||
@@ -439,7 +444,7 @@ async function QueryDmsCustomerByName(socket, JobData) {
|
||||
arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
|
||||
arg2: {
|
||||
verb: "EXACT",
|
||||
key: ownerName.replaceAll(replaceSpecialRegex, ""),
|
||||
key: ownerName,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -576,14 +581,10 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
|
||||
//TODO: Verify whether we need to bring more information in.
|
||||
id: { value: newCustomerNumber },
|
||||
address: {
|
||||
addressLine: socket.JobData.ownr_addr1.replaceAll(
|
||||
replaceSpecialRegex,
|
||||
""
|
||||
),
|
||||
city: socket.JobData.ownr_city.replaceAll(
|
||||
replaceSpecialRegex,
|
||||
""
|
||||
),
|
||||
addressLine:
|
||||
socket.JobData.ownr_addr1 &&
|
||||
socket.JobData.ownr_addr1.replace(replaceSpecialRegex, ""),
|
||||
city: socket.JobData.ownr_city&& socket.JobData.ownr_city.replace(replaceSpecialRegex, ""),
|
||||
country: null,
|
||||
postalCode:
|
||||
socket.JobData.ownr_zip &&
|
||||
@@ -591,7 +592,7 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
|
||||
.toUpperCase()
|
||||
.replace(/\W/g, "")
|
||||
.replace(/(...)/, "$1 "),
|
||||
stateOrProvince: socket.JobData.ownr_st,
|
||||
stateOrProvince: socket.JobData.ownr_st&&socket.JobData.ownr_st.replace(replaceSpecialRegex, ""),
|
||||
},
|
||||
contactInfo: {
|
||||
mainTelephoneNumber: {
|
||||
@@ -605,19 +606,16 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
|
||||
},
|
||||
demographics: null,
|
||||
name1: {
|
||||
companyname: socket.JobData.ownr_co_nm.replaceAll(
|
||||
replaceSpecialRegex,
|
||||
""
|
||||
),
|
||||
firstName: socket.JobData.ownr_fn.replaceAll(
|
||||
replaceSpecialRegex,
|
||||
""
|
||||
),
|
||||
companyname:
|
||||
socket.JobData.ownr_co_nm &&
|
||||
socket.JobData.ownr_co_nm.replace(replaceSpecialRegex, ""),
|
||||
firstName:
|
||||
socket.JobData.ownr_fn &&
|
||||
socket.JobData.ownr_fn.replace(replaceSpecialRegex, ""),
|
||||
fullname: null,
|
||||
lastName: socket.JobData.ownr_ln.replaceAll(
|
||||
replaceSpecialRegex,
|
||||
""
|
||||
),
|
||||
lastName:
|
||||
socket.JobData.ownr_ln &&
|
||||
socket.JobData.ownr_ln.replace(replaceSpecialRegex, ""),
|
||||
middleName: null,
|
||||
nameType: "Person",
|
||||
suffix: null,
|
||||
@@ -886,7 +884,9 @@ async function InsertDmsStartWip(socket) {
|
||||
arg2: {
|
||||
acctgDate: moment().format("YYYY-MM-DD"),
|
||||
//socket.JobData.invoice_date
|
||||
desc: socket.txEnvelope.story.replaceAll(replaceSpecialRegex, ""),
|
||||
desc:
|
||||
socket.txEnvelope.story &&
|
||||
socket.txEnvelope.story.replace(replaceSpecialRegex, ""),
|
||||
docType: 10 || 7, //Need to check what this usually would be? Apparently it is almost always 10 or 7.
|
||||
//1 Cash Receipt , 2 Check, 3 Journal Voucher, 4 Parts invoice, 5 Payable Invoice, 6 Recurring Entry, 7 Repair Order Invoice, 8 Vehicle Purchase Invoice, 9 Vehicle Sale Invoice, 10 Other, 11 Payroll, 12 Finance Charge, 13 FMLR Invoice, 14 Parts Credit Memo, 15 Manufacturer Document, 16 FMLR Credit Memo
|
||||
m13Flag: 0,
|
||||
@@ -1090,18 +1090,6 @@ async function GenerateTransWips(socket) {
|
||||
|
||||
wips.push(item);
|
||||
});
|
||||
|
||||
//should validate that the wips = 0
|
||||
|
||||
console.log(
|
||||
"WIPS TOTAL",
|
||||
wips.reduce((acc, val) => {
|
||||
console.log(val);
|
||||
console.log(acc + val.postAmt);
|
||||
return acc + val.postAmt;
|
||||
}, 0)
|
||||
);
|
||||
|
||||
return wips;
|
||||
}
|
||||
|
||||
|
||||
651
server/data/SampleArms.json
Normal file
651
server/data/SampleArms.json
Normal file
@@ -0,0 +1,651 @@
|
||||
{
|
||||
"RepairOrderFolderAddRq": {
|
||||
"RqUID": "426cce3a-efa7-44d9-b76e-50b9102c4198",
|
||||
"DocumentInfo": {
|
||||
"BMSVer": "4.0.0",
|
||||
"DocumentType": "Repair Order",
|
||||
"DocumentVerCode": "EM",
|
||||
"DocumentVerNum": 0,
|
||||
"DocumentStatus": "O",
|
||||
"CreateDateTime": "2009-03-11T11:58:31.0404914-07:00",
|
||||
"TransmitDateTime": "2009-03-11T11:58:31.0404914-07:00"
|
||||
},
|
||||
"EventInfo": {
|
||||
"AssignmentEvent": {
|
||||
"CreateDateTime": "2009-03-02T17:00:00.0000000-08:00"
|
||||
},
|
||||
"EstimateEvent": {
|
||||
"UploadDateTime": "2009-03-02T17:00:00.0000000-08:00"
|
||||
},
|
||||
"RepairEvent": {
|
||||
"ArrivalDateTime": "2009-03-02T17:00:00.0000000-08:00",
|
||||
"ArrivalOdometerReading": 12540,
|
||||
"TargetCompletionDateTime": "2009-03-09T17:00:00.0000000-07:00",
|
||||
"ActualCompletionDateTime": "2009-03-05T17:00:00.0000000-08:00",
|
||||
"ActualPickUpDateTime": "2009-03-06T17:00:00.0000000-08:00",
|
||||
"CreatedDateTime": "2009-03-02T17:00:00.0000000-08:00",
|
||||
"CloseDateTime": "2009-03-06T17:00:00.0000000-08:00"
|
||||
}
|
||||
},
|
||||
"RepairOrderHeader": {
|
||||
"AdminInfo": {
|
||||
"InsuranceCompany": {
|
||||
"Party": {
|
||||
"OrgInfo": {
|
||||
"CompanyName": "Nationwide Insurance",
|
||||
"IDInfo": {
|
||||
"IDQualifierCode": "US",
|
||||
"IDNum": 44
|
||||
},
|
||||
"Communications": [
|
||||
{
|
||||
"CommQualifier": "WA",
|
||||
"Address": {
|
||||
"Address1": "1245 Central Street",
|
||||
"Address2": "#310",
|
||||
"City": "Anaheim",
|
||||
"StateProvince": "CA",
|
||||
"PostalCode": 92808,
|
||||
"CountryCode": "US"
|
||||
}
|
||||
},
|
||||
{
|
||||
"CommQualifier": "WP",
|
||||
"CommPhone": "714-5551212"
|
||||
},
|
||||
{
|
||||
"CommQualifier": "WF",
|
||||
"CommPhone": "714-5551414"
|
||||
}
|
||||
]
|
||||
},
|
||||
"ContactInfo": {
|
||||
"ContactJobTitle": "Adjuster",
|
||||
"ContactName": {
|
||||
"FirstName": "Jim",
|
||||
"LastName": "Smith"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"InsuranceAgent": {
|
||||
"Party": {
|
||||
"OrgInfo": {
|
||||
"CompanyName": "Nationwide Insurance",
|
||||
"Communications": {
|
||||
"CommQualifier": "WP",
|
||||
"CommPhone": "714-5551212"
|
||||
}
|
||||
},
|
||||
"ContactInfo": {
|
||||
"ContactJobTitle": "Insurance Agent",
|
||||
"ContactName": {
|
||||
"FirstName": "Paul",
|
||||
"LastName": "White"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Insured": {
|
||||
"Party": {
|
||||
"PersonInfo": {
|
||||
"PersonName": {
|
||||
"FirstName": "Jim",
|
||||
"LastName": "Smith"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Owner": {
|
||||
"Party": {
|
||||
"PersonInfo": {
|
||||
"PersonName": {
|
||||
"FirstName": "Cathy",
|
||||
"LastName": "Jones"
|
||||
},
|
||||
"Communications": [
|
||||
{
|
||||
"CommQualifier": "HA",
|
||||
"Address": {
|
||||
"Address1": "2571 Elm Street",
|
||||
"City": "Anaheim",
|
||||
"StateProvince": "CA",
|
||||
"PostalCode": 92808,
|
||||
"CountryCode": "US"
|
||||
}
|
||||
},
|
||||
{
|
||||
"CommQualifier": "HP",
|
||||
"CommPhone": "714-5551212"
|
||||
},
|
||||
{
|
||||
"CommQualifier": "WP",
|
||||
"CommPhone": "714-5551414"
|
||||
},
|
||||
{
|
||||
"CommQualifier": "CP",
|
||||
"CommPhone": "714-5551616"
|
||||
},
|
||||
{
|
||||
"CommQualifier": "EM",
|
||||
"CommEmail": "cjones@cox.net"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"Claimant": {
|
||||
"Party": {
|
||||
"PersonInfo": {
|
||||
"PersonName": {
|
||||
"FirstName": "Jim",
|
||||
"LastName": "Smith"
|
||||
}
|
||||
}
|
||||
},
|
||||
"OwnerInd": true
|
||||
},
|
||||
"Estimator": {
|
||||
"Party": {
|
||||
"PersonInfo": {
|
||||
"PersonName": {
|
||||
"FirstName": "Jim",
|
||||
"LastName": "Smith"
|
||||
},
|
||||
"IDInfo": {
|
||||
"IDQualifierCode": "US",
|
||||
"IDNum": 2941
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"RepairFacility": {
|
||||
"Party": {
|
||||
"OrgInfo": {
|
||||
"CompanyName": "Mikes Auto Collision",
|
||||
"IDInfo": {
|
||||
"IDQualifierCode": "US",
|
||||
"IDNum": 2207
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"RepairOrderIDs": {
|
||||
"RepairOrderNum": 10245,
|
||||
"VendorCode": "C",
|
||||
"EstimateDocumentID": "1223HJ76"
|
||||
},
|
||||
"RepairOrderType": "DRP",
|
||||
"ReferralSourceType": "Yellow Pages",
|
||||
"VehicleInfo": {
|
||||
"VINInfo": {
|
||||
"VIN": {
|
||||
"VINNum": "Z13838383"
|
||||
}
|
||||
},
|
||||
"License": {
|
||||
"LicensePlateNum": "2H6781"
|
||||
},
|
||||
"VehicleDesc": {
|
||||
"ProductionDate": "2009-10",
|
||||
"ModelYear": 2006,
|
||||
"MakeDesc": "Ford",
|
||||
"ModelName": "Mustang Convertible"
|
||||
},
|
||||
"Paint": {
|
||||
"Exterior": {
|
||||
"Color": {
|
||||
"ColorName": "Red",
|
||||
"OEMColorCode": "1M3"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Body": {
|
||||
"BodyStyle": "2 Door Convertible",
|
||||
"Trim": {
|
||||
"TrimCode": "1B3"
|
||||
}
|
||||
},
|
||||
"Condition": {
|
||||
"DrivableInd": "Y"
|
||||
}
|
||||
},
|
||||
"ClaimInfo": {
|
||||
"ClaimNum": "C03062009-01",
|
||||
"PolicyInfo": {
|
||||
"PolicyNum": "P29438484"
|
||||
},
|
||||
"LossInfo": {
|
||||
"Facts": {
|
||||
"LossDateTime": "2009-03-09T17:00:00.0000000-07:00",
|
||||
"LossDescCode": "Collision",
|
||||
"PrimaryPOI": {
|
||||
"POICode": 3
|
||||
},
|
||||
"SecondaryPOI": {
|
||||
"POICode": 2
|
||||
}
|
||||
},
|
||||
"TotalLossInd": "N"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ProfileInfo": {
|
||||
"ProfileName": "Shop Standard Rates",
|
||||
"RateInfo": [
|
||||
{
|
||||
"RateType": "PA",
|
||||
"RateDesc": "Parts Tax",
|
||||
"TaxInfo": {
|
||||
"TaxType": "LS",
|
||||
"TaxableInd": true,
|
||||
"TaxTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Percentage": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "LA",
|
||||
"RateDesc": "Labor Tax",
|
||||
"TaxInfo": {
|
||||
"TaxType": "LS",
|
||||
"TaxableInd": true,
|
||||
"TaxTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Percentage": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "LAB",
|
||||
"RateDesc": "Body Labor",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 38
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "LAS",
|
||||
"RateDesc": "Structural Labor",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 38
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "LAR",
|
||||
"RateDesc": "Refinish Labor",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 38
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "LAG",
|
||||
"RateDesc": "Glass Labor",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 38
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "LAF",
|
||||
"RateDesc": "Frame Labor",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 38
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "LAM",
|
||||
"RateDesc": "Mechancial Labor",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 38
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "LAU",
|
||||
"RateDesc": "User Defined Labor",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "MAPA",
|
||||
"RateDesc": "Paint Materials",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 21,
|
||||
"ThresholdAmt": 0
|
||||
},
|
||||
"MaterialCalcSettings": {
|
||||
"CalcMethodCode": 2,
|
||||
"CalcMaxAmt": 9999.99
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "MASH",
|
||||
"RateDesc": "Shop Materials",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 0
|
||||
},
|
||||
"MaterialCalcSettings": {
|
||||
"CalcMethodCode": 4,
|
||||
"CalcMaxAmt": 9999.99
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "MAHW",
|
||||
"RateDesc": "Hazardous Wastes Removal",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 10
|
||||
},
|
||||
"MaterialCalcSettings": {
|
||||
"CalcMethodCode": 2,
|
||||
"CalcMaxAmt": 10
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "MA2S",
|
||||
"RateDesc": "Two Stage Paint",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 0
|
||||
},
|
||||
"MaterialCalcSettings": {
|
||||
"CalcMethodCode": 1,
|
||||
"CalcMaxAmt": 999999.99
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "MA2T",
|
||||
"RateDesc": "Two Tone Paint",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 0
|
||||
},
|
||||
"MaterialCalcSettings": {
|
||||
"CalcMethodCode": 1,
|
||||
"CalcMaxAmt": 999999.99
|
||||
}
|
||||
},
|
||||
{
|
||||
"RateType": "MA3S",
|
||||
"RateDesc": "Three Stage Paint",
|
||||
"RateTierInfo": {
|
||||
"TierNum": 1,
|
||||
"Rate": 0
|
||||
},
|
||||
"MaterialCalcSettings": {
|
||||
"CalcMethodCode": 1,
|
||||
"CalcMaxAmt": 999999.99
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"StorageDuration": 17,
|
||||
"RepairTotalsInfo": {
|
||||
"LaborTotalsInfo": [
|
||||
{
|
||||
"TotalType": "LAB",
|
||||
"TotalTypeDesc": "Body Labor",
|
||||
"TotalHours": 5.7,
|
||||
"TotalAmt": 216.6
|
||||
},
|
||||
{
|
||||
"TotalType": "LAF",
|
||||
"TotalTypeDesc": "Frame Labor",
|
||||
"TotalHours": 2.5,
|
||||
"TotalAmt": 255.45
|
||||
},
|
||||
{
|
||||
"TotalType": "LAM",
|
||||
"TotalTypeDesc": "Mechanical Labor",
|
||||
"TotalHours": 4.7,
|
||||
"TotalAmt": 489.45
|
||||
},
|
||||
{
|
||||
"TotalType": "LAR",
|
||||
"TotalTypeDesc": "Refinish Labor",
|
||||
"TotalHours": 3.7,
|
||||
"TotalAmt": 140.6
|
||||
}
|
||||
],
|
||||
"PartsTotalsInfo": [
|
||||
{
|
||||
"TotalType": "PAA",
|
||||
"TotalTypeDesc": "Aftermarket Parts",
|
||||
"TotalAmt": 26.4
|
||||
},
|
||||
{
|
||||
"TotalType": "PAC",
|
||||
"TotalTypeDesc": "Re-Chromed Parts",
|
||||
"TotalAmt": 156.45
|
||||
},
|
||||
{
|
||||
"TotalType": "PAG",
|
||||
"TotalTypeDesc": "Glass Parts",
|
||||
"TotalAmt": 740
|
||||
},
|
||||
{
|
||||
"TotalType": "PAL",
|
||||
"TotalTypeDesc": "LKQ/Used Parts",
|
||||
"TotalAmt": 326.4
|
||||
},
|
||||
{
|
||||
"TotalType": "PAM",
|
||||
"TotalTypeDesc": "Remanufactured Parts",
|
||||
"TotalAmt": 50.94
|
||||
},
|
||||
{
|
||||
"TotalType": "PAN",
|
||||
"TotalTypeDesc": "New Parts",
|
||||
"TotalAmt": 4526.4
|
||||
},
|
||||
{
|
||||
"TotalType": "PAR",
|
||||
"TotalTypeDesc": "Recored Parts",
|
||||
"TotalAmt": 209.45
|
||||
}
|
||||
],
|
||||
"OtherChargesTotalsInfo": [
|
||||
{
|
||||
"TotalType": "OTSL",
|
||||
"TotalTypeDesc": "Sublet",
|
||||
"TotalAmt": 0
|
||||
},
|
||||
{
|
||||
"TotalType": "MAPA",
|
||||
"TotalTypeDesc": "Paint Materials",
|
||||
"TotalAmt": 77.7
|
||||
},
|
||||
{
|
||||
"TotalType": "MASH",
|
||||
"TotalTypeDesc": "Shop Materials",
|
||||
"TotalAmt": 0
|
||||
},
|
||||
{
|
||||
"TotalType": "MAHW",
|
||||
"TotalTypeDesc": "Hazardous Wastes Removal",
|
||||
"TotalAmt": 10
|
||||
},
|
||||
{
|
||||
"TotalType": "OTST",
|
||||
"TotalTypeDesc": "Storage",
|
||||
"TotalAmt": 0
|
||||
},
|
||||
{
|
||||
"TotalType": "OTTW",
|
||||
"TotalTypeDesc": "Towing",
|
||||
"TotalAmt": 0
|
||||
},
|
||||
{
|
||||
"TotalType": "OTAC",
|
||||
"TotalTypeDesc": "Additional Charges",
|
||||
"TotalAmt": 0
|
||||
}
|
||||
],
|
||||
"SummaryTotalsInfo": [
|
||||
{
|
||||
"TotalType": "TOT",
|
||||
"TotalSubType": "T2",
|
||||
"TotalTypeDesc": "Net Total",
|
||||
"TotalAmt": 471.3
|
||||
},
|
||||
{
|
||||
"TotalType": "TOT",
|
||||
"TotalSubType": "F7",
|
||||
"TotalTypeDesc": "Sales Tax",
|
||||
"TotalAmt": 0
|
||||
},
|
||||
{
|
||||
"TotalType": "TOT",
|
||||
"TotalSubType": "GST",
|
||||
"TotalTypeDesc": "GST Tax",
|
||||
"TotalAmt": 0
|
||||
},
|
||||
{
|
||||
"TotalType": "TOT",
|
||||
"TotalSubType": "TT",
|
||||
"TotalTypeDesc": "Gross Total",
|
||||
"TotalAmt": 471.3
|
||||
},
|
||||
{
|
||||
"TotalType": "TOT",
|
||||
"TotalSubType": "SM",
|
||||
"TotalTypeDesc": "Supplement Total",
|
||||
"TotalAmt": 0
|
||||
},
|
||||
{
|
||||
"TotalType": "TOT",
|
||||
"TotalSubType": "D2",
|
||||
"TotalTypeDesc": "Deductible",
|
||||
"TotalAmt": 0
|
||||
},
|
||||
{
|
||||
"TotalType": "TOT",
|
||||
"TotalSubType": "BTR",
|
||||
"TotalTypeDesc": "Betterment",
|
||||
"TotalAmt": 0
|
||||
},
|
||||
{
|
||||
"TotalType": "TOT",
|
||||
"TotalSubType": "AA",
|
||||
"TotalTypeDesc": "Appearance Allowance",
|
||||
"TotalAmt": 0
|
||||
},
|
||||
{
|
||||
"TotalType": "TOT",
|
||||
"TotalSubType": "D8",
|
||||
"TotalTypeDesc": "Bottom Line Discount",
|
||||
"TotalAmt": 0
|
||||
},
|
||||
{
|
||||
"TotalType": "TOT",
|
||||
"TotalSubType": "INS",
|
||||
"TotalTypeDesc": "Insurance Pay",
|
||||
"TotalAmt": 471.3
|
||||
},
|
||||
{
|
||||
"TotalType": "TOT",
|
||||
"TotalSubType": "DEPOSIT",
|
||||
"TotalTypeDesc": "Deposit",
|
||||
"TotalAmt": 0
|
||||
},
|
||||
{
|
||||
"TotalType": "TOT",
|
||||
"TotalSubType": "CUST",
|
||||
"TotalTypeDesc": "Customer Pay",
|
||||
"TotalAmt": 0
|
||||
}
|
||||
],
|
||||
"RepairTotalsType": 1
|
||||
},
|
||||
"RepairLabor": {
|
||||
"LaborAllocations": {
|
||||
"LaborAllocation": [
|
||||
{
|
||||
"LaborAllocationUUID": "426cce3a-efa7-44d9-b76e-50b9102c4198",
|
||||
"LaborType": "LAB",
|
||||
"Technician": {
|
||||
"Employee": {
|
||||
"PersonInfo": {
|
||||
"PersonName": {
|
||||
"FirstName": "Jose",
|
||||
"LastName": "Gonzalez"
|
||||
},
|
||||
"IDInfo": {
|
||||
"IDQualifierCode": "US",
|
||||
"IDNum": 2987
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AllocatedHours": 3.5
|
||||
},
|
||||
{
|
||||
"LaborAllocationUUID": "426cce3a-efa7-44d9-b76e-50b9102c4199",
|
||||
"LaborType": "LAR",
|
||||
"Technician": {
|
||||
"Employee": {
|
||||
"PersonInfo": {
|
||||
"PersonName": {
|
||||
"FirstName": "Rcardo",
|
||||
"LastName": "Himenez"
|
||||
},
|
||||
"IDInfo": {
|
||||
"IDQualifierCode": "US",
|
||||
"IDNum": 2989
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"AllocatedHours": 5.5
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ProductionStatus": {
|
||||
"ProductionStage": {
|
||||
"ProductionStageCode": 4,
|
||||
"ProductionStageDateTime": "2009-03-11T11:58:32.1898319-07:00",
|
||||
"ProductionStageStatusComment": "Going to be painted this afternoon"
|
||||
},
|
||||
"RepairStatus": {
|
||||
"RepairStatusCode": 2,
|
||||
"RepairStatusDateTime": "2009-03-11T11:58:32.1898319-07:00",
|
||||
"RepairStatusMemo": "Waiting on back ordered parts"
|
||||
}
|
||||
},
|
||||
"RepairOrderNotes": {
|
||||
"RepairOrderNote": {
|
||||
"LineSequenceNum": 1,
|
||||
"Note": "Revision Requested : approved--but needs est separated.8/22/2008 11:58:53 AM",
|
||||
"CreateDateTime": "2008-08-22T11:58:53",
|
||||
"AuthoredBy": {
|
||||
"FirstName": {
|
||||
"#text": "Elizabeth/FirstName>",
|
||||
"LastName": "Unis"
|
||||
}
|
||||
},
|
||||
"RepairOrderNote": {
|
||||
"LineSequenceNum": 2,
|
||||
"Note": "Approved : 8/26/2008 12:21:08 PM",
|
||||
"CreateDateTime": "2008-08-26T12:21:08",
|
||||
"AuthoredBy": {
|
||||
"FirstName": {
|
||||
"#text": "Elizabeth/FirstName>",
|
||||
"LastName": "Unis"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
817
server/data/arms.js
Normal file
817
server/data/arms.js
Normal file
@@ -0,0 +1,817 @@
|
||||
const path = require("path");
|
||||
const queries = require("../graphql-client/queries");
|
||||
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(),
|
||||
`.env.${process.env.NODE_ENV || "development"}`
|
||||
),
|
||||
});
|
||||
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const uuid = require("uuid").v4;
|
||||
exports.default = async (req, res) => {
|
||||
//Query for the List of Bodyshop Clients.
|
||||
logger.log("arms-start", "DEBUG", "api", null, null);
|
||||
const { bodyshops } = await client.request(queries.GET_ENTEGRAL_SHOPS);
|
||||
|
||||
const allxmlsToUpload = [];
|
||||
const allErrors = [];
|
||||
try {
|
||||
for (const bodyshop of bodyshops) {
|
||||
logger.log("arms-start-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
shopname: bodyshop.shopname,
|
||||
});
|
||||
const erroredJobs = [];
|
||||
try {
|
||||
const { jobs } = await client.request(queries.ENTEGRAL_EXPORT, {
|
||||
bodyshopid: bodyshop.id,
|
||||
});
|
||||
const ret = jobs.map((job) => {
|
||||
const transId = uuid(); // Can this actually be the job id?
|
||||
return {
|
||||
RepairOrderFolderAddRq: {
|
||||
RqUID: transId,
|
||||
DocumentInfo: {
|
||||
BMSVer: "4.0.0",
|
||||
DocumentType: "Repair Order",
|
||||
DocumentVerCode: "EM",
|
||||
DocumentVerNum: GetSupplementNumber(job.joblines), //TODO Get Supplement Number
|
||||
DocumentStatus: GetDocumentstatus(job, bodyshop),
|
||||
CreateDateTime: moment().format(),
|
||||
// TransmitDateTime: "2009-03-11T11:58:31.0404914-07:00", Omitted from ARMS docs
|
||||
},
|
||||
EventInfo: {
|
||||
AssignmentEvent: {
|
||||
CreateDateTime:
|
||||
job.asgn_date && moment(job.asgn_date).format(),
|
||||
},
|
||||
EstimateEvent: {
|
||||
UploadDateTime: "2009-03-02T17:00:00.0000000-08:00", //TODO Figure out what this actually is. 'Date Estimate was uploaded'
|
||||
},
|
||||
RepairEvent: {
|
||||
ArrivalDateTime:
|
||||
job.date_open && moment(job.date_open).format(),
|
||||
ArrivalOdometerReading: job.kmin,
|
||||
TargetCompletionDateTime:
|
||||
job.scheduled_completion &&
|
||||
moment(job.scheduled_completion).format(),
|
||||
ActualCompletionDateTime:
|
||||
job.actual_completion &&
|
||||
moment(job.actual_completion).format(),
|
||||
ActualPickUpDateTime:
|
||||
job.actual_delivery && moment(job.actual_delivery).format(),
|
||||
CloseDateTime:
|
||||
job.date_exported && moment(job.date_exported).format(),
|
||||
},
|
||||
},
|
||||
RepairOrderHeader: {
|
||||
AdminInfo: {
|
||||
InsuranceCompany: {
|
||||
Party: {
|
||||
OrgInfo: {
|
||||
CompanyName: job.ins_co_nm,
|
||||
IDInfo: {
|
||||
IDQualifierCode: "US",
|
||||
IDNum: 44, // ** Not sure where to get this entegral ID from?
|
||||
},
|
||||
Communications: [
|
||||
{
|
||||
CommQualifier: "WA",
|
||||
Address: {
|
||||
Address1: job.ins_addr1,
|
||||
Address2: job.ins_addr2,
|
||||
City: job.ins_city,
|
||||
StateProvince: job.ins_st,
|
||||
PostalCode: job.ins_zip,
|
||||
CountryCode: job.ins_ctry,
|
||||
},
|
||||
},
|
||||
{
|
||||
CommQualifier: "WP",
|
||||
CommPhone: job.ins_ph1,
|
||||
},
|
||||
{
|
||||
CommQualifier: "WF",
|
||||
CommPhone: job.ins_ph2,
|
||||
},
|
||||
],
|
||||
},
|
||||
ContactInfo: {
|
||||
ContactJobTitle: "Adjuster",
|
||||
ContactName: {
|
||||
FirstName: job.est_ct_fn,
|
||||
LastName: job.est_ct_ln,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
// InsuranceAgent: {
|
||||
// Party: {
|
||||
// OrgInfo: {
|
||||
// CompanyName: "Nationwide Insurance",
|
||||
// Communications: {
|
||||
// CommQualifier: "WP",
|
||||
// CommPhone: "714-5551212",
|
||||
// },
|
||||
// },
|
||||
// ContactInfo: {
|
||||
// ContactJobTitle: "Insurance Agent",
|
||||
// ContactName: {
|
||||
// FirstName: "Paul",
|
||||
// LastName: "White",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
Insured: {
|
||||
Party: {
|
||||
PersonInfo: {
|
||||
PersonName: {
|
||||
FirstName: job.insd_fn,
|
||||
LastName: job.insd_ln,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Owner: {
|
||||
Party: {
|
||||
PersonInfo: {
|
||||
PersonName: {
|
||||
FirstName: job.ownr_fn,
|
||||
LastName: job.ownr_ln,
|
||||
},
|
||||
Communications: [
|
||||
{
|
||||
CommQualifier: "HA",
|
||||
Address: {
|
||||
Address1: job.ownr_addr1,
|
||||
|
||||
City: job.ownr_city,
|
||||
StateProvince: job.ownr_st,
|
||||
PostalCode: job.ownr_zip,
|
||||
CountryCode: job.ownr_ctry,
|
||||
},
|
||||
},
|
||||
{
|
||||
CommQualifier: "HP",
|
||||
CommPhone: job.ownr_ph1,
|
||||
},
|
||||
{
|
||||
CommQualifier: "WP",
|
||||
CommPhone: job.ownr_ph2,
|
||||
},
|
||||
{
|
||||
CommQualifier: "CP",
|
||||
CommPhone: job.ownr_ph1,
|
||||
},
|
||||
{
|
||||
CommQualifier: "EM",
|
||||
CommEmail: job.ownr_ea,
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
Claimant: {
|
||||
Party: {
|
||||
PersonInfo: {
|
||||
PersonName: {
|
||||
FirstName: job.clm_ct_fn,
|
||||
LastName: job.clm_ct_ln,
|
||||
},
|
||||
},
|
||||
},
|
||||
OwnerInd: true,
|
||||
},
|
||||
Estimator: {
|
||||
Party: {
|
||||
PersonInfo: {
|
||||
PersonName: {
|
||||
FirstName: job.est_ct_fn,
|
||||
LastName: job.est_ct_ln,
|
||||
},
|
||||
// IDInfo: {
|
||||
// IDQualifierCode: "US",
|
||||
// IDNum: 2941,
|
||||
// },
|
||||
},
|
||||
},
|
||||
},
|
||||
RepairFacility: {
|
||||
//This section not in documentation.
|
||||
Party: {
|
||||
OrgInfo: {
|
||||
CompanyName: bodyshop.shopname,
|
||||
IDInfo: {
|
||||
IDQualifierCode: "US",
|
||||
IDNum: bodyshop.entegral_id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RepairOrderIDs: {
|
||||
RepairOrderNum: job.ro_number,
|
||||
// VendorCode: "C",
|
||||
// EstimateDocumentID: "1223HJ76",
|
||||
},
|
||||
//RepairOrderType: "DRP",
|
||||
//ReferralSourceType: "Yellow Pages",
|
||||
VehicleInfo: {
|
||||
VINInfo: {
|
||||
VIN: {
|
||||
VINNum: job.v_vin,
|
||||
},
|
||||
},
|
||||
License: {
|
||||
LicensePlateNum: job.plate_no,
|
||||
},
|
||||
VehicleDesc: {
|
||||
//ProductionDate: "2009-10",
|
||||
ModelYear: job.v_model_yr,
|
||||
MakeDesc: job.v_make_desc,
|
||||
ModelName: job.v_model_desc,
|
||||
},
|
||||
Paint: {
|
||||
Exterior: {
|
||||
Color: {
|
||||
ColorName: job.v_color,
|
||||
// OEMColorCode: "1M3",
|
||||
},
|
||||
},
|
||||
},
|
||||
// Body: {
|
||||
// BodyStyle: "2 Door Convertible",
|
||||
// Trim: {
|
||||
// TrimCode: "1B3",
|
||||
// },
|
||||
// },
|
||||
Condition: {
|
||||
DrivableInd: job.driveable ? "Y" : "N",
|
||||
},
|
||||
},
|
||||
ClaimInfo: {
|
||||
ClaimNum: job.clm_no,
|
||||
PolicyInfo: {
|
||||
PolicyNum: job.policy_no,
|
||||
},
|
||||
LossInfo: {
|
||||
Facts: {
|
||||
LossDateTime:
|
||||
job.loss_date && moment(job.loss_date).format(),
|
||||
LossDescCode: "Collision",
|
||||
PrimaryPOI: {
|
||||
POICode: job.area_of_damage.impact1,
|
||||
},
|
||||
SecondaryPOI: {
|
||||
POICode: job.area_of_damage.impact2,
|
||||
},
|
||||
},
|
||||
TotalLossInd: job.tlos_ind,
|
||||
},
|
||||
},
|
||||
},
|
||||
ProfileInfo: {
|
||||
//ProfileName: "Shop Standard Rates",
|
||||
RateInfo: [
|
||||
{
|
||||
RateType: "PA",
|
||||
RateDesc: "Parts Tax",
|
||||
TaxInfo: {
|
||||
TaxType: "LS",
|
||||
TaxableInd: true,
|
||||
TaxTierInfo: {
|
||||
TierNum: 1,
|
||||
Percentage: 0, //TODO Find the best place to take the tax rates for parts.
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "LA",
|
||||
RateDesc: "Labor Tax",
|
||||
TaxInfo: {
|
||||
TaxType: "LS",
|
||||
TaxableInd: true,
|
||||
TaxTierInfo: {
|
||||
TierNum: 1,
|
||||
Percentage: 0, //TODO Find the best place to take the tax rates for labor.
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "LAB",
|
||||
RateDesc: "Body Labor",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_lab,
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "LAS",
|
||||
RateDesc: "Structural Labor",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_las,
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "LAR",
|
||||
RateDesc: "Refinish Labor",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_lar,
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "LAG",
|
||||
RateDesc: "Glass Labor",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_lag,
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "LAF",
|
||||
RateDesc: "Frame Labor",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_laf,
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "LAM",
|
||||
RateDesc: "Mechancial Labor",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_lam,
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "LAU",
|
||||
RateDesc: "User Defined Labor",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_lau,
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "MAPA",
|
||||
RateDesc: "Paint Materials",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_mapa,
|
||||
ThresholdAmt: 0,
|
||||
},
|
||||
MaterialCalcSettings: {
|
||||
CalcMethodCode: 2,
|
||||
CalcMaxAmt: 9999.99, //TODO Find threshold amts.
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "MASH",
|
||||
RateDesc: "Shop Materials",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_mash,
|
||||
},
|
||||
MaterialCalcSettings: {
|
||||
CalcMethodCode: 4,
|
||||
CalcMaxAmt: 9999.99, //TODO Find threshold amounts.
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "MAHW",
|
||||
RateDesc: "Hazardous Wastes Removal",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_mahw,
|
||||
},
|
||||
MaterialCalcSettings: {
|
||||
//Todo Capture Calc Settings
|
||||
CalcMethodCode: 2,
|
||||
CalcMaxAmt: 10,
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "MA2S",
|
||||
RateDesc: "Two Stage Paint",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_ma2s,
|
||||
},
|
||||
MaterialCalcSettings: {
|
||||
CalcMethodCode: 1,
|
||||
CalcMaxAmt: 999999.99,
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "MA2T",
|
||||
RateDesc: "Two Tone Paint",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_ma2t,
|
||||
},
|
||||
MaterialCalcSettings: {
|
||||
CalcMethodCode: 1,
|
||||
CalcMaxAmt: 999999.99,
|
||||
},
|
||||
},
|
||||
{
|
||||
RateType: "MA3S",
|
||||
RateDesc: "Three Stage Paint",
|
||||
RateTierInfo: {
|
||||
TierNum: 1,
|
||||
Rate: job.rate_ma3s,
|
||||
},
|
||||
MaterialCalcSettings: {
|
||||
CalcMethodCode: 1,
|
||||
CalcMaxAmt: 999999.99,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
//StorageDuration: 17,
|
||||
RepairTotalsInfo: {
|
||||
LaborTotalsInfo: [
|
||||
{
|
||||
TotalType: "LAB",
|
||||
TotalTypeDesc: "Body Labor",
|
||||
TotalHours: job.job_totals.rates.lab.hours,
|
||||
TotalAmt: Dinero(job.job_totals.rates.lab.total).toFormat(
|
||||
0.0
|
||||
),
|
||||
},
|
||||
{
|
||||
TotalType: "LAF",
|
||||
TotalTypeDesc: "Frame Labor",
|
||||
TotalHours: job.job_totals.rates.laf.hours,
|
||||
TotalAmt: Dinero(job.job_totals.rates.laf.total).toFormat(
|
||||
0.0
|
||||
),
|
||||
},
|
||||
{
|
||||
TotalType: "LAM",
|
||||
TotalTypeDesc: "Mechanical Labor",
|
||||
TotalHours: job.job_totals.rates.lam.hours,
|
||||
TotalAmt: Dinero(job.job_totals.rates.lam.total).toFormat(
|
||||
0.0
|
||||
),
|
||||
},
|
||||
{
|
||||
TotalType: "LAR",
|
||||
TotalTypeDesc: "Refinish Labor",
|
||||
TotalHours: job.job_totals.rates.lar.hours,
|
||||
TotalAmt: Dinero(job.job_totals.rates.lar.total).toFormat(
|
||||
0.0
|
||||
),
|
||||
},
|
||||
],
|
||||
PartsTotalsInfo: [
|
||||
{
|
||||
TotalType: "PAA",
|
||||
TotalTypeDesc: "Aftermarket Parts",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.parts.parts.list.paa &&
|
||||
job.job_totals.parts.parts.list.paa.total
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
{
|
||||
TotalType: "PAC",
|
||||
TotalTypeDesc: "Re-Chromed Parts",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.parts.parts.list.pac &&
|
||||
job.job_totals.parts.parts.list.pac.total
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
{
|
||||
TotalType: "PAG",
|
||||
TotalTypeDesc: "Glass Parts",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.parts.parts.list.pag &&
|
||||
job.job_totals.parts.parts.list.pag.total
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
{
|
||||
TotalType: "PAL",
|
||||
TotalTypeDesc: "LKQ/Used Parts",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.parts.parts.list.pal &&
|
||||
job.job_totals.parts.parts.list.pal.total
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
{
|
||||
TotalType: "PAM",
|
||||
TotalTypeDesc: "Remanufactured Parts",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.parts.parts.list.pam &&
|
||||
job.job_totals.parts.parts.list.pam.total
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
{
|
||||
TotalType: "PAN",
|
||||
TotalTypeDesc: "New Parts",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.parts.parts.list.pan &&
|
||||
job.job_totals.parts.parts.list.pan.total
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
{
|
||||
TotalType: "PAR",
|
||||
TotalTypeDesc: "Recored Parts",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.parts.parts.list.par &&
|
||||
job.job_totals.parts.parts.list.par.total
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
],
|
||||
OtherChargesTotalsInfo: [
|
||||
{
|
||||
TotalType: "OTSL",
|
||||
TotalTypeDesc: "Sublet",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.parts.sublets.total
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
{
|
||||
TotalType: "MAPA",
|
||||
TotalTypeDesc: "Paint Materials",
|
||||
TotalAmt: Dinero(job.job_totals.rates.mapa.total).toFormat(
|
||||
0.0
|
||||
),
|
||||
},
|
||||
{
|
||||
TotalType: "MASH",
|
||||
TotalTypeDesc: "Shop Materials",
|
||||
TotalAmt: Dinero(job.job_totals.rates.mash.total).toFormat(
|
||||
0.0
|
||||
),
|
||||
},
|
||||
// {
|
||||
// TotalType: "MAHW",
|
||||
// TotalTypeDesc: "Hazardous Wastes Removal",
|
||||
// TotalAmt: Dinero(job.job_totals.rates.mahw.total).toFormat(
|
||||
// 0.0
|
||||
// ),
|
||||
// },
|
||||
{
|
||||
TotalType: "OTST",
|
||||
TotalTypeDesc: "Storage",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.additional.storage
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
{
|
||||
TotalType: "OTTW",
|
||||
TotalTypeDesc: "Towing",
|
||||
TotalAmt: Dinero(job.job_totals.additional.towing).toFormat(
|
||||
0.0
|
||||
),
|
||||
},
|
||||
{
|
||||
TotalType: "OTAC",
|
||||
TotalTypeDesc: "Additional Charges",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.additional.additionalCosts
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
],
|
||||
SummaryTotalsInfo: [
|
||||
{
|
||||
TotalType: "TOT",
|
||||
TotalSubType: "T2",
|
||||
TotalTypeDesc: "Net Total",
|
||||
TotalAmt: Dinero(job.job_totals.totals.subtotal).toFormat(
|
||||
0.0
|
||||
),
|
||||
},
|
||||
{
|
||||
TotalType: "TOT",
|
||||
TotalSubType: "F7",
|
||||
TotalTypeDesc: "Sales Tax",
|
||||
TotalAmt: Dinero(job.job_totals.totals.state_tax).toFormat(
|
||||
0.0
|
||||
),
|
||||
},
|
||||
{
|
||||
TotalType: "TOT",
|
||||
TotalSubType: "GST",
|
||||
TotalTypeDesc: "GST Tax",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.totals.federal_tax
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
{
|
||||
TotalType: "TOT",
|
||||
TotalSubType: "TT",
|
||||
TotalTypeDesc: "Gross Total",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.totals.total_repairs
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
// {
|
||||
// TotalType: "TOT",
|
||||
// TotalSubType: "SM",
|
||||
// TotalTypeDesc: "Supplement Total",
|
||||
// TotalAmt: 0,
|
||||
// },
|
||||
{
|
||||
TotalType: "TOT",
|
||||
TotalSubType: "D2",
|
||||
TotalTypeDesc: "Deductible",
|
||||
TotalAmt: Dinero({
|
||||
amount: Math.round((job.ded_amt || 0) * 100),
|
||||
}).toFormat("0.0"),
|
||||
},
|
||||
{
|
||||
TotalType: "TOT",
|
||||
TotalSubType: "BTR",
|
||||
TotalTypeDesc: "Betterment",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.totals.custPayable.dep_taxes
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
{
|
||||
TotalType: "TOT",
|
||||
TotalSubType: "AA",
|
||||
TotalTypeDesc: "Appearance Allowance",
|
||||
TotalAmt: 0,
|
||||
},
|
||||
{
|
||||
TotalType: "TOT",
|
||||
TotalSubType: "D8",
|
||||
TotalTypeDesc: "Bottom Line Discount",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.additional.adjustments
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
{
|
||||
TotalType: "TOT",
|
||||
TotalSubType: "INS",
|
||||
TotalTypeDesc: "Insurance Pay",
|
||||
TotalAmt: Dinero(job.job_totals.totals.total_repairs)
|
||||
.subtract(Dinero(job.job_totals.totals.custPayable.total))
|
||||
.toFormat("0.0"),
|
||||
},
|
||||
// {
|
||||
// TotalType: "TOT",
|
||||
// TotalSubType: "DEPOSIT",
|
||||
// TotalTypeDesc: "Deposit",
|
||||
// TotalAmt: 0,
|
||||
// },
|
||||
{
|
||||
TotalType: "TOT",
|
||||
TotalSubType: "CUST",
|
||||
TotalTypeDesc: "Customer Pay",
|
||||
TotalAmt: Dinero(
|
||||
job.job_totals.totals.custPayable.total
|
||||
).toFormat("0.0"),
|
||||
},
|
||||
],
|
||||
RepairTotalsType: 1,
|
||||
},
|
||||
// RepairLabor: {
|
||||
// LaborAllocations: {
|
||||
// LaborAllocation: [
|
||||
// {
|
||||
// LaborAllocationUUID:
|
||||
// "426cce3a-efa7-44d9-b76e-50b9102c4198",
|
||||
// LaborType: "LAB",
|
||||
// Technician: {
|
||||
// Employee: {
|
||||
// PersonInfo: {
|
||||
// PersonName: {
|
||||
// FirstName: "Jose",
|
||||
// LastName: "Gonzalez",
|
||||
// },
|
||||
// IDInfo: {
|
||||
// IDQualifierCode: "US",
|
||||
// IDNum: 2987,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// AllocatedHours: 3.5,
|
||||
// },
|
||||
// {
|
||||
// LaborAllocationUUID:
|
||||
// "426cce3a-efa7-44d9-b76e-50b9102c4199",
|
||||
// LaborType: "LAR",
|
||||
// Technician: {
|
||||
// Employee: {
|
||||
// PersonInfo: {
|
||||
// PersonName: {
|
||||
// FirstName: "Rcardo",
|
||||
// LastName: "Himenez",
|
||||
// },
|
||||
// IDInfo: {
|
||||
// IDQualifierCode: "US",
|
||||
// IDNum: 2989,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// AllocatedHours: 5.5,
|
||||
// },
|
||||
// ],
|
||||
// },
|
||||
// },
|
||||
ProductionStatus: {
|
||||
ProductionStage: {
|
||||
ProductionStageCode: 4,
|
||||
ProductionStageDateTime: "2009-03-11T11:58:32.1898319-07:00",
|
||||
ProductionStageStatusComment:
|
||||
"Going to be painted this afternoon",
|
||||
},
|
||||
RepairStatus: {
|
||||
RepairStatusCode: 2,
|
||||
RepairStatusDateTime: "2009-03-11T11:58:32.1898319-07:00",
|
||||
RepairStatusMemo: "Waiting on back ordered parts",
|
||||
},
|
||||
},
|
||||
// RepairOrderNotes: {
|
||||
// RepairOrderNote: {
|
||||
// LineSequenceNum: 1,
|
||||
// Note: "Revision Requested : approved--but needs est separated.8/22/2008 11:58:53 AM",
|
||||
// CreateDateTime: "2008-08-22T11:58:53",
|
||||
// AuthoredBy: {
|
||||
// FirstName: {
|
||||
// "#text": "Elizabeth/FirstName>",
|
||||
// LastName: "Unis",
|
||||
// },
|
||||
// },
|
||||
// RepairOrderNote: {
|
||||
// LineSequenceNum: 2,
|
||||
// Note: "Approved : 8/26/2008 12:21:08 PM",
|
||||
// CreateDateTime: "2008-08-26T12:21:08",
|
||||
// AuthoredBy: {
|
||||
// FirstName: {
|
||||
// "#text": "Elizabeth/FirstName>",
|
||||
// LastName: "Unis",
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
if (erroredJobs.length > 0) {
|
||||
logger.log("arms-failed-jobs", "ERROR", "api", bodyshop.id, {
|
||||
count: erroredJobs.length,
|
||||
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)),
|
||||
});
|
||||
}
|
||||
|
||||
logger.log("arms-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
shopname: bodyshop.shopname,
|
||||
});
|
||||
res.json(ret);
|
||||
} catch (error) {
|
||||
//Error at the shop level.
|
||||
logger.log("arms-error-shop", "ERROR", "api", bodyshop.id, {
|
||||
error,
|
||||
});
|
||||
|
||||
allErrors.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
fatal: true,
|
||||
errors: [error.toString()],
|
||||
});
|
||||
} finally {
|
||||
allErrors.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
errors: erroredJobs,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
res.status(200).json(error);
|
||||
}
|
||||
};
|
||||
|
||||
function GetSupplementNumber(joblines) {
|
||||
return _.max(joblines.map((jl) => jl.line_ind));
|
||||
}
|
||||
|
||||
function GetDocumentstatus(job, bodyshop) {
|
||||
switch (job.status) {
|
||||
case bodyshop.md_ro_statuses.default_void:
|
||||
return "V";
|
||||
case bodyshop.md_ro_statuses.default_invoiced:
|
||||
case bodyshop.md_ro_statuses.default_exported:
|
||||
return "V";
|
||||
|
||||
default:
|
||||
return "0";
|
||||
}
|
||||
}
|
||||
@@ -1 +1,2 @@
|
||||
exports.autohouse = require("./autohouse").default;
|
||||
exports.arms = require("./arms").default;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user