Compare commits

..

1 Commits

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

Approved-by: Patrick Fic
2021-08-12 20:43:16 +00:00
93 changed files with 2106 additions and 49971 deletions

File diff suppressed because one or more lines are too long

View File

@@ -3575,95 +3575,6 @@
</concept_node>
</children>
</folder_node>
<folder_node>
<name>dms</name>
<children>
<concept_node>
<name>default_journal</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>dms_acctnumber</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>dms_wip_acctnumber</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>mappingname</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<concept_node>
<name>email</name>
<definition_loaded>false</definition_loaded>
@@ -7547,74 +7458,6 @@
</translation>
</translations>
</concept_node>
<folder_node>
<name>dms</name>
<children>
<concept_node>
<name>cdk_dealerid</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>title</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<concept_node>
<name>emaillater</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>employees</name>
<definition_loaded>false</definition_loaded>
@@ -7846,27 +7689,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>printlater</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>rbac</name>
<definition_loaded>false</definition_loaded>
@@ -12948,32 +12770,6 @@
</concept_node>
</children>
</folder_node>
<folder_node>
<name>labels</name>
<children>
<concept_node>
<name>attempts</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
@@ -17003,27 +16799,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>dmsautoallocate</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>export</name>
<definition_loaded>false</definition_loaded>
@@ -18609,137 +18384,6 @@
</translation>
</translations>
</concept_node>
<folder_node>
<name>dms</name>
<children>
<concept_node>
<name>center</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>cost</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>cost_dms_acctnumber</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>dms_wip_acctnumber</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>sale</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>sale_dms_acctnumber</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<concept_node>
<name>driveable</name>
<definition_loaded>false</definition_loaded>
@@ -35236,37 +34880,6 @@
</folder_node>
</children>
</folder_node>
<folder_node>
<name>scoredboard</name>
<children>
<folder_node>
<name>successes</name>
<children>
<concept_node>
<name>updated</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
</children>
</folder_node>
<folder_node>
<name>tech</name>
<children>
@@ -35838,27 +35451,6 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>flat_rate</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>memo</name>
<definition_loaded>false</definition_loaded>

View File

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

43861
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,10 +8,6 @@
"@craco/craco": "^6.2.0",
"@fingerprintjs/fingerprintjs": "^3.2.0",
"@lourenci/react-kanban": "^2.1.0",
"@openreplay/tracker": "^3.2.1",
"@openreplay/tracker-assist": "^3.0.3",
"@openreplay/tracker-graphql": "^3.0.0",
"@openreplay/tracker-redux": "^3.0.0",
"@sentry/react": "^6.10.0",
"@sentry/tracing": "^6.10.0",
"@stripe/react-stripe-js": "^1.4.0",
@@ -112,7 +108,6 @@
]
},
"devDependencies": {
"@sentry/webpack-plugin": "^1.17.1",
"redux-logger": "^3.0.6",
"source-map-explorer": "^2.5.2"
}

View File

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

View File

@@ -109,17 +109,6 @@ export default function AccountingPayablesTableComponent({ loading, bills }) {
<Checkbox disabled checked={record.is_credit_memo} />
),
},
{
title: t("exportlogs.labels.attempts"),
dataIndex: "attempts",
key: "attempts",
render: (text, record) => {
const success = record.exportlogs.filter((e) => e.successful).length;
const attempts = record.exportlogs.length;
return `${success}/${attempts}`;
},
},
{
title: t("general.labels.actions"),
dataIndex: "actions",

View File

@@ -108,17 +108,7 @@ export default function AccountingPayablesTableComponent({
<DateTimeFormatter>{record.exportedat}</DateTimeFormatter>
),
},
{
title: t("exportlogs.labels.attempts"),
dataIndex: "attempts",
key: "attempts",
render: (text, record) => {
const success = record.exportlogs.filter((e) => e.successful).length;
const attempts = record.exportlogs.length;
return `${success}/${attempts}`;
},
},
{
title: t("general.labels.actions"),
dataIndex: "actions",

View File

@@ -114,21 +114,11 @@ export default function AccountingReceivablesTableComponent({ loading, jobs }) {
);
},
},
{
title: t("exportlogs.labels.attempts"),
dataIndex: "attempts",
key: "attempts",
render: (text, record) => {
const success = record.exportlogs.filter((e) => e.successful).length;
const attempts = record.exportlogs.length;
return `${success}/${attempts}`;
},
},
{
title: t("general.labels.actions"),
dataIndex: "actions",
key: "actions",
sorter: (a, b) => a.clm_total - b.clm_total,
render: (text, record) => (
<Space wrap>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -97,24 +97,6 @@ export function JobsAvailableContainer({
});
return;
}
//IO-539 Check for Parts Rate on PAL for SGI use case.
if (
estData.est_data.parts_tax_rates &&
estData.est_data.parts_tax_rates.PAL &&
(estData.est_data.parts_tax_rates.PAL.prt_tax_rt === null ||
estData.est_data.parts_tax_rates.PAL.prt_tax_rt === 0)
) {
console.log("checking");
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for used parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}%.`
);
if (res) {
estData.est_data.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAL.prt_tax_in = true;
}
}
const newTotals = (
await Axios.post("/job/totals", {
@@ -214,24 +196,6 @@ export function JobsAvailableContainer({
message: t("jobs.errors.creating", { error: "No job data present." }),
});
} else {
//IO-539 Check for Parts Rate on PAL for SGI use case.
if (
estData.est_data.parts_tax_rates &&
estData.est_data.parts_tax_rates.PAL &&
(estData.est_data.parts_tax_rates.PAL.prt_tax_rt === null ||
estData.est_data.parts_tax_rates.PAL.prt_tax_rt === 0)
) {
console.log("checking");
const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for used parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}%.`
);
if (res) {
estData.est_data.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAL.prt_tax_in = true;
}
}
//create upsert job
let supp = replaceEmpty({ ...estData.est_data });
@@ -424,11 +388,3 @@ function replaceEmpty(someObj, replaceValue = null) {
console.log("Parsed", JSON.parse(temp));
return JSON.parse(temp);
}
function confirmDialog(msg) {
return new Promise(function (resolve, reject) {
let confirmed = window.confirm(msg);
return confirmed ? resolve(true) : reject(false);
});
}

View File

@@ -1,4 +1,4 @@
import { Button, Dropdown, Menu } from "antd";
import { Button } from "antd";
import _ from "lodash";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -12,8 +12,11 @@ const mapStateToProps = createStructuredSelector({
export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
const { t } = useTranslation();
const handleAllocate = () => {
logImEXEvent("jobs_close_allocate_auto");
const { defaults } = bodyshop.md_responsibility_centers;
const handleAllocate = (defaults) => {
form.setFieldsValue({
joblines: joblines.map((jl) => {
const ret = _.cloneDeep(jl);
@@ -45,36 +48,8 @@ export function JobsCloseAutoAllocate({ bodyshop, joblines, form, disabled }) {
});
};
const handleAutoAllocateClick = () => {
logImEXEvent("jobs_close_allocate_auto");
const { defaults } = bodyshop.md_responsibility_centers;
handleAllocate(defaults);
};
const handleMenuClick = ({ item, key, keyPath, domEvent }) => {
logImEXEvent("jobs_close_allocate_auto_dms");
handleAllocate(
bodyshop.md_responsibility_centers.dms_defaults.find(
(x) => x.name === key
)
);
};
const overlay = bodyshop.cdk_dealerid && (
<Menu onClick={handleMenuClick}>
{bodyshop.md_responsibility_centers.dms_defaults.map((mapping) => (
<Menu.Item key={mapping.name}>{mapping.name}</Menu.Item>
))}
</Menu>
);
return bodyshop.cdk_dealerid ? (
<Dropdown overlay={overlay}>
<Button disabled={disabled}>{t("jobs.actions.dmsautoallocate")}</Button>
</Dropdown>
) : (
<Button onClick={handleAutoAllocateClick} disabled={disabled}>
return (
<Button onClick={handleAllocate} disabled={disabled}>
{t("jobs.actions.autoallocate")}
</Button>
);

View File

@@ -150,7 +150,7 @@ export function JobsCloseExportButton({
}
if (setSelectedJobs) {
setSelectedJobs((selectedJobs) => {
return selectedJobs.filter((i) => i !== jobId);
return selectedJobs.filter((i) => i.id !== jobId);
});
}
}

View File

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

View File

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

View File

@@ -145,7 +145,7 @@ export function PayableExportButton({
}
if (setSelectedBills) {
setSelectedBills((selectedBills) => {
return selectedBills.filter((i) => i !== billId);
return selectedBills.filter((i) => i.id !== billId);
});
}
}

View File

@@ -145,7 +145,7 @@ export function PaymentExportButton({
if (setSelectedPayments) {
setSelectedPayments((selectedBills) => {
return selectedBills.filter((i) => i !== paymentId);
return selectedBills.filter((i) => i.id !== paymentId);
});
}
}

View File

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

View File

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

View File

@@ -165,20 +165,6 @@ export default function ShopInfoGeneral({ form }) {
);
}}
</Form.Item>
<Form.Item
label={t("bodyshop.labels.printlater")}
valuePropName="checked"
name={["accountingconfig", "printlater"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.labels.emaillater")}
valuePropName="checked"
name={["accountingconfig", "emaillater"]}
>
<Switch />
</Form.Item>
<Form.Item
label={t("bodyshop.fields.inhousevendorid")}
name={"inhousevendorid"}

View File

@@ -1,10 +1,9 @@
import { EditFilled } from "@ant-design/icons";
import { Card, Space, Table } from "antd";
import { EditFilled } from "@ant-design/icons";
import moment from "moment";
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import {
selectAuthLevel,
@@ -17,6 +16,8 @@ import RbacWrapper, {
HasRbacAccess,
} from "../rbac-wrapper/rbac-wrapper.component";
import TimeTicketEnterButton from "../time-ticket-enter-button/time-ticket-enter-button.component";
import { Link } from "react-router-dom";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
authLevel: selectAuthLevel,
@@ -267,12 +268,8 @@ export function TimeTicketList({
<Table.Summary.Cell />
<Table.Summary.Cell />
<Table.Summary.Cell />
<Table.Summary.Cell>
{totals.productivehrs.toFixed(1)}
</Table.Summary.Cell>
<Table.Summary.Cell>
{totals.actualhrs.toFixed(1)}
</Table.Summary.Cell>
<Table.Summary.Cell>{totals.productivehrs}</Table.Summary.Cell>
<Table.Summary.Cell>{totals.actualhrs}</Table.Summary.Cell>
<Table.Summary.Cell>
{totals.actualhrs === 0 || !totals.actualhrs
? "∞"

View File

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

View File

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

View File

@@ -93,7 +93,6 @@ export const QUERY_BODYSHOP = gql`
features
attach_pdf_to_email
tt_allow_post_to_invoiced
cdk_configuration
employees {
id
active
@@ -183,7 +182,6 @@ export const UPDATE_SHOP = gql`
cdk_dealerid
attach_pdf_to_email
tt_allow_post_to_invoiced
cdk_configuration
employees {
id
first_name

View File

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

View File

@@ -318,7 +318,6 @@ export const QUERY_JOB_COSTING_DETAILS = gql`
cost_center
actualhrs
productivehrs
flat_rate
}
}
}
@@ -1876,100 +1875,3 @@ export const FIND_JOBS_BY_CLAIM = gql`
}
}
`;
export const QUERY_JOB_EXPORT_DMS = gql`
query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
jobs_by_pk(id: $id) {
ro_number
invoice_allocation
ins_co_id
id
ded_amt
ded_status
depreciation_taxes
other_amount_payable
towing_payable
storage_payable
adjustment_bottom_line
federal_tax_rate
state_tax_rate
local_tax_rate
tax_tow_rt
tax_str_rt
tax_paint_mat_rt
tax_sub_rt
tax_lbr_rt
tax_levies_rt
parts_tax_rates
job_totals
rate_la1
rate_la2
rate_la3
rate_la4
rate_laa
rate_lab
rate_lad
rate_lae
rate_laf
rate_lag
rate_lam
rate_lar
rate_las
rate_lau
rate_ma2s
rate_ma2t
rate_ma3s
rate_mabl
rate_macs
rate_mahw
rate_mapa
rate_mash
rate_matd
status
date_exported
date_invoiced
voided
scheduled_completion
actual_completion
scheduled_delivery
actual_delivery
scheduled_in
actual_in
bills {
id
federal_tax_rate
local_tax_rate
state_tax_rate
is_credit_memo
billlines {
actual_cost
cost_center
id
quantity
}
}
joblines(where: { removed: { _eq: false } }) {
id
removed
tax_part
line_desc
prt_dsmk_p
prt_dsmk_m
part_type
oem_partno
db_price
act_price
part_qty
mod_lbr_ty
db_hrs
mod_lb_hrs
lbr_op
lbr_amt
op_code_desc
profitcenter_labor
profitcenter_part
prt_dsmk_p
}
}
}
`;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -29,7 +29,6 @@ import {
} from "./user.actions";
import UserActionTypes from "./user.types";
import * as Sentry from "@sentry/browser";
import { tracker } from "../../App/App.container";
export function* onEmailSignInStart() {
yield takeLatest(UserActionTypes.EMAIL_SIGN_IN_START, signInWithEmail);
@@ -69,7 +68,6 @@ export function* isUserAuthenticated() {
}
LogRocket.identify(user.email);
tracker.setUserID(user.email);
yield put(
signInSuccess({
uid: user.uid,
@@ -171,8 +169,6 @@ export function* onSignInSuccess() {
export function* signInSuccessSaga({ payload }) {
LogRocket.identify(payload.email);
tracker.setUserID(payload.email);
try {
// window.$crisp.push(["set", "user:email", [payload.email]]);
console.log("$crisp set nickname", [payload.displayName || payload.email]);

View File

@@ -231,12 +231,6 @@
"deliver": {
"templates": "Delivery Templates"
},
"dms": {
"default_journal": "Default Journal",
"dms_acctnumber": "DMS Account #",
"dms_wip_acctnumber": "DMS W.I.P. Account #",
"mappingname": "DMS Mapping Name"
},
"email": "General Shop Email",
"enforce_class": "Enforce Class on Conversion?",
"enforce_referral": "Enforce Referrals",
@@ -473,11 +467,6 @@
"defaultcostsmapping": "Default Costs Mapping",
"defaultprofitsmapping": "Default Profits Mapping",
"deliverchecklist": "Delivery Checklist",
"dms": {
"cdk_dealerid": "CDK Dealer ID",
"title": "DMS"
},
"emaillater": "Email Later",
"employees": "Employees",
"insurancecos": "Insurance Companies",
"intakechecklist": "Intake Checklist",
@@ -489,7 +478,6 @@
"notespresets": "Notes Presets",
"orderstatuses": "Order Statuses",
"partslocations": "Parts Locations",
"printlater": "Print Later",
"rbac": "Role Based Access Control",
"responsibilitycenters": {
"costs": "Cost Centers",
@@ -827,9 +815,6 @@
"exportlogs": {
"fields": {
"createdat": "Created At"
},
"labels": {
"attempts": "Export Attempts"
}
},
"general": {
@@ -1066,7 +1051,6 @@
"changestatus": "Change Status",
"convert": "Convert",
"deliver": "Deliver",
"dmsautoallocate": "DMS Auto Allocate",
"export": "Export",
"exportcustdata": "Export Customer Data",
"exportselected": "Export Selected",
@@ -1146,14 +1130,6 @@
"ded_amt": "Deductible",
"ded_status": "Deductible Status",
"depreciation_taxes": "Depreciation/Taxes",
"dms": {
"center": "Center",
"cost": "Cost",
"cost_dms_acctnumber": "Cost DMS Acct #",
"dms_wip_acctnumber": "Cost WIP DMS Acct #",
"sale": "Sale",
"sale_dms_acctnumber": "Sale DMS Acct #"
},
"driveable": "Driveable",
"employee_body": "Body",
"employee_csr": "Customer Service Rep.",
@@ -2114,11 +2090,6 @@
"updated": "Scoreboard updated."
}
},
"scoredboard": {
"successes": {
"updated": "Scoreboard entry updated."
}
},
"tech": {
"fields": {
"employeeid": "Employee ID",
@@ -2162,7 +2133,6 @@
"date": "Ticket Date",
"efficiency": "Efficiency",
"employee": "Employee",
"flat_rate": "Flat Rate?",
"memo": "Memo",
"productivehrs": "Productive Hours",
"ro_number": "Job to Post Against"

View File

@@ -231,12 +231,6 @@
"deliver": {
"templates": ""
},
"dms": {
"default_journal": "",
"dms_acctnumber": "",
"dms_wip_acctnumber": "",
"mappingname": ""
},
"email": "",
"enforce_class": "",
"enforce_referral": "",
@@ -473,11 +467,6 @@
"defaultcostsmapping": "",
"defaultprofitsmapping": "",
"deliverchecklist": "",
"dms": {
"cdk_dealerid": "",
"title": ""
},
"emaillater": "",
"employees": "",
"insurancecos": "",
"intakechecklist": "",
@@ -489,7 +478,6 @@
"notespresets": "",
"orderstatuses": "",
"partslocations": "",
"printlater": "",
"rbac": "",
"responsibilitycenters": {
"costs": "",
@@ -827,9 +815,6 @@
"exportlogs": {
"fields": {
"createdat": ""
},
"labels": {
"attempts": ""
}
},
"general": {
@@ -1066,7 +1051,6 @@
"changestatus": "Cambiar Estado",
"convert": "Convertir",
"deliver": "",
"dmsautoallocate": "",
"export": "",
"exportcustdata": "",
"exportselected": "",
@@ -1146,14 +1130,6 @@
"ded_amt": "Deducible",
"ded_status": "Estado deducible",
"depreciation_taxes": "Depreciación / Impuestos",
"dms": {
"center": "",
"cost": "",
"cost_dms_acctnumber": "",
"dms_wip_acctnumber": "",
"sale": "",
"sale_dms_acctnumber": ""
},
"driveable": "",
"employee_body": "",
"employee_csr": "Representante de servicio al cliente.",
@@ -2114,11 +2090,6 @@
"updated": ""
}
},
"scoredboard": {
"successes": {
"updated": ""
}
},
"tech": {
"fields": {
"employeeid": "",
@@ -2162,7 +2133,6 @@
"date": "",
"efficiency": "",
"employee": "",
"flat_rate": "",
"memo": "",
"productivehrs": "",
"ro_number": ""

View File

@@ -231,12 +231,6 @@
"deliver": {
"templates": ""
},
"dms": {
"default_journal": "",
"dms_acctnumber": "",
"dms_wip_acctnumber": "",
"mappingname": ""
},
"email": "",
"enforce_class": "",
"enforce_referral": "",
@@ -473,11 +467,6 @@
"defaultcostsmapping": "",
"defaultprofitsmapping": "",
"deliverchecklist": "",
"dms": {
"cdk_dealerid": "",
"title": ""
},
"emaillater": "",
"employees": "",
"insurancecos": "",
"intakechecklist": "",
@@ -489,7 +478,6 @@
"notespresets": "",
"orderstatuses": "",
"partslocations": "",
"printlater": "",
"rbac": "",
"responsibilitycenters": {
"costs": "",
@@ -827,9 +815,6 @@
"exportlogs": {
"fields": {
"createdat": ""
},
"labels": {
"attempts": ""
}
},
"general": {
@@ -1066,7 +1051,6 @@
"changestatus": "Changer le statut",
"convert": "Convertir",
"deliver": "",
"dmsautoallocate": "",
"export": "",
"exportcustdata": "",
"exportselected": "",
@@ -1146,14 +1130,6 @@
"ded_amt": "Déductible",
"ded_status": "Statut de franchise",
"depreciation_taxes": "Amortissement / taxes",
"dms": {
"center": "",
"cost": "",
"cost_dms_acctnumber": "",
"dms_wip_acctnumber": "",
"sale": "",
"sale_dms_acctnumber": ""
},
"driveable": "",
"employee_body": "",
"employee_csr": "représentant du service à la clientèle",
@@ -2114,11 +2090,6 @@
"updated": ""
}
},
"scoredboard": {
"successes": {
"updated": ""
}
},
"tech": {
"fields": {
"employeeid": "",
@@ -2162,7 +2133,6 @@
"date": "",
"efficiency": "",
"employee": "",
"flat_rate": "",
"memo": "",
"productivehrs": "",
"ro_number": ""

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,5 +0,0 @@
- args:
cascade: false
read_only: false
sql: ALTER TABLE "public"."bodyshops" ADD COLUMN "cdk_configuration" jsonb NULL;
type: run_sql

View File

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

View File

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

View File

@@ -1,81 +0,0 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_update_permission
- args:
permission:
columns:
- accountingconfig
- address1
- address2
- appt_alt_transport
- appt_colors
- appt_length
- attach_pdf_to_email
- bill_tax_rates
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- enforce_referral
- federal_tax_id
- id
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- jc_hourly_rates
- logo_img_path
- md_categories
- md_ccc_rates
- md_classes
- md_hour_split
- md_ins_cos
- md_jobline_presets
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- md_rbac
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- phone
- prodtargethrs
- production_config
- schedule_end_time
- schedule_start_time
- scoreboard_target
- shopname
- shoprates
- speedprint
- ssbuckets
- state
- state_tax_id
- target_touchtime
- tt_allow_post_to_invoiced
- updated_at
- use_fippa
- website
- workingdays
- zip_post
filter:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: bodyshops
schema: public
type: create_update_permission

View File

@@ -1,82 +0,0 @@
- args:
role: user
table:
name: bodyshops
schema: public
type: drop_update_permission
- args:
permission:
columns:
- accountingconfig
- address1
- address2
- appt_alt_transport
- appt_colors
- appt_length
- attach_pdf_to_email
- bill_tax_rates
- cdk_configuration
- city
- country
- created_at
- default_adjustment_rate
- deliverchecklist
- email
- enforce_class
- enforce_referral
- federal_tax_id
- id
- inhousevendorid
- insurance_vendor_id
- intakechecklist
- jc_hourly_rates
- logo_img_path
- md_categories
- md_ccc_rates
- md_classes
- md_hour_split
- md_ins_cos
- md_jobline_presets
- md_labor_rates
- md_messaging_presets
- md_notes_presets
- md_order_statuses
- md_parts_locations
- md_payment_types
- md_rbac
- md_referral_sources
- md_responsibility_centers
- md_ro_statuses
- phone
- prodtargethrs
- production_config
- schedule_end_time
- schedule_start_time
- scoreboard_target
- shopname
- shoprates
- speedprint
- ssbuckets
- state
- state_tax_id
- target_touchtime
- tt_allow_post_to_invoiced
- updated_at
- use_fippa
- website
- workingdays
- zip_post
filter:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: bodyshops
schema: public
type: create_update_permission

View File

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

View File

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

View File

@@ -1,39 +0,0 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- memo
- productivehrs
- rate
- updated_at
set: {}
role: user
table:
name: timetickets
schema: public
type: create_insert_permission

View File

@@ -1,40 +0,0 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_insert_permission
- args:
permission:
check:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- flat_rate
- id
- jobid
- memo
- productivehrs
- rate
- updated_at
set: {}
role: user
table:
name: timetickets
schema: public
type: create_insert_permission

View File

@@ -1,40 +0,0 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- memo
- productivehrs
- rate
- updated_at
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: timetickets
schema: public
type: create_select_permission

View File

@@ -1,41 +0,0 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_select_permission
- args:
permission:
allow_aggregations: false
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- flat_rate
- id
- jobid
- memo
- productivehrs
- rate
- updated_at
computed_fields: []
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
role: user
table:
name: timetickets
schema: public
type: create_select_permission

View File

@@ -1,39 +0,0 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_update_permission
- args:
permission:
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- id
- jobid
- memo
- productivehrs
- rate
- updated_at
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: timetickets
schema: public
type: create_update_permission

View File

@@ -1,40 +0,0 @@
- args:
role: user
table:
name: timetickets
schema: public
type: drop_update_permission
- args:
permission:
columns:
- actualhrs
- bodyshopid
- ciecacode
- clockoff
- clockon
- cost_center
- created_at
- date
- employeeid
- flat_rate
- id
- jobid
- memo
- productivehrs
- rate
- updated_at
filter:
bodyshop:
associations:
_and:
- user:
authid:
_eq: X-Hasura-User-Id
- active:
_eq: true
set: {}
role: user
table:
name: timetickets
schema: public
type: create_update_permission

View File

@@ -796,7 +796,6 @@ tables:
- appt_length
- attach_pdf_to_email
- bill_tax_rates
- cdk_configuration
- cdk_dealerid
- city
- country
@@ -874,7 +873,6 @@ tables:
- appt_length
- attach_pdf_to_email
- bill_tax_rates
- cdk_configuration
- city
- country
- created_at
@@ -4097,7 +4095,6 @@ tables:
- created_at
- date
- employeeid
- flat_rate
- id
- jobid
- memo
@@ -4117,7 +4114,6 @@ tables:
- created_at
- date
- employeeid
- flat_rate
- id
- jobid
- memo
@@ -4146,7 +4142,6 @@ tables:
- created_at
- date
- employeeid
- flat_rate
- id
- jobid
- memo

View File

@@ -29,7 +29,6 @@
"firebase-admin": "^9.11.0",
"graphql": "^15.5.1",
"graphql-request": "^3.4.0",
"graylog2": "^0.2.1",
"inline-css": "^3.0.0",
"intuit-oauth": "^4.0.0",
"lodash": "^4.17.21",

View File

@@ -4,7 +4,6 @@ const bodyParser = require("body-parser");
const path = require("path");
const compression = require("compression");
const twilio = require("twilio");
const logger = require("./server/utils/logger");
global.fetch = require("node-fetch");
var fb = require("./server/firebase/firebase-handler");
@@ -34,13 +33,23 @@ app.post("/sendemail", fb.validateFirebaseIdToken, sendEmail.sendEmail);
//Test route to ensure Express is responding.
app.get("/test", async function (req, res) {
const commit = require("child_process").execSync(
console.log("Incoming request verified.", req);
const Commit = require("child_process").execSync(
"git rev-parse --short HEAD"
);
logger.log("test-api-status", "DEBUG", "api", { commit });
res.status(200).send(`OK - ${commit}`);
res.status(200).send(`OK - ${Commit}`);
});
const test = require("./server/_test/test.js");
app.post("/test", test.testResponse);
//Accounting-IIF
const accountingIIF = require("./server/accounting/iif/iif");
app.post(
"/accounting/iif/receivables",
fb.validateFirebaseIdToken,
accountingIIF.receivables
);
//Accounting Qbxml
const accountQbxml = require("./server/accounting/qbxml/qbxml");
@@ -98,8 +107,8 @@ var scheduling = require("./server/scheduling/scheduling-job");
app.post("/scheduling/job", fb.validateFirebaseIdToken, scheduling.job);
//Handlebars Paths for Email/Report Rendering
// var renderHandlebars = require("./server/render/renderHandlebars");
// app.post("/render", fb.validateFirebaseIdToken, renderHandlebars.render);
var renderHandlebars = require("./server/render/renderHandlebars");
app.post("/render", fb.validateFirebaseIdToken, renderHandlebars.render);
var inlineCss = require("./server/render/inlinecss");
app.post("/render/inlinecss", fb.validateFirebaseIdToken, inlineCss.inlinecss);
@@ -157,11 +166,7 @@ const io = new Server(server, {
server.listen(port, (error) => {
if (error) throw error;
logger.log(
`[${process.env.NODE_ENV || "DEVELOPMENT"}] Server running on port ${port}`,
"DEBUG",
"api"
);
console.log(`[${process.env.NODE_ENV}] Server running on port ${port}`);
});
exports.io = io;
require("./server/web-sockets/web-socket");

74
server/_test/test.js Normal file
View File

@@ -0,0 +1,74 @@
const path = require("path");
const admin = require("../firebase/firebase-handler").admin;
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
exports.testResponse = async (req, res) => {
console.log("Test");
const uniqueTokens = [
"f7B-k-ceDNCEAIFYCfhF3M:APA91bEn-xOmUahCBMJBBDqXpVOZJnnb_qhWlo8eOPrIkvFeSc2nqaKd4D8zs3qqZ_VNgS_OhifsetJXcwtczO8N4k3xfDzCyI3i6j6YTUNK56QC-WNmVOLR2C_g-owy7hSvhGhWilZ3",
"eNdzsUqRBBZCM8LQKvqk6e:APA91bFgL0VQLf_TooYmHKQ7_b4H--ZmUYCdgiZpT7dxHSyEkpcCHUz33K7sKqgifUk8rMAEhSsHWa0TJgLbOJxWD6lJaGEpXn8G3PbunkJsJCNCA3CprMONylBr9d6hnQ5wnjUX2Gt6",
];
var message = {
notification: {
title: "Test Notification",
body: "Test Body",
//click_action: "TEST CLICK ACTION",
},
data: {
jobid: "1234",
title: "Test Notification",
body: "Test Body",
},
tokens: uniqueTokens,
// android: {
// notification: {
// body: "This is an FCM notification specifically for android.",
// title: "FCM Notification for Android",
// image: "/logo192.png",
// badge: "/logo192.png",
// },
// },
webpush: {
headers: {
// Urgency: "high",
},
notification: {
body: "This is a message from FCM to web",
requireInteraction: "true",
actions: [{ action: "the action - matched in sw", title: "Read" }],
// renotify: true,
//tag: "1234", image: "/logo192.png",
badge: "/logo192.png",
//badge: "/badge-icon.png",
},
},
};
// Send a message to the device corresponding to the provided
// registration token.
admin
.messaging()
.sendMulticast(message)
.then((response) => {
// Response is a message ID string.
console.log(
"[TEST] Successfully sent FCM Broadcast.:",
response
//JSON.stringify(response)
);
})
.catch((error) => {
console.log("Error sending message:", error);
});
res.status(200).send("OK");
};

View File

@@ -0,0 +1,167 @@
const GraphQLClient = require("graphql-request").GraphQLClient;
const DineroQbFormat = require("../accounting-constants").DineroQbFormat;
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const queries = require("../../graphql-client/queries");
const Dinero = require("dinero.js");
exports.default = async (req, res) => {
const BearerToken = req.headers.authorization;
const { jobId } = req.body;
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
Authorization: BearerToken,
},
});
try {
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { id: jobId });
const { jobs_by_pk } = result;
const { bodyshop } = jobs_by_pk;
//Build the IIF file.
const response = [];
response.push(TRNS_HEADER);
response.push(
generateInvoiceHeader(jobs_by_pk, bodyshop.md_responsibility_centers.ar)
);
//Allocations
const invoice_allocation = jobs_by_pk.invoice_allocation;
Object.keys(invoice_allocation.partsAllocations).forEach(
(partsAllocationKey) => {
if (
!!!invoice_allocation.partsAllocations[partsAllocationKey].allocations
)
return;
invoice_allocation.partsAllocations[
partsAllocationKey
].allocations.forEach((alloc) => {
response.push(
generateInvoiceLine(
jobs_by_pk,
alloc,
bodyshop.md_responsibility_centers
)
);
});
}
);
Object.keys(invoice_allocation.labMatAllocations).forEach(
(AllocationKey) => {
if (!!!invoice_allocation.labMatAllocations[AllocationKey].allocations)
return;
invoice_allocation.labMatAllocations[AllocationKey].allocations.forEach(
(alloc) => {
response.push(
generateInvoiceLine(
jobs_by_pk,
alloc,
bodyshop.md_responsibility_centers
)
);
}
);
}
);
//End Allocations
//Taxes
const taxMapping = bodyshop.md_responsibility_centers.taxes;
const { federal_tax, state_tax, local_tax } = JSON.parse(
jobs_by_pk.job_totals
).totals;
const federal_tax_dinero = Dinero(federal_tax);
const state_tax_dinero = Dinero(state_tax);
const local_tax_dinero = Dinero(local_tax);
if (federal_tax_dinero.getAmount() > 0) {
response.push(
generateTaxLine(jobs_by_pk, federal_tax_dinero, "federal", taxMapping)
);
}
if (state_tax_dinero.getAmount() > 0) {
response.push(
generateTaxLine(jobs_by_pk, state_tax_dinero, "state", taxMapping)
);
}
if (local_tax_dinero.getAmount() > 0) {
response.push(
generateTaxLine(jobs_by_pk, local_tax_dinero, "local", taxMapping)
);
}
//End Taxes
response.push(END_TRNS);
//Prep the response and send it.
res.setHeader("Content-type", "application/octet-stream");
res.setHeader("Content-disposition", "attachment; filename=file.txt");
res.setHeader("filename", `${jobs_by_pk.ro_number}-RECEIVABLES.iif`);
res.send(response.join("\n"));
} catch (error) {
console.log("error", error);
res.status(400).send(JSON.stringify(error));
}
};
const TRNS_HEADER = `!TRNS TRNSID TRNSTYPE DATE ACCNT NAME CLASS AMOUNT DOCNUM MEMO CLEAR TOPRINT NAMEISTAXABLE ADDR1 ADDR2 ADDR3 ADDR4 DUEDATE TERMS OTHER1 PONUM
!SPL SPLID TRNSTYPE DATE ACCNT NAME CLASS AMOUNT DOCNUM MEMO CLEAR QNTY PRICE INVITEM PAYMETH TAXABLE VALADJ SERVICEDATE OTHER2 EXTRA
!ENDTRNS`;
const generateInvoiceHeader = (job, arMapping) =>
`TRNS INVOICE ${generateJobInvoiceDate(job)} ${arMapping.name} GUO DA Acct.# ${
job.ownerid
}:${job.ro_number} 0100 ${job.clm_total} ${job.ro_number} N N Y GUO DA Acct.# ${
job.ownr_id
}:${job.ro_number} ${job.ownr_addr1} ${job.ownr_city} ${job.ownr_st} ${
job.ownr_zip
} `;
const generateInvoiceLine = (job, allocation, responsibilityCenters) => {
const { amount, center } = allocation;
const DineroAmount = Dinero(amount);
const account = responsibilityCenters.profits.find(
(i) => i.name.toLowerCase() === center.toLowerCase()
);
if (!!!account) {
throw new Error(
`A matching account does not exist for the allocation. Center: ${center}`
);
}
return `SPL INVOICE ${generateJobInvoiceDate(job)} ${
account.accountname
} 0100 ${DineroAmount.multiply(-1).toFormat(DineroQbFormat)} ${job.ro_number} ${
account.accountdesc
} N ${DineroAmount.toFormat(DineroQbFormat)} ${account.accountitem} Y N `;
};
const generateTaxLine = (job, amount, type, taxMapping) => {
return `SPL INVOICE ${generateJobInvoiceDate(job)} ${
taxMapping[type].accountname
} ${taxMapping[type].accountdesc} 0100 ${amount
.multiply(-1)
.toFormat(DineroQbFormat)} ${job.ro_number} N ${taxMapping[type].rate.toFixed(
2
)}% ${taxMapping[type].accountitem} N N AUTOSTAX `;
};
const END_TRNS = `ENDTRNS`;
const generateJobInvoiceDate = (job) => {
return `${new Date(job.date_invoiced).getMonth() + 1}/${new Date(
job.date_invoiced
).getDate()}/${new Date(job.date_invoiced).getFullYear()}`;
};

View File

@@ -0,0 +1 @@
exports.receivables = require("./iif-receivables").default

View File

@@ -6,7 +6,7 @@ const Dinero = require("dinero.js");
var builder = require("xmlbuilder2");
const QbXmlUtils = require("./qbxml-utils");
const moment = require("moment");
const logger = require("../../utils/logger");
require("dotenv").config({
path: path.resolve(
process.cwd(),
@@ -25,13 +25,6 @@ exports.default = async (req, res) => {
});
try {
logger.log(
"qbxml-payable-create",
"DEBUG",
req.user.email,
req.body.billsToQuery
);
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QUERY_BILLS_FOR_PAYABLES_EXPORT, {
@@ -49,15 +42,10 @@ exports.default = async (req, res) => {
});
//For each invoice.
res.status(200).json(QbXmlToExecute);
} catch (error) {
logger.log(
"qbxml-payable-error",
"ERROR",
req.user.email,
req.body.billsToQuery,
{ error }
);
console.log("error", error);
res.status(400).send(JSON.stringify(error));
}
};
@@ -101,6 +89,7 @@ const generateBill = (bill) => {
.end({ pretty: true });
const billQbxml_Full = QbXmlUtils.addQbxmlHeader(billQbxml_partial);
console.log("generateBill -> billQbxml_Full", billQbxml_Full);
return billQbxml_Full;
};
@@ -124,6 +113,13 @@ const generateBillLine = (billLine, responsibilityCenters, jobClass) => {
};
};
// [
// {
// AccountRef: { FullName: "BODY SHOP COST:SUBLET" },
// Amount: invoice.amount,
// },
// ],
const findTaxCode = (billLine, taxcode) => {
const {
applicable_taxes: { local, state, federal },
@@ -135,6 +131,7 @@ const findTaxCode = (billLine, taxcode) => {
!!t.federal === !!federal
);
if (t.length === 1) {
console.log(t);
return t[0].code;
} else if (t.length > 1) {
return "Multiple Tax Codes Match";

View File

@@ -7,8 +7,6 @@ var builder = require("xmlbuilder2");
const moment = require("moment");
const QbXmlUtils = require("./qbxml-utils");
const QbxmlReceivables = require("./qbxml-receivables");
const logger = require("../../utils/logger");
require("dotenv").config({
path: path.resolve(
process.cwd(),
@@ -29,14 +27,6 @@ exports.default = async (req, res) => {
});
try {
logger.log(
"qbxml-payments-create",
"DEBUG",
req.user.email,
req.body.paymentsToQuery,
null
);
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
@@ -90,18 +80,13 @@ exports.default = async (req, res) => {
res.status(200).json(QbXmlToExecute);
} catch (error) {
logger.log(
"qbxml-payments-error",
"error",
req.user.email,
req.body.paymentsToQuery,
error
);
console.log("error", error);
res.status(400).send(JSON.stringify(error));
}
};
const generatePayment = (payment, isThreeTier, twoTierPref) => {
console.log("generatePayment -> payment", payment);
let paymentQbxmlObj;
if (payment.amount > 0) {
paymentQbxmlObj = {
@@ -209,6 +194,7 @@ const generatePayment = (payment, isThreeTier, twoTierPref) => {
.end({ pretty: true });
const paymentQbxmlFull = QbXmlUtils.addQbxmlHeader(paymentQbxmlPartial);
console.log("generateBill -> paymentQbxmlFull", paymentQbxmlFull);
return paymentQbxmlFull;
};

View File

@@ -6,8 +6,6 @@ const Dinero = require("dinero.js");
const moment = require("moment");
var builder = require("xmlbuilder2");
const QbXmlUtils = require("./qbxml-utils");
const logger = require("../../utils/logger");
require("dotenv").config({
path: path.resolve(
process.cwd(),
@@ -29,14 +27,6 @@ exports.default = async (req, res) => {
});
try {
logger.log(
"qbxml-receivables-create",
"DEBUG",
req.user.email,
req.body.jobIds,
null
);
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QUERY_JOBS_FOR_RECEIVABLES_EXPORT, { ids: jobIds });
@@ -103,13 +93,7 @@ exports.default = async (req, res) => {
res.status(200).json(QbXmlToExecute);
} catch (error) {
logger.log(
"qbxml-receivables-error",
"error",
req.user.email,
req.body.jobIds,
error
);
console.log("error", error);
res.status(400).send(JSON.stringify(error));
}
};
@@ -122,13 +106,13 @@ const generateSourceCustomerQbxml = (jobs_by_pk, bodyshop) => {
CustomerAddRq: {
CustomerAdd: {
Name: jobs_by_pk.ins_co_nm,
// BillAddress: {
// Addr1: jobs_by_pk.ownr_addr1,
// Addr2: jobs_by_pk.ownr_addr2,
// City: jobs_by_pk.ownr_city,
// State: jobs_by_pk.ownr_st,
// PostalCode: jobs_by_pk.ownr_zip,
// },
BillAddress: {
Addr1: jobs_by_pk.ownr_addr1,
Addr2: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownrzip,
},
},
},
},
@@ -181,25 +165,6 @@ const generateJobQbxml = (
FullName: ParentRefName,
}
: null,
...(tierLevel === 3
? {
BillAddress: {
Addr1: jobs_by_pk.ownr_addr1,
Addr2: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownr_zip,
},
ShipAddress: {
Addr1: jobs_by_pk.ownr_addr1,
Addr2: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownr_zip,
},
Email: jobs_by_pk.ownr_ea,
}
: {}),
},
},
},
@@ -242,7 +207,7 @@ const generateInvoiceQbxml = (
jobs_by_pk.joblines.map((jobline) => {
//Parts Lines
if (jobline.db_ref === "936008") {
//If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
//If either of these DB REFs change, they also need to change in job-totals calculations.
hasMapaLine = true;
}
if (jobline.db_ref === "936007") {
@@ -255,7 +220,7 @@ const generateInvoiceQbxml = (
}).multiply(jobline.part_qty || 1);
if (jobline.prt_dsmk_p && jobline.prt_dsmk_p !== 0) {
// console.log("Have a part discount", jobline);
console.log("Have a part discount", jobline);
DineroAmount = DineroAmount.add(
DineroAmount.percentage(jobline.prt_dsmk_p || 0)
);
@@ -265,13 +230,6 @@ const generateInvoiceQbxml = (
);
if (!account) {
logger.log(
"qbxml-receivables-no-account",
"warn",
null,
jobline.id,
null
);
throw new Error(
`A matching account does not exist for the part allocation. Center: ${jobline.profitcenter_part}`
);
@@ -331,7 +289,7 @@ const generateInvoiceQbxml = (
// console.log("Done creating hash", JSON.stringify(invoiceLineHash));
if (!hasMapaLine && jobs_by_pk.job_totals.rates.mapa.total.amount > 0) {
// console.log("Adding MAPA Line Manually.");
console.log("Adding MAPA Line Manually.");
const mapaAccountName = responsibilityCenters.defaults.profits.MAPA;
const mapaAccount = responsibilityCenters.profits.find(
@@ -351,12 +309,12 @@ const generateInvoiceQbxml = (
},
});
} else {
//console.log("NO MAPA ACCOUNT FOUND!!");
console.log("NO MAPA ACCOUNT FOUND!!");
}
}
if (!hasMashLine && jobs_by_pk.job_totals.rates.mash.total.amount > 0) {
// console.log("Adding MASH Line Manually.");
console.log("Adding MASH Line Manually.");
const mashAccountName = responsibilityCenters.defaults.profits.MASH;
@@ -377,7 +335,7 @@ const generateInvoiceQbxml = (
},
});
} else {
// console.log("NO MASH ACCOUNT FOUND!!");
console.log("NO MASH ACCOUNT FOUND!!");
}
}
@@ -465,18 +423,6 @@ const generateInvoiceQbxml = (
TxnDate: moment(jobs_by_pk.date_invoiced).format("YYYY-MM-DD"),
RefNumber: jobs_by_pk.ro_number,
BillAddress: {
Addr1: jobs_by_pk.ownr_co_nm
? jobs_by_pk.ownr_co_nm.substring(0, 30)
: `${`${jobs_by_pk.ownr_ln || ""} ${
jobs_by_pk.ownr_fn || ""
}`.substring(0, 30)}`,
Addr2: jobs_by_pk.ownr_addr1,
Addr3: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownr_zip,
},
ShipAddress: {
Addr1: jobs_by_pk.ownr_co_nm
? jobs_by_pk.ownr_co_nm.substring(0, 30)
@@ -487,14 +433,9 @@ const generateInvoiceQbxml = (
Addr3: jobs_by_pk.ownr_addr2,
City: jobs_by_pk.ownr_city,
State: jobs_by_pk.ownr_st,
PostalCode: jobs_by_pk.ownr_zip,
PostalCode: jobs_by_pk.ownrzip,
},
PONumber: jobs_by_pk.clm_no,
IsToBePrinted: bodyshop.accountingconfig.printlater,
...(jobs_by_pk.ownr_ea
? { IsToBeEmailed: bodyshop.accountingconfig.emaillater }
: {}),
InvoiceLineAdd: InvoiceLineAdd,
},
},

View File

@@ -1,167 +0,0 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const GraphQLClient = require("graphql-request").GraphQLClient;
const queries = require("../graphql-client/queries");
const CdkBase = require("../web-sockets/web-socket");
const Dinero = require("dinero.js");
const _ = require("lodash");
exports.default = async function (socket, jobid) {
try {
CdkBase.createLogEvent(
socket,
"DEBUG",
`Received request to calculate allocations for ${jobid}`
);
const job = await QueryJobData(socket, jobid);
const { bodyshop } = job;
const taxAllocations = {
local: {
center: bodyshop.md_responsibility_centers.taxes.local.name,
sale: Dinero(job.job_totals.totals.local_tax),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes.local,
costCenter: bodyshop.md_responsibility_centers.taxes.local,
},
state: {
center: bodyshop.md_responsibility_centers.taxes.state.name,
sale: Dinero(job.job_totals.totals.state_tax),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes.state,
costCenter: bodyshop.md_responsibility_centers.taxes.state,
},
federal: {
center: bodyshop.md_responsibility_centers.taxes.federal.name,
sale: Dinero(job.job_totals.totals.federal_tax),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes.federal,
costCenter: bodyshop.md_responsibility_centers.taxes.federal,
},
};
const profitCenterHash = job.joblines.reduce((acc, val) => {
//Check the Parts Assignment
if (val.profitcenter_part) {
if (!acc[val.profitcenter_part]) acc[val.profitcenter_part] = Dinero();
acc[val.profitcenter_part] = acc[val.profitcenter_part].add(
Dinero({
amount: Math.round((val.act_price || 0) * 100),
}).multiply(val.part_qty || 0)
);
}
if (val.profitcenter_labor) {
//Check the Labor Assignment.
if (!acc[val.profitcenter_labor])
acc[val.profitcenter_labor] = Dinero();
acc[val.profitcenter_labor] = acc[val.profitcenter_labor].add(
Dinero({
amount: Math.round(
job[`rate_${val.mod_lbr_ty.toLowerCase()}`] * 100
),
}).multiply(val.mod_lb_hrs)
);
}
return acc;
}, {});
const costCenterHash = job.bills.reduce((bill_acc, bill_val) => {
bill_val.billlines.map((line_val) => {
if (!bill_acc[line_val.cost_center])
bill_acc[line_val.cost_center] = Dinero();
const lineDinero = Dinero({
amount: Math.round((line_val.actual_cost || 0) * 100),
})
.multiply(line_val.quantity)
.multiply(bill_val.is_credit_memo ? -1 : 1);
bill_acc[line_val.cost_center] =
bill_acc[line_val.cost_center].add(lineDinero);
//Add appropriate tax amounts.
const {
applicable_taxes: { local, state, federal },
} = line_val;
if (local) {
taxAllocations.local.cost = taxAllocations.local.cost.add(
lineDinero.percentage(bill_val.local_tax_rate || 0)
);
}
if (state) {
taxAllocations.state.cost = taxAllocations.state.cost.add(
lineDinero.percentage(bill_val.state_tax_rate || 0)
);
}
if (federal) {
taxAllocations.federal.cost = taxAllocations.federal.cost.add(
lineDinero.percentage(bill_val.federal_tax_rate || 0)
);
}
return null;
});
return bill_acc;
}, {});
const jobAllocations = _.union(
Object.keys(profitCenterHash),
Object.keys(costCenterHash)
).map((key) => {
const profitCenter = bodyshop.md_responsibility_centers.profits.find(
(c) => c.name === key
);
const costCenter = bodyshop.md_responsibility_centers.costs.find(
(c) => c.name === key
);
return {
center: key,
sale: profitCenterHash[key] ? profitCenterHash[key] : Dinero(),
cost: costCenterHash[key] ? costCenterHash[key] : Dinero(),
profitCenter,
costCenter,
};
});
return [
...jobAllocations,
...Object.keys(taxAllocations)
.filter(
(key) =>
taxAllocations[key].sale.getAmount() > 0 ||
taxAllocations[key].cost.getAmount() > 0
)
.map((key) => taxAllocations[key]),
];
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error encountered in CdkCalculateAllocations. ${error}`
);
}
};
async function QueryJobData(socket, jobid) {
CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
CdkBase.createLogEvent(
socket,
"TRACE",
`Job data query result ${JSON.stringify(result, null, 2)}`
);
return result.jobs_by_pk;
}

View File

@@ -1,78 +0,0 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const GraphQLClient = require("graphql-request").GraphQLClient;
const soap = require("soap");
const queries = require("../graphql-client/queries");
const CdkBase = require("../web-sockets/web-socket");
const CdkWsdl = require("./cdk-wsdl").default;
const logger = require("../utils/logger");
const Dinero = require("dinero.js");
const _ = require("lodash");
const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
const { performance } = require("perf_hooks");
exports.default = async function (socket, cdk_dealerid) {
try {
CdkBase.createLogEvent(
socket,
"DEBUG",
`Getting makes and models list from CDK.`
);
return await GetCdkMakes(socket, cdk_dealerid);
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error encountered in CdkGetMakes. ${error}`
);
}
};
async function GetCdkMakes(socket, cdk_dealerid) {
CdkBase.createLogEvent(socket, "TRACE", `{1} Begin GetCDkMakes WSDL Call`);
try {
const soapClientVehicleInsert = await soap.createClientAsync(
CdkWsdl.VehicleInsert
);
const start = performance.now();
const soapResponseVehicleSearch =
await soapClientVehicleInsert.getMakeModelAsync(
{
arg0: CDK_CREDENTIALS,
arg1: { id: cdk_dealerid },
},
{}
);
CheckCdkResponseForError(socket, soapResponseVehicleSearch);
const [
result, //rawResponse, soapheader, rawRequest
] = soapResponseVehicleSearch;
const end = performance.now();
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientVehicleInsert.getMakeModelAsync Result Length ${
result.return.length
} and took ${end - start}ms`
);
return result.return.map((element, index) => {
return { id: index, ...element };
});
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in GetCdkMakes - ${JSON.stringify(error, null, 2)}`
);
throw new Error(error);
}
}

View File

@@ -10,167 +10,40 @@ const soap = require("soap");
const queries = require("../graphql-client/queries");
const CdkBase = require("../web-sockets/web-socket");
const CdkWsdl = require("./cdk-wsdl").default;
const logger = require("../utils/logger");
const { CDK_CREDENTIALS, CheckCdkResponseForError } = require("./cdk-wsdl");
const IMEX_CDK_USER = process.env.IMEX_CDK_USER,
IMEX_CDK_PASSWORD = process.env.IMEX_CDK_PASSWORD;
exports.default = async function (socket, jobid) {
socket.logEvents = [];
socket.recordid = jobid;
try {
CdkBase.createLogEvent(
socket,
"DEBUG",
`Received Job export request for id ${jobid}`
);
//The following values will be stored on the socket to allow callbacks.
//let clVFV, clADPV, clADPC;
const JobData = await QueryJobData(socket, jobid);
console.log(JSON.stringify(JobData, null, 2));
const DealerId = JobData.bodyshop.cdk_dealerid;
CdkBase.createLogEvent(
socket,
"TRACE",
`Dealer ID detected: ${JSON.stringify(DealerId)}`
);
//{1} Begin Calculate DMS Vehicle Id
socket.clVFV = await CalculateDmsVid(socket, JobData);
if (socket.clVFV.newId === "Y") {
//{1.2} This is a new Vehicle ID
CdkBase.createLogEvent(
socket,
"DEBUG",
`{1.2} clVFV DMSVid does *not* exist.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`{1.2} clVFV: ${JSON.stringify(socket.clVFV, null, 2)}`
);
//Check if DMSCustId is Empty - which it should always be?
//{6.6} Should check to see if a customer exists so that we can marry it to the new vehicle.
CdkBase.createLogEvent(
socket,
"DEBUG",
`{6.6} Trying to find customer ID in DMS.`
);
//Array
const strIDS = await FindCustomerIdFromDms(socket, JobData);
if (strIDS && strIDS.length > 0) {
CdkBase.createLogEvent(
socket,
"DEBUG",
`{8.2} ${strIDS.length} Customer ID(s) found.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`{8.2} strIDS: ${JSON.stringify(strIDS, null, 2)}`
);
if (strIDS.length > 1) {
//We have multiple IDs
//TODO: Do we need to let the person select it?
CdkBase.createLogEvent(
socket,
"WARNING",
`{F} Mutliple customer ids have been found (${strIDS.length})`
);
CdkBase.createLogEvent(
socket,
"DEBUG",
`Asking for user intervention to select customer.`
);
socket.emit("cdk-select-customer", strIDS);
return;
}
} else {
CdkBase.createLogEvent(
socket,
"DEBUG",
`{8.5} Customer ID(s) *not* found.`
);
//Create a customer number, then use that to insert the customer record.
const newCustomerNumber = await GenerateCustomerNumberFromDms(
socket,
JobData
);
CdkBase.createLogEvent(
socket,
"DEBUG",
`{10.1} New Customer number generated. newCustomerNumber: ${newCustomerNumber}`
);
//Use the new customer number to insert the customer record.
socket.clADPC = await CreateCustomerInDms(
socket,
JobData,
newCustomerNumber
);
CdkBase.createLogEvent(
socket,
"DEBUG",
`{11.1} New Customer inserted.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`{11.1} clADPC: ${JSON.stringify(socket.clADPC, null, 2)}`
);
}
} else {
CdkBase.createLogEvent(socket, "DEBUG", `{1.1} clVFV DMSVid does exist.`);
CdkBase.createLogEvent(
socket,
"TRACE",
`{1.1} clVFV: ${JSON.stringify(socket.clVFV, null, 2)}`
);
//{2} Begin Find Vehicle in DMS
socket.clADPV = await FindVehicleInDms(socket, JobData, socket.clVFV); //TODO: Verify that this should always return a result. If an ID was found previously, it should be correct?
//{2.2} Check if the vehicle was found in the DMS.
if (socket.clADPV.AppErrorNo === "0") {
//Vehicle was found.
CdkBase.createLogEvent(
socket,
"DEBUG",
`{1.4} Vehicle was found in the DMS.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`{1.4} clADPV: ${JSON.stringify(socket.clADPV, null, 2)}`
);
} else {
//Vehicle was not found.
CdkBase.createLogEvent(
socket,
"DEBUG",
`{6.4} Vehicle does not exist in DMS. Will have to create one.`
);
CdkBase.createLogEvent(
socket,
"TRACE",
`{6.4} clVFV: ${JSON.stringify(socket.clVFV, null, 2)}`
);
}
}
// Begin Calculate VID from DMS {1}
await DetermineDMSVid(socket, JobData);
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error encountered in CdkJobExport. ${error}`
`Error encountered in JobExport. ${error}`
);
} finally {
//Ensure we always insert logEvents
//GQL to insert logevents.
CdkBase.createLogEvent(
socket,
"DEBUG",
`Capturing log events to database.`
);
}
};
@@ -188,267 +61,27 @@ async function QueryJobData(socket, jobid) {
return result.jobs_by_pk;
}
async function CreateCustomerInDms(socket, JobData, newCustomerNumber) {
CdkBase.createLogEvent(socket, "DEBUG", `{11} Begin Create Customer in DMS`);
async function DetermineDMSVid(socket, JobData) {
CdkBase.createLogEvent(socket, "TRACE", "{1} Begin Determine DMS VehicleID");
try {
const soapClientCustomerInsertUpdate = await soap.createClientAsync(
CdkWsdl.CustomerInsertUpdate
);
const soapResponseCustomerInsertUpdate =
await soapClientCustomerInsertUpdate.insertAsync(
{
arg0: CDK_CREDENTIALS,
arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
arg2: { userId: null },
arg3: {
//Copied the required fields from the other integration.
//TODO: Verify whether we need to bring more information in.
id: { value: newCustomerNumber },
address: {
city: JobData.ownr_city,
country: null,
postalcode: JobData.ownr_zip,
stateOrProvince: JobData.ownr_st,
},
contactInfo: {
mainTelephoneNumber: { main: true, value: JobData.ownr_ph1 },
},
demographics: null,
name1: {
companyname: null,
firstName: JobData.ownr_fn,
fullname: null,
lastName: JobData.ownr_ln,
middleName: null,
nameType: "Person",
suffix: null,
title: null,
},
},
},
//Create SOAP Request for <getVehIds/>
const soapClient = await soap.createClientAsync(CdkWsdl.VehicleSearch);
const result = await soapClient.searchIDsByVINAsync(
{
arg0: { password: IMEX_CDK_PASSWORD, username: IMEX_CDK_USER },
arg1: { id: JobData.bodyshop.cdk_dealerid },
arg2: { VIN: JobData.v_vin },
},
{}
);
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
const [
result, //rawResponse, soapheader, rawRequest
] = soapResponseCustomerInsertUpdate;
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientCustomerInsertUpdate.insertAsync Result ${JSON.stringify(
result,
null,
2
)}`
{}
);
const customer = result && result.return;
return customer;
console.log(result);
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in CreateCustomerInDms - ${JSON.stringify(error, null, 2)}`
`Error in DetermineDMSVid - ${JSON.stringify(error, null, 2)}`
);
throw new Error(error);
}
}
async function GenerateCustomerNumberFromDms(socket, JobData) {
CdkBase.createLogEvent(
socket,
"DEBUG",
`{10} Begin Generate Customer Number from DMS`
);
try {
const soapClientCustomerInsertUpdate = await soap.createClientAsync(
CdkWsdl.CustomerInsertUpdate
);
const soapResponseCustomerInsertUpdate =
await soapClientCustomerInsertUpdate.getCustomerNumberAsync(
{
arg0: CDK_CREDENTIALS,
arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
arg2: { userId: null },
},
{}
);
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
const [
result, //rawResponse, soapheader, rawRequest
] = soapResponseCustomerInsertUpdate;
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientCustomerInsertUpdate.getCustomerNumberAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
const customerNumber =
result && result.return && result.return.customerNumber;
return customerNumber;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in GenerateCustomerNumberFromDms - ${JSON.stringify(
error,
null,
2
)}`
);
throw new Error(error);
}
}
async function FindCustomerIdFromDms(socket, JobData) {
const ownerName = `${JobData.ownr_ln},${JobData.ownr_fn}`;
CdkBase.createLogEvent(
socket,
"DEBUG",
`{8} Begin Read Customer from DMS using OWNER NAME: ${ownerName}`
);
try {
const soapClientCustomerSearch = await soap.createClientAsync(
CdkWsdl.CustomerSearch
);
const soapResponseCustomerSearch =
await soapClientCustomerSearch.executeSearchAsync(
{
arg0: CDK_CREDENTIALS,
arg1: { dealerId: JobData.bodyshop.cdk_dealerid }, //TODO: Verify why this does not follow the other standards.
arg2: {
verb: "EXACT",
key: ownerName,
},
},
{}
);
CheckCdkResponseForError(socket, soapResponseCustomerSearch);
const [
result, // rawResponse, soapheader, rawRequest
] = soapResponseCustomerSearch;
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientCustomerSearch.executeSearchBulkAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
const CustomersFromDms = result && result.return;
return CustomersFromDms;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in FindCustomerIdFromDms - ${JSON.stringify(error, null, 2)}`
);
throw new Error(error);
}
}
async function FindVehicleInDms(socket, JobData, clVFV) {
CdkBase.createLogEvent(
socket,
"DEBUG",
`{2}/{6} Begin Find Vehicle In DMS using clVFV: ${clVFV}`
);
try {
const soapClientVehicleInsertUpdate = await soap.createClientAsync(
CdkWsdl.VehicleInsertUpdate
);
const soapResponseVehicleInsertUpdate =
await soapClientVehicleInsertUpdate.readBulkAsync(
{
arg0: CDK_CREDENTIALS,
arg1: { id: JobData.bodyshop.cdk_dealerid },
arg2: {
fileType: "VEHICLES",
vehiclesVehicleId: clVFV.vehiclesVehId,
},
},
{}
);
CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
const [
result, //rawResponse, soapheader, rawRequest
] = soapResponseVehicleInsertUpdate;
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientVehicleInsertUpdate.readBulkAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
const VehicleFromDMS = result && result.return && result.return[0];
return VehicleFromDMS;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in FindVehicleInDms - ${JSON.stringify(error, null, 2)}`
);
throw new Error(error);
}
}
async function CalculateDmsVid(socket, JobData) {
CdkBase.createLogEvent(
socket,
"TRACE",
`{1} Begin Calculate DMS Vehicle ID using VIN: ${JobData.v_vin}`
);
try {
const soapClientVehicleInsertUpdate = await soap.createClientAsync(
CdkWsdl.VehicleInsertUpdate
);
const soapResponseVehicleInsertUpdate =
await soapClientVehicleInsertUpdate.getVehIdsAsync(
{
arg0: CDK_CREDENTIALS,
arg1: { id: JobData.bodyshop.cdk_dealerid },
arg2: { VIN: JobData.v_vin },
},
{}
);
CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
const [
result, //rawResponse, soapheader, rawRequest
] = soapResponseVehicleInsertUpdate;
CdkBase.createLogEvent(
socket,
"TRACE",
`soapClientVehicleInsertUpdate.searchIDsByVINAsync Result ${JSON.stringify(
result,
null,
2
)}`
);
const DmsVehicle = result && result.return && result.return[0];
return DmsVehicle;
} catch (error) {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error in CalculateDmsVid - ${JSON.stringify(error, null, 2)}`
);
throw new Error(error);
}
}

View File

@@ -1,99 +1,4 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(
process.cwd(),
`.env.${process.env.NODE_ENV || "development"}`
),
});
const CdkBase = require("../web-sockets/web-socket");
const IMEX_CDK_USER = process.env.IMEX_CDK_USER,
IMEX_CDK_PASSWORD = process.env.IMEX_CDK_PASSWORD;
const CDK_CREDENTIALS = {
password: IMEX_CDK_PASSWORD,
username: IMEX_CDK_USER,
};
exports.CDK_CREDENTIALS = CDK_CREDENTIALS;
// const cdkDomain =
// process.env.NODE_ENV === "production"
// ? "https://3pa.dmotorworks.com"
// : "https://uat-3pa.dmotorworks.com";
function CheckCdkResponseForError(socket, soapResponse) {
if (!soapResponse[0]) {
//The response was null, this might be ok, it might not.
CdkBase.createLogEvent(
socket,
"WARNING",
`Warning detected in CDK Response - it appears to be null. Stack: ${
new Error().stack
}`
);
return;
}
const ResultToCheck = soapResponse[0].return;
if (Array.isArray(ResultToCheck)) {
ResultToCheck.forEach((result) => checkIndividualResult(socket, result));
} else {
checkIndividualResult(socket, ResultToCheck);
}
}
exports.CheckCdkResponseForError = CheckCdkResponseForError;
function checkIndividualResult(socket, ResultToCheck) {
if (
ResultToCheck.errorLevel === 0 ||
ResultToCheck.errorLevel === "0" ||
ResultToCheck.code === "success" ||
(!ResultToCheck.code && !ResultToCheck.errorLevel)
)
//TODO: Verify that this is the best way to detect errors.
return;
else {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error detected in CDK Response - ${JSON.stringify(
ResultToCheck,
null,
2
)}`
);
throw new Error(
`Error found while validating CDK response for ${JSON.stringify(
ResultToCheck,
null,
2
)}:`
);
}
}
exports.checkIndividualResult = checkIndividualResult;
const cdkDomain = "https://uat-3pa.dmotorworks.com";
exports.default = {
// VehicleSearch: `${cdkDomain}/pip-vehicle/services/VehicleSearch?wsdl`,
VehicleInsertUpdate: `${cdkDomain}/pip-vehicle/services/VehicleInsertUpdate?wsdl`,
CustomerInsertUpdate: `${cdkDomain}/pip-customer/services/CustomerInsertUpdate?wsdl`,
CustomerSearch: `${cdkDomain}/pip-customer/services/CustomerSearch?wsdl`,
VehicleSearch: `${cdkDomain}/pip-vehicle/services/VehicleSearch?wsdl`,
VehicleInsert: `${cdkDomain}/pip-vehicle/services/VehicleInsertUpdate?wsdl`,
VehicleSearch:
"https://uat-3pa.dmotorworks.com/pip-vehicle/services/VehicleSearch?wsdl",
};
// The following login credentials will be used for all PIPs and all environments (User Acceptance Testing and Production).
// Only the URLs will change from https://uat-3pa.dmoto... to https://3pa.dmoto... or https://api-dit.connect... to https://api.connect...
// Accounting GL/Accounting GL WIP Update - https://uat-3pa.dmotorworks.com/pip-accounting-gl/services/AccountingGLInsertUpdate?wsdl
// Customer Insert Update - https://uat-3pa.dmotorworks.com/pip-customer/services/CustomerInsertUpdate?wsdl
// Help Database Location - https://uat-3pa.dmotorworks.com/pip-help-database-location/services/HelpDatabaseLocation?wsdl
// Parts Inventory Insert Update - https://uat-3pa.dmotorworks.com/pip-parts-inventory/services/PartsInventoryInsertUpdate?wsdl
// Purchase Order Insert - https://uat-3pa.dmotorworks.com/pip-purchase-order/services/PurchaseOrderInsert?wsdl
// Repair Order MLS Insert Update - https://uat-3pa.dmotorworks.com/pip-repair-order-mls/services/RepairOrderMLSInsertUpdate?wsdl
// Repair Order Parts Insert Update - https://uat-3pa.dmotorworks.com/pip-repair-order-parts/services/RepairOrderPartsInsertUpdate?wsdl
// Service History Insert - https://uat-3pa.dmotorworks.com/pip-service-history-insert/services/ServiceHistoryInsert?wsdl
// Service Repair Order Update - https://uat-3pa.dmotorworks.com/pip-service-repair-order/services/ServiceRepairOrderUpdate?wsdl
// Service Vehicle Insert Update - https://uat-3pa.dmotorworks.com/pip-vehicle/services/VehicleInsertUpdate?wsdl

View File

@@ -4,7 +4,7 @@ const Dinero = require("dinero.js");
const moment = require("moment");
var builder = require("xmlbuilder2");
const _ = require("lodash");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(
process.cwd(),
@@ -25,7 +25,7 @@ const ftpSetup = {
port: process.env.AUTOHOUSE_PORT,
username: process.env.AUTOHOUSE_USER,
password: process.env.AUTOHOUSE_PASSWORD,
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
debug: console.log,
algorithms: {
serverHostKey: ["ssh-rsa", "ssh-dss"],
},
@@ -33,16 +33,14 @@ const ftpSetup = {
exports.default = async (req, res) => {
//Query for the List of Bodyshop Clients.
logger.log("autohouse-start", "DEBUG", "api", null, null);
console.log("Starting Autohouse datapump request.");
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
const allxmlsToUpload = [];
const allErrors = [];
try {
for (const bodyshop of bodyshops) {
logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname,
});
console.log("Starting extract for ", bodyshop.shopname);
const erroredJobs = [];
try {
const { jobs } = await client.request(queries.AUTOHOUSE_QUERY, {
@@ -62,12 +60,12 @@ exports.default = async (req, res) => {
},
};
if (erroredJobs.length > 0) {
logger.log("autohouse-failed-jobs", "ERROR", "api", bodyshop.id, {
count: erroredJobs.length,
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number)),
});
}
console.log(
bodyshop.shopname,
"***Number of Failed jobs***: ",
erroredJobs.length,
JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
);
var ret = builder
.create(autoHouseObject, {
@@ -83,15 +81,10 @@ exports.default = async (req, res) => {
)}.xml`,
});
logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, {
shopname: bodyshop.shopname,
});
console.log("Finished extract for shop ", bodyshop.shopname);
} catch (error) {
//Error at the shop level.
logger.log("autohouse-error-shop", "ERROR", "api", bodyshop.id, {
error,
});
console.log("Error at shop level", bodyshop.shopname, error);
allErrors.push({
bodyshopid: bodyshop.id,
imexshopid: bodyshop.imexshopid,
@@ -109,9 +102,7 @@ exports.default = async (req, res) => {
let sftp = new Client();
sftp.on("error", (errors) =>
logger.log("autohouse-sftp-error", "ERROR", "api", null, {
errors,
})
console.log("Error in FTP client", JSON.stringify(errors))
);
try {
//Connect to the FTP and upload all.
@@ -119,24 +110,20 @@ exports.default = async (req, res) => {
await sftp.connect(ftpSetup);
for (const xmlObj of allxmlsToUpload) {
logger.log("autohouse-sftp-upload", "DEBUG", "api", null, {
filename: xmlObj.filename,
});
console.log("Uploading", xmlObj.filename);
const uploadResult = await sftp.put(
Buffer.from(xmlObj.xml),
`/${xmlObj.filename}`
);
logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, {
uploadResult,
});
console.log(
"🚀 ~ file: autohouse.js ~ line 94 ~ uploadResult",
uploadResult
);
}
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
} catch (error) {
logger.log("autohouse-sftp-error", "ERROR", "api", null, {
error,
});
console.log("Error when connecting to FTP", error);
} finally {
sftp.end();
}
@@ -511,10 +498,7 @@ const CreateRepairOrderTag = (job, errorCallback) => {
};
return ret;
} catch (error) {
logger.log("autohouse-job-calculate-error", "ERROR", "api", null, {
error,
});
console.log("Error calculating job", error);
errorCallback({ job, error });
}
};
@@ -526,7 +510,7 @@ const CreateCosts = (job) => {
//At the bill level.
bill_val.billlines.map((line_val) => {
//At the bill line level.
//console.log("JobCostingPartsTable -> line_val", line_val);
if (!bill_acc[line_val.cost_center])
bill_acc[line_val.cost_center] = Dinero();

View File

@@ -8,7 +8,7 @@ require("dotenv").config({
const axios = require("axios");
let nodemailer = require("nodemailer");
let aws = require("aws-sdk");
const logger = require("../utils/logger");
const ses = new aws.SES({
apiVersion: "2010-12-01",
region: "ca-central-1",
@@ -19,13 +19,9 @@ let transporter = nodemailer.createTransport({
});
exports.sendEmail = async (req, res) => {
logger.log("send-email", "DEBUG", req.user.email, null, {
from: `${req.body.from.name} <${req.body.from.address}>`,
replyTo: req.body.ReplyTo.Email,
to: req.body.to,
cc: req.body.cc,
subject: req.body.subject,
});
if (process.env.NODE_ENV !== "production") {
console.log("[EMAIL] Incoming Message", req.body.from.name);
}
let downloadedMedia = [];
if (req.body.media && req.body.media.length > 0) {
@@ -34,14 +30,7 @@ exports.sendEmail = async (req, res) => {
try {
return getImage(m);
} catch (error) {
logger.log("send-email-error", "ERROR", req.user.email, null, {
from: `${req.body.from.name} <${req.body.from.address}>`,
replyTo: req.body.ReplyTo.Email,
to: req.body.to,
cc: req.body.cc,
subject: req.body.subject,
error,
});
console.log(error);
}
})
);
@@ -84,26 +73,10 @@ exports.sendEmail = async (req, res) => {
(err, info) => {
console.log(err || info);
if (info) {
logger.log("send-email-success", "DEBUG", req.user.email, null, {
from: `${req.body.from.name} <${req.body.from.address}>`,
replyTo: req.body.ReplyTo.Email,
to: req.body.to,
cc: req.body.cc,
subject: req.body.subject,
info,
});
console.log("[EMAIL] Email sent: " + info);
res.json({ success: true, response: info });
} else {
logger.log("send-email-failure", "ERROR", req.user.email, null, {
from: `${req.body.from.name} <${req.body.from.address}>`,
replyTo: req.body.ReplyTo.Email,
to: req.body.to,
cc: req.body.cc,
subject: req.body.subject,
error: err,
});
console.log("[EMAIL] Email send failed. ", err);
res.json({ success: false, error: err });
}
}

View File

@@ -1,5 +1,5 @@
var admin = require("firebase-admin");
const logger = require("../utils/logger");
const path = require("path");
require("dotenv").config({
path: path.resolve(
@@ -26,20 +26,8 @@ const adminEmail = [
];
exports.updateUser = (req, res) => {
logger.log("admin-update-user", "WARN", req.user.email, null, {
request: req.body,
});
console.log("USer Requesting", req.user);
if (!adminEmail.includes(req.user.email)) {
logger.log(
"admin-update-user-unauthorized",
"ERROR",
req.user.email,
null,
{
request: req.body,
user: req.user,
}
);
res.sendStatus(404);
}
@@ -60,16 +48,11 @@ exports.updateUser = (req, res) => {
)
.then((userRecord) => {
// See the UserRecord reference doc for the contents of userRecord.
logger.log("admin-update-user-success", "DEBUG", req.user.email, null, {
userRecord,
});
console.log("Successfully updated user", userRecord.toJSON());
res.json(userRecord);
})
.catch((error) => {
logger.log("admin-update-user-error", "ERROR", req.user.email, null, {
error,
});
console.log("Error updating user:", error);
res.status(500).json(error);
});
};
@@ -102,6 +85,8 @@ exports.sendNotification = (req, res) => {
};
exports.validateFirebaseIdToken = async (req, res, next) => {
console.log("Check if request is authorized with Firebase ID token");
if (
(!req.headers.authorization ||
!req.headers.authorization.startsWith("Bearer ")) &&
@@ -127,10 +112,7 @@ exports.validateFirebaseIdToken = async (req, res, next) => {
} else {
// No cookie
console.error("Unauthorized attempt. No cookie provided.");
logger.log("api-unauthorized-call", "WARN", null, null, {
req,
type: "no-cookie",
});
res.status(403).send("Unauthorized");
return;
}
@@ -142,12 +124,7 @@ exports.validateFirebaseIdToken = async (req, res, next) => {
next();
return;
} catch (error) {
logger.log("api-unauthorized-call", "WARN", null, null, {
req,
type: "unauthroized",
error,
});
console.error("Error while verifying Firebase ID token:", error);
res.status(403).send("Unauthorized");
return;
}

View File

@@ -63,7 +63,6 @@ query QUERY_JOBS_FOR_RECEIVABLES_EXPORT($ids: [uuid!]!) {
ownr_zip
ownr_city
ownr_st
ownr_ea
ins_co_nm
job_totals
rate_la1
@@ -752,7 +751,6 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
ca_customer_gst
joblines(where: { removed: { _eq: false } }) {
id
db_ref
unq_seq
line_ind
tax_part
@@ -792,7 +790,6 @@ exports.QUERY_JOB_COSTING_DETAILS = ` query QUERY_JOB_COSTING_DETAILS($id: uuid!
cost_center
actualhrs
productivehrs
flat_rate
}
bodyshop{
id
@@ -855,7 +852,6 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
ca_customer_gst
joblines(where: {removed: {_eq: false}}) {
id
db_ref
unq_seq
line_ind
tax_part
@@ -895,7 +891,6 @@ exports.QUERY_JOB_COSTING_DETAILS_MULTI = ` query QUERY_JOB_COSTING_DETAILS_MULT
cost_center
actualhrs
productivehrs
flat_rate
}
bodyshop {
id
@@ -933,105 +928,3 @@ exports.GET_AUTOHOUSE_SHOPS = `query GET_AUTOHOUSE_SHOPS {
}
}
`;
exports.GET_CDK_ALLOCATIONS = `
query QUERY_JOB_CLOSE_DETAILS($id: uuid!) {
jobs_by_pk(id: $id) {
bodyshop{
id
md_responsibility_centers
}
ro_number
invoice_allocation
ins_co_id
id
ded_amt
ded_status
depreciation_taxes
other_amount_payable
towing_payable
storage_payable
adjustment_bottom_line
federal_tax_rate
state_tax_rate
local_tax_rate
tax_tow_rt
tax_str_rt
tax_paint_mat_rt
tax_sub_rt
tax_lbr_rt
tax_levies_rt
parts_tax_rates
job_totals
rate_la1
rate_la2
rate_la3
rate_la4
rate_laa
rate_lab
rate_lad
rate_lae
rate_laf
rate_lag
rate_lam
rate_lar
rate_las
rate_lau
rate_ma2s
rate_ma2t
rate_ma3s
rate_mabl
rate_macs
rate_mahw
rate_mapa
rate_mash
rate_matd
status
date_exported
date_invoiced
voided
scheduled_completion
actual_completion
scheduled_delivery
actual_delivery
scheduled_in
actual_in
bills {
id
federal_tax_rate
local_tax_rate
state_tax_rate
is_credit_memo
billlines {
actual_cost
cost_center
id
quantity
applicable_taxes
}
}
joblines(where: { removed: { _eq: false } }) {
id
removed
tax_part
line_desc
prt_dsmk_p
prt_dsmk_m
part_type
oem_partno
db_price
act_price
part_qty
mod_lbr_ty
db_hrs
mod_lb_hrs
lbr_op
lbr_amt
op_code_desc
profitcenter_labor
profitcenter_part
prt_dsmk_p
}
}
}
`;

View File

@@ -3,16 +3,16 @@ const queries = require("../graphql-client/queries");
//const client = require("../graphql-client/graphql-client").client;
const _ = require("lodash");
const GraphQLClient = require("graphql-request").GraphQLClient;
const logger = require("../utils/logger");
// Dinero.defaultCurrency = "USD";
// Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_EVEN";
async function JobCosting(req, res) {
const { jobid } = req.body;
console.time("Query for Data");
const BearerToken = req.headers.authorization;
logger.log("job-costing-start", "DEBUG", req.user.email, jobid, null);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
Authorization: BearerToken,
@@ -25,16 +25,15 @@ async function JobCosting(req, res) {
.request(queries.QUERY_JOB_COSTING_DETAILS, {
id: jobid,
});
console.timeEnd("querydata");
console.time(`generatecostingdata-${resp.jobs_by_pk.id}`);
const ret = GenerateCostingData(resp.jobs_by_pk);
console.timeEnd(`generatecostingdata-${resp.jobs_by_pk.id}`);
res.status(200).json(ret);
} catch (error) {
logger.log("job-costing-error", "ERROR", req.user.email, jobid, {
jobid,
error,
});
console.log("error", error);
res.status(400).send(JSON.stringify(error));
}
}
@@ -42,8 +41,7 @@ async function JobCosting(req, res) {
async function JobCostingMulti(req, res) {
const { jobids } = req.body;
const BearerToken = req.headers.authorization;
logger.log("job-costing-multi-start", "DEBUG", req.user.email, jobids, null);
console.time("JobCostingMultiQueryExecution");
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
Authorization: BearerToken,
@@ -80,8 +78,12 @@ async function JobCostingMulti(req, res) {
const ret = {};
resp.jobs.map((job) => {
console.time(`CostingData-${job.id}`);
const costingData = GenerateCostingData(job);
ret[job.id] = costingData;
console.timeEnd(`CostingData-${job.id}`);
console.time(`SummaryOfCostingData-${job.id}`);
//Merge on a cost center basis.
@@ -163,6 +165,7 @@ async function JobCostingMulti(req, res) {
costingData.summaryData.totalPartsGp
);
console.timeEnd(`SummaryOfCostingData-${job.id}`);
//Take the summary data & add it to total summary data.
});
@@ -217,16 +220,15 @@ async function JobCostingMulti(req, res) {
//Calculate thte total gross profit percentages.
console.timeEnd("JobCostingMultiQueryExecution");
res.status(200).json({
allCostCenterData: finalCostingdata,
allSummaryData: multiSummary.summaryData,
data: ret,
});
} catch (error) {
logger.log("job-costing-multi-error", "ERROR", req.user.email, [jobids], {
jobids,
error,
});
console.log("error", error);
res.status(400).send(JSON.stringify(error));
}
}
@@ -240,22 +242,12 @@ function GenerateCostingData(job) {
);
const materialsHours = { mapaHrs: 0, mashHrs: 0 };
let hasMapaLine = false;
let hasMashLine = false;
//Massage the data.
const jobLineTotalsByProfitCenter =
job &&
job.joblines.reduce(
(acc, val) => {
//Parts Lines
if (val.db_ref === "936008") {
//If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
hasMapaLine = true;
}
if (val.db_ref === "936007") {
hasMashLine = true;
}
if (val.mod_lbr_ty) {
const laborProfitCenter =
val.profitcenter_labor || defaultProfits[val.mod_lbr_ty] || "?";
@@ -273,11 +265,32 @@ function GenerateCostingData(job) {
acc.labor[laborProfitCenter].add(laborAmount);
if (val.mod_lbr_ty === "LAR") {
if (!acc.parts[defaultProfits["MAPA"]])
acc.parts[defaultProfits["MAPA"]] = Dinero();
materialsHours.mapaHrs += val.mod_lb_hrs || 0;
acc.parts[defaultProfits["MAPA"]] = acc.parts[
defaultProfits["MAPA"]
].add(
Dinero({
amount: Math.round((job.rate_mapa || 0) * 100),
}).multiply(val.mod_lb_hrs || 0)
);
}
if (!acc.parts[defaultProfits["MASH"]])
acc.parts[defaultProfits["MASH"]] = Dinero();
if (val.mod_lbr_ty !== "LAR") {
acc.parts[defaultProfits["MASH"]] = acc.parts[
defaultProfits["MASH"]
].add(
Dinero({
amount: Math.round((job.rate_mash || 0) * 100),
}).multiply(val.mod_lb_hrs || 0)
);
materialsHours.mashHrs += val.mod_lb_hrs || 0;
}
//If labor line, add to paint and shop materials.
}
if (val.part_type && val.part_type !== "PAE") {
@@ -345,27 +358,6 @@ function GenerateCostingData(job) {
{ parts: {}, labor: {} }
);
if (!hasMapaLine) {
if (!jobLineTotalsByProfitCenter.parts[defaultProfits["MAPA"]])
jobLineTotalsByProfitCenter.parts[defaultProfits["MAPA"]] = Dinero();
jobLineTotalsByProfitCenter.parts[defaultProfits["MAPA"]] =
jobLineTotalsByProfitCenter.parts[defaultProfits["MAPA"]].add(
Dinero({
amount: Math.round((job.rate_mapa || 0) * 100),
}).multiply(materialsHours.mapaHrs || 0)
);
}
if (!hasMashLine) {
if (!jobLineTotalsByProfitCenter.parts[defaultProfits["MASH"]])
jobLineTotalsByProfitCenter.parts[defaultProfits["MASH"]] = Dinero();
jobLineTotalsByProfitCenter.parts[defaultProfits["MASH"]] =
jobLineTotalsByProfitCenter.parts[defaultProfits["MASH"]].add(
Dinero({
amount: Math.round((job.rate_mash || 0) * 100),
}).multiply(materialsHours.mashHrs || 0)
);
}
const billTotalsByCostCenters = job.bills.reduce((bill_acc, bill_val) => {
//At the bill level.
bill_val.billlines.map((line_val) => {
@@ -445,11 +437,7 @@ function GenerateCostingData(job) {
].add(
Dinero({
amount: Math.round((ticket_val.rate || 0) * 100),
}).multiply(
ticket_val.flat_rate
? ticket_val.productivehrs || ticket_val.actualhrs || 0
: ticket_val.actualhrs || ticket_val.productivehrs || 0
) //Should base this on the employee.
}).multiply(ticket_val.actualhrs || ticket_val.productivehrs || 0)
);
return ticket_acc;
@@ -615,14 +603,17 @@ const formatGpPercent = (gppercent) => {
//Verify that this stays in line with jobs-close-auto-allocate logic from the application.
const getAdditionalCostCenter = (jl, profitCenters) => {
console.log("Checking additional cost center", jl.line_desc);
if (!jl.part_type && !jl.mod_lbr_ty) {
const lineDesc = jl.line_desc.toLowerCase();
if (lineDesc.includes("shop mat")) {
return profitCenters["MASH"];
} else if (lineDesc.includes("paint/mat")) {
return profitCenters["MAPA"];
} else if (lineDesc.includes("ats amount")) {
const lineDesc = jl.line_desc ? jl.line_desc.toLowerCase() : "";
//This logic is covered prior and assigned based on the labor type of the lines
// if (lineDesc.includes("shop materials")) {
// return profitCenters["MASH"];
// } else if (lineDesc.includes("paint/materials")) {
// return profitCenters["MAPA"];
// } else
//End covered logic
if (lineDesc.includes("ats amount")) {
return profitCenters["ATS"];
} else {
return null;

View File

@@ -1,7 +1,7 @@
const Dinero = require("dinero.js");
const queries = require("../graphql-client/queries");
const GraphQLClient = require("graphql-request").GraphQLClient;
const logger = require("../utils/logger");
// Dinero.defaultCurrency = "USD";
// Dinero.globalLocale = "en-CA";
Dinero.globalRoundingMode = "HALF_EVEN";
@@ -9,7 +9,7 @@ Dinero.globalRoundingMode = "HALF_EVEN";
exports.totalsSsu = async function (req, res) {
const BearerToken = req.headers.authorization;
const { id } = req.body;
logger.log("job-totals-ssu", "DEBUG", req.user.email, id, null);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
Authorization: BearerToken,
@@ -43,10 +43,7 @@ exports.totalsSsu = async function (req, res) {
res.status(200).send();
} catch (error) {
logger.log("job-totals-ssu-error", "ERROR", req.user.email, id, {
jobid: id,
error,
});
console.log(error);
res.status(503).send();
}
};
@@ -54,6 +51,9 @@ exports.totalsSsu = async function (req, res) {
//IMPORTANT*** These two functions MUST be mirrrored.
async function TotalsServerSide(req, res) {
const { job } = req.body;
console.log(
`Calculating Job Totals on the server side for ${job.id} - ${job.ro_number}`
);
try {
let ret = {
parts: CalculatePartsTotals(job.joblines),
@@ -64,20 +64,14 @@ async function TotalsServerSide(req, res) {
return ret;
} catch (error) {
logger.log("job-totals-ssu-error", "ERROR", req.user.email, job.id, {
jobid: job.id,
error,
});
console.log("error", error);
res.status(400).send(JSON.stringify(error));
}
}
async function Totals(req, res) {
const { job } = req.body;
logger.log("job-totals", "DEBUG", req.user.email, job.id, {
jobid: job.id,
});
console.log(`Calculating Job Totals for ${job.id} - ${job.ro_number}`);
try {
let ret = {
parts: CalculatePartsTotals(job.joblines),
@@ -88,10 +82,7 @@ async function Totals(req, res) {
res.status(200).json(ret);
} catch (error) {
logger.log("job-totals-error", "ERROR", req.user.email, job.id, {
jobid: job.id,
error,
});
console.log("error", error);
res.status(400).send(JSON.stringify(error));
}
}
@@ -166,28 +157,7 @@ function CalculateRatesTotals(ratesList) {
},
};
//Determine if there are MAPA and MASH lines already on the estimate.
//If there are, don't do anything extra (mitchell estimate)
//Otherwise, calculate them and add them to the default MAPA and MASH centers.
let hasMapaLine = false;
let hasMashLine = false;
jobLines.forEach((item) => {
//IO-1317 Use the lines on the estimate if they exist instead.
if (item.db_ref === "936008") {
//If either of these DB REFs change, they also need to change in job-totals/job-costing calculations.
hasMapaLine = true;
ret["mapa"].total = Dinero({
amount: Math.round((item.act_price || 0) * 100),
});
}
if (item.db_ref === "936007") {
hasMashLine = true;
ret["mash"].total = Dinero({
amount: Math.round((item.act_price || 0) * 100),
});
}
if (item.mod_lbr_ty) {
//There's a labor type, assign the hours.
ret[item.mod_lbr_ty.toLowerCase()].hours =
@@ -203,22 +173,11 @@ function CalculateRatesTotals(ratesList) {
let subtotal = Dinero({ amount: 0 });
let rates_subtotal = Dinero({ amount: 0 });
for (const property in ret) {
//Skip calculating mapa and mash if we got the amounts.
if (
!(
(property === "mapa" && hasMapaLine) ||
(property === "mash" && hasMashLine)
)
) {
ret[property].total = Dinero({
amount: Math.round((ret[property].rate || 0) * 100),
}).multiply(ret[property].hours);
}
ret[property].total = Dinero({
amount: Math.round((ret[property].rate || 0) * 100),
}).multiply(ret[property].hours);
subtotal = subtotal.add(ret[property].total);
if (property !== "mapa" && property !== "mash")
rates_subtotal = rates_subtotal.add(ret[property].total);
}
@@ -404,8 +363,7 @@ function CalculateTaxesTotals(job, otherTotals) {
job.joblines
.filter((jl) => !jl.removed)
.forEach((val) => {
if (!val.tax_part) return;
if (!val.part_type && IsAdditionalCost(val)) {
if (!val.tax_part || (!val.part_type && IsAdditionalCost(val))) {
additionalItemsTax = additionalItemsTax.add(
Dinero({ amount: Math.round((val.act_price || 0) * 100) })
.multiply(val.part_qty || 0)

View File

@@ -1,7 +1,5 @@
const path = require("path");
const _ = require("lodash");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(
process.cwd(),
@@ -13,7 +11,8 @@ var cloudinary = require("cloudinary").v2;
cloudinary.config(process.env.CLOUDINARY_URL);
exports.createSignedUploadURL = (req, res) => {
logger.log("media-signed-upload", "DEBUG", req.user.email, null, null);
console.log("Request to create signed upload URL for Cloudinary.", req.body);
res.send(
cloudinary.utils.api_sign_request(
req.body,
@@ -24,7 +23,6 @@ exports.createSignedUploadURL = (req, res) => {
exports.downloadFiles = (req, res) => {
const { ids } = req.body;
logger.log("media-bulk-download", "DEBUG", req.user.email, ids, null);
const url = cloudinary.utils.download_zip_url({
public_ids: ids,
@@ -36,8 +34,7 @@ exports.downloadFiles = (req, res) => {
exports.deleteFiles = async (req, res) => {
const { ids } = req.body;
const types = _.groupBy(ids, (x) => DetermineFileType(x.type));
logger.log("media-bulk-delete", "DEBUG", req.user.email, ids, null);
console.log("🚀 ~ file: media.js ~ line 28 ~ types", types);
const returns = [];
if (types.image) {
@@ -68,15 +65,16 @@ exports.deleteFiles = async (req, res) => {
)
);
}
console.log("🚀 ~ file: media.js ~ line 40 ~ returns", returns);
res.send(returns);
};
exports.renameKeys = async (req, res) => {
const { documents } = req.body;
logger.log("media-bulk-rename", "DEBUG", req.user.email, null, documents);
//{id: "", from: "", to:""}
const proms = [];
console.log("Documents", documents);
documents.forEach((d) => {
proms.push(
(async () => {

View File

@@ -5,25 +5,23 @@ require("dotenv").config({
`.env.${process.env.NODE_ENV || "development"}`
),
});
const logger = require("../utils/logger");
const inlineCssTool = require("inline-css");
exports.inlinecss = (req, res) => {
//Perform request validation
logger.log("email-inline-css", "DEBUG", req.user.email, null, null);
console.log("[CSS] New Inline CSS Request.");
const { html, url } = req.body;
inlineCssTool(html, { url: url })
.then((inlinedHtml) => {
console.log("Inline success.");
res.send(inlinedHtml);
})
.catch((error) => {
logger.log("email-inline-css-error", "ERROR", req.user.email, null, {
error,
});
console.log("Error while inlining CSS", JSON.stringify(error));
res.send(error);
});
};

View File

@@ -0,0 +1,212 @@
// const path = require("path");
// const moment = require("moment");
// require("dotenv").config({
// path: path.resolve(
// process.cwd(),
// `.env.${process.env.NODE_ENV || "development"}`
// ),
// });
// var _ = require("lodash");
// const Handlebars = require("handlebars");
// const phone = require("phone");
// var Dinero = require("dinero.js");
// Dinero.defaultCurrency = "CAD";
// Dinero.globalLocale = "en-CA";
// //Usage: {{moment appointments_by_pk.start format="dddd, DD MMMM YYYY"}}
// Handlebars.registerHelper("round", function (context, block) {
// if (context && context.hash) {
// block = _.cloneDeep(context);
// context = undefined;
// }
// try {
// return context.toFixed(2);
// } catch {
// return context;
// }
// });
// Handlebars.registerHelper("dinerof", function (context, block) {
// if (context && context.hash) {
// block = _.cloneDeep(context);
// context = undefined;
// }
// var amount = Dinero(context);
// if (context) {
// return amount.toFormat();
// }
// return "";
// });
// Handlebars.registerHelper("phonef", function (context, block) {
// if (context && context.hash) {
// block = _.cloneDeep(context);
// context = undefined;
// }
// var ph = phone(context)[0];
// if (context) {
// return ph;
// }
// return "";
// });
// Handlebars.registerHelper("partType", function (context, block) {
// if (!context) return "";
// switch (context.toUpperCase()) {
// case "PAA":
// return "Aftermarket";
// case "PAE":
// return "Existing";
// case "PAN":
// return "OEM";
// case "PAO":
// return "Other";
// case "PAS":
// return "Sublet";
// case "PASL":
// return "Sublet";
// case "PAL":
// return "LKQ";
// case "PAM":
// return "Remanufactured";
// case "PAC":
// return "Chrome";
// case "PAP":
// return "OEM Partial";
// case "PAR":
// return "Record";
// default:
// return context;
// }
// });
// Handlebars.registerHelper("lbrType", function (context, block) {
// if (!context) return "";
// switch (context.toUpperCase()) {
// case "LAA":
// return "Aluminum";
// case "LAB":
// return "Body";
// case "LAD":
// return "Diagnostic";
// case "LAF":
// return "Frame";
// case "LAG":
// return "Glass";
// case "LAM":
// return "Mechanical";
// case "LAR":
// return "Refinish";
// case "LAS":
// return "Structural";
// case "LAU":
// return "Detail";
// default:
// return context;
// }
// });
// Handlebars.registerHelper("objectKeys", function (obj, block) {
// var accum = "";
// obj &&
// Object.keys(obj).map((key) => {
// accum += block.fn({ key, value: obj[key] });
// });
// return accum;
// });
// Handlebars.registerHelper("dinero", function (context, block) {
// if (context && context.hash) {
// block = _.cloneDeep(context);
// context = undefined;
// }
// var amount = Dinero({
// amount: Math.round((context || 0) * 100),
// currency: "CAD",
// });
// return amount.toFormat();
// });
// Handlebars.registerHelper("moment", function (context, block) {
// if (context && context.hash) {
// block = _.cloneDeep(context);
// context = undefined;
// }
// if (!!!context) return "";
// var date = moment(context);
// if (block.hash.timezone) {
// date.tz(block.hash.timezone);
// }
// var hasFormat = false;
// // Reset the language back to default before doing anything else
// date.locale("en");
// for (var i in block.hash) {
// if (i === "format") {
// hasFormat = true;
// } else if (date[i]) {
// date = date[i](block.hash[i]);
// } else {
// console.log('moment.js does not support "' + i + '"');
// }
// }
// if (hasFormat) {
// date = date.format(block.hash.format);
// }
// return date;
// });
// Handlebars.registerHelper("duration", function (context, block) {
// if (context && context.hash) {
// block = _.cloneDeep(context);
// context = 0;
// }
// var duration = moment.duration(context);
// var hasFormat = false;
// // Reset the language back to default before doing anything else
// duration = duration.lang("en");
// for (var i in block.hash) {
// if (i === "format") {
// hasFormat = true;
// } else if (duration[i]) {
// duration = duration[i](block.hash[i]);
// } else {
// console.log('moment.js duration does not support "' + i + '"');
// }
// }
// if (hasFormat) {
// duration = duration.format(block.hash.format);
// }
// return duration;
// });
exports.render = (req, res) => {
// //Perform request validation
// let view;
// console.log("[HJS Render] New Render Request.");
// //console.log("[HJS Render] Context", req.body.context);
// if (req.body.context.bodyshop.template_header) {
// console.log("[HJS Render] Including Header");
// //view = req.body.view;
// view = `${req.body.context.bodyshop.template_header}${req.body.view}`;
// } else {
// console.log("[HJS Render] No header to include.");
// view = req.body.view;
// }
// var template = Handlebars.compile(view);
// res.send(template(req.body.context));
};

View File

@@ -3,7 +3,7 @@ const path = require("path");
const queries = require("../graphql-client/queries");
const Dinero = require("dinero.js");
const moment = require("moment");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(
process.cwd(),
@@ -12,10 +12,10 @@ require("dotenv").config({
});
exports.job = async (req, res) => {
const BearerToken = req.headers.authorization;
const { jobId } = req.body;
try {
logger.log("smart-scheduling-start", "DEBUG", req.user.email, jobId, null);
const BearerToken = req.headers.authorization;
const { jobId } = req.body;
console.log("exports.job -> jobId", jobId);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {
headers: {
@@ -133,12 +133,10 @@ exports.job = async (req, res) => {
)
possibleDates.push(new Date(bmkey).toISOString().substr(0, 10));
});
console.log("possibleDates", possibleDates, "bucketMatrix", bucketMatrix);
res.json(possibleDates);
} catch (error) {
logger.log("smart-scheduling-error", "ERROR", req.user.email, jobId, {
error,
});
console.log("error", error);
res.status(400).send(error);
}
};

View File

@@ -10,29 +10,16 @@ const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");
const { phone } = require("phone");
const admin = require("../firebase/firebase-handler").admin;
const logger = require("../utils/logger");
exports.receive = (req, res) => {
//Perform request validation
logger.log("sms-inbound", "DEBUG", "api", null, {
msid: req.body.SmsMessageSid,
text: req.body.Body,
image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body),
});
console.log("[SMS Receive] Inbound Twilio Message.", req.body.SmsMessageSid);
console.log("req.body", req.body);
if (
!!!req.body ||
!!!req.body.MessagingServiceSid ||
!!!req.body.SmsMessageSid
) {
logger.log("sms-inbound-error", "ERROR", "api", null, {
msid: req.body.SmsMessageSid,
text: req.body.Body,
image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body),
type: "malformed-request",
});
res.status(400);
res.json({ success: false, error: "Malformed Request" });
} else {
@@ -42,6 +29,8 @@ exports.receive = (req, res) => {
phone: phone(req.body.From).phoneNumber,
})
.then((response) => {
console.log("re", req.body);
let newMessage = {
msid: req.body.SmsMessageSid,
text: req.body.Body,
@@ -66,14 +55,10 @@ exports.receive = (req, res) => {
response.bodyshops[0].conversations[0].id;
} else {
//We should never get here.
logger.log("sms-inbound-error", "ERROR", "api", null, {
msid: req.body.SmsMessageSid,
text: req.body.Body,
image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body),
messagingServiceSid: req.body.MessagingServiceSid,
type: "duplicate-phone",
});
console.log(
"Massive Error: Duplicate Phone Numbers for MSSID: " +
req.body.MessagingServiceSid
);
}
client
@@ -82,9 +67,6 @@ exports.receive = (req, res) => {
conversationid: response.bodyshops[0].conversations[0].id,
})
.then((r2) => {
logger.log("sms-inbound-success", "DEBUG", "api", null, {
newMessage,
});
res.status(200).send("");
const arrayOfAllUserFcmTokens =
@@ -127,15 +109,7 @@ exports.receive = (req, res) => {
// });
})
.catch((e2) => {
logger.log("sms-inbound-error", "ERROR", "api", null, {
msid: req.body.SmsMessageSid,
text: req.body.Body,
image: !!req.body.MediaUrl0,
image_path: generateMediaArray(req.body),
messagingServiceSid: req.body.MessagingServiceSid,
error: e2,
});
console.log("e2", e2);
res.sendStatus(500).json(e2);
});
}

View File

@@ -9,7 +9,7 @@ require("dotenv").config({
const twilio = require("twilio");
const { phone } = require("phone");
const queries = require("../graphql-client/queries");
const logger = require("../utils/logger");
const client = twilio(
process.env.TWILIO_AUTH_TOKEN,
process.env.TWILIO_AUTH_KEY
@@ -19,21 +19,9 @@ const gqlClient = require("../graphql-client/graphql-client").client;
exports.send = (req, res) => {
const { to, messagingServiceSid, body, conversationid, selectedMedia } =
req.body;
logger.log("sms-outbound", "DEBUG", req.user.email, null, {
messagingServiceSid: messagingServiceSid,
to: phone(to).phoneNumber,
mediaUrl: selectedMedia.map((i) => i.src),
text: body,
conversationid,
isoutbound: true,
userid: req.user.email,
image: req.body.selectedMedia.length > 0,
image_path:
req.body.selectedMedia.length > 0 ? selectedMedia.map((i) => i.src) : [],
});
console.log("[Sending Sms] " + conversationid + " | " + body);
if (!!to && !!messagingServiceSid && !!body && !!conversationid) {
console.log(phone(to));
client.messages
.create({
body: body,
@@ -58,47 +46,40 @@ exports.send = (req, res) => {
.request(queries.INSERT_MESSAGE, { msg: newMessage })
.then((r2) => {
//console.log("Responding GQL Message ID", JSON.stringify(r2));
logger.log("sms-outbound-success", "DEBUG", req.user.email, null, {
msid: message.sid,
conversationid,
});
res.sendStatus(200);
})
.catch((e2) => {
logger.log("sms-outbound-error", "ERROR", req.user.email, null, {
msid: message.sid,
conversationid,
error: e2,
});
console.log("e2", e2);
//res.json({ success: false, message: e2 });
});
})
.catch((e1) => {
//res.json({ success: false, message: error });
logger.log("sms-outbound-error", "ERROR", req.user.email, null, {
conversationid,
error: e1,
});
console.log("e1", e1);
});
} else {
logger.log("sms-outbound-error", "ERROR", req.user.email, null, {
type: "missing-parameters",
messagingServiceSid: messagingServiceSid,
to: phone(to).phoneNumber,
text: body,
conversationid,
isoutbound: true,
userid: req.user.email,
image: req.body.selectedMedia.length > 0,
image_path:
req.body.selectedMedia.length > 0
? selectedMedia.map((i) => i.src)
: [],
});
res
.status(400)
.json({ success: false, message: "Missing required parameter(s)." });
}
};
// //Image
// acc.push({
// src: `${process.env.REACT_APP_CLOUDINARY_ENDPOINT}/${DetermineFileType(
// value.type
// )}/upload/${value.key}`,
// thumbnail: `${
// process.env.REACT_APP_CLOUDINARY_ENDPOINT
// }/${DetermineFileType(value.type)}/upload/${
// process.env.REACT_APP_CLOUDINARY_THUMB_TRANSFORMATIONS
// }/${value.key}`,
// thumbnailHeight: 225,
// thumbnailWidth: 225,
// isSelected: false,
// key: value.key,
// extension: value.extension,
// id: value.id,
// type: value.type,
// tags: [{ value: value.type, title: value.type }],
// });

View File

@@ -9,7 +9,6 @@ require("dotenv").config({
const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");
const { phone } = require("phone");
const logger = require("../utils/logger");
exports.status = (req, res) => {
const { SmsSid, SmsStatus } = req.body;
@@ -19,17 +18,10 @@ exports.status = (req, res) => {
fields: { status: SmsStatus },
})
.then((response) => {
logger.log("sms-status-update", "DEBUG", "api", null, {
msid: SmsSid,
fields: { status: SmsStatus },
});
console.log("Message Updated", JSON.stringify(response));
})
.catch((error) => {
logger.log("sms-status-update-error", "ERROR", "api", null, {
msid: SmsSid,
fields: { status: SmsStatus },
error,
});
console.log("Error updating message status", error);
});
res.sendStatus(200);
};

View File

@@ -1,7 +1,7 @@
const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");
const path = require("path");
const logger = require("../utils/logger");
require("dotenv").config({
path: path.resolve(
process.cwd(),
@@ -11,7 +11,7 @@ require("dotenv").config({
exports.techLogin = async (req, res) => {
const { shopid, employeeid, pin } = req.body;
logger.log("tech-console-login", "DEBUG", req.user.email, null, null);
try {
const result = await client.request(queries.QUERY_EMPLOYEE_PIN, {
shopId: shopid,
@@ -28,23 +28,14 @@ exports.techLogin = async (req, res) => {
delete dbRecord.pin;
technician = dbRecord;
} else {
logger.log("tech-console-login-error", "DEBUG", req.user.email, null, {
type: "wrong-pin",
});
error = "The employee ID and PIN combination are not correct.";
}
} else {
logger.log("tech-console-login-error", "DEBUG", req.user.email, null, {
type: "invalid-employee",
});
error = "The employee ID does not exist.";
}
res.json({ valid, technician, error });
} catch (error) {
logger.log("tech-console-login-error", "DEBUG", req.user.email, null, {
error,
});
console.log("error", error);
res.status(400).send(error);
}
};

View File

@@ -1,24 +0,0 @@
const graylog2 = require("graylog2");
const logger = new graylog2.graylog({
servers: [{ host: "logs.bodyshop.app", port: 12201 }],
});
function log(message, type, user, record, object) {
console.log(message, {
type,
env: process.env.NODE_ENV || "development",
user,
record,
...object,
});
logger.log(message, {
type,
env: process.env.NODE_ENV || "development",
user,
record,
...object,
});
}
module.exports = { log };

View File

@@ -1,4 +1,5 @@
const path = require("path");
const _ = require("lodash");
require("dotenv").config({
path: path.resolve(
process.cwd(),
@@ -9,11 +10,7 @@ require("dotenv").config({
const { io } = require("../../server");
const { admin } = require("../firebase/firebase-handler");
const CdkJobExport = require("../cdk/cdk-job-export").default;
const CdkGetMakes = require("../cdk/cdk-get-makes").default;
const CdkCalculateAllocations =
require("../cdk/cdk-calculate-allocations").default;
const { isArray } = require("lodash");
const logger = require("../utils/logger");
io.use(function (socket, next) {
try {
@@ -33,10 +30,6 @@ io.use(function (socket, next) {
}
} catch (error) {
console.log("Uncaught connection error:::", error);
logger.log("websocket-connection-error", "error", null, null, {
token: socket.handshake.auth.token,
...error,
});
next(new Error(`Authentication error ${error}`));
}
});
@@ -53,40 +46,6 @@ io.on("connection", (socket) => {
socket.on("cdk-export-job", (jobid) => {
CdkJobExport(socket, jobid);
});
socket.on("cdk-selected-customer", (selectedCustomerId) => {
createLogEvent(
socket,
"DEBUG",
`User selected customer ID ${selectedCustomerId}`
);
socket.selectedCustomerId = selectedCustomerId;
//CdkJobExport(socket, jobid);
});
socket.on("cdk-get-makes", async (cdk_dealerid, callback) => {
try {
const makes = await CdkGetMakes(socket, cdk_dealerid);
callback(makes);
} catch (error) {
createLogEvent(
socket,
"ERROR",
`Error in cdk-get-makes WS call. ${JSON.stringify(error, null, 2)}`
);
}
});
socket.on("cdk-calculate-allocations", async (jobid, callback) => {
const allocations = await CdkCalculateAllocations(socket, jobid);
createLogEvent(socket, "DEBUG", `Allocations calculated.`);
createLogEvent(
socket,
"TRACE",
`Allocations calculated. ${JSON.stringify(allocations, null, 2)}`
);
callback(allocations);
});
socket.on("disconnect", () => {
createLogEvent(socket, "DEBUG", `User disconnected.`);
@@ -96,7 +55,7 @@ io.on("connection", (socket) => {
function createLogEvent(socket, level, message) {
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) {
console.log(
`[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${
`[CDK LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${
socket.id
} - ${message}`
);
@@ -106,10 +65,6 @@ function createLogEvent(socket, level, message) {
message,
});
logger.log("ws-log-event", level, socket.user.email, socket.recordid, {
wsmessage: message,
});
if (socket.logEvents && isArray(socket.logEvents)) {
socket.logEvents.push({
timestamp: new Date(),
@@ -117,9 +72,6 @@ function createLogEvent(socket, level, message) {
message,
});
}
// if (level === "ERROR") {
// throw new Error(message);
// }
}
}

721
yarn.lock

File diff suppressed because it is too large Load Diff