Merged in release/2021-10-01 (pull request #231)

Release/2021 10 01
This commit is contained in:
Patrick Fic
2021-09-30 01:50:34 +00:00
311 changed files with 8935 additions and 1460 deletions

View File

@@ -8229,6 +8229,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>qbo</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>rbac</name> <name>rbac</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -32575,6 +32596,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>csi_invitation_action</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>diagnostic_authorization</name> <name>diagnostic_authorization</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -33205,6 +33247,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>sgi_certificate_of_repairs</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>sgi_windshield_auth</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node> <concept_node>
<name>stolen_recovery_checklist</name> <name>stolen_recovery_checklist</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>

View File

@@ -4,18 +4,18 @@
"private": true, "private": true,
"proxy": "http://localhost:5000", "proxy": "http://localhost:5000",
"dependencies": { "dependencies": {
"@apollo/client": "^3.4.13", "@apollo/client": "^3.4.14",
"@craco/craco": "^6.3.0", "@craco/craco": "^6.3.0",
"@fingerprintjs/fingerprintjs": "^3.3.0", "@fingerprintjs/fingerprintjs": "^3.3.0",
"@lourenci/react-kanban": "^2.1.0", "@lourenci/react-kanban": "^2.1.0",
"@openreplay/tracker": "^3.3.1", "@openreplay/tracker": "^3.4.0",
"@openreplay/tracker-assist": "^3.1.1", "@openreplay/tracker-assist": "^3.4.0",
"@openreplay/tracker-graphql": "^3.0.0", "@openreplay/tracker-graphql": "^3.0.0",
"@openreplay/tracker-redux": "^3.0.0", "@openreplay/tracker-redux": "^3.0.0",
"@sentry/react": "^6.13.0", "@sentry/react": "^6.13.2",
"@sentry/tracing": "^6.13.0", "@sentry/tracing": "^6.13.2",
"@stripe/react-stripe-js": "^1.4.0", "@stripe/react-stripe-js": "^1.5.0",
"@stripe/stripe-js": "^1.17.1", "@stripe/stripe-js": "^1.18.0",
"@tanem/react-nprogress": "^3.0.79", "@tanem/react-nprogress": "^3.0.79",
"antd": "^4.16.13", "antd": "^4.16.13",
"apollo-link-logger": "^2.0.0", "apollo-link-logger": "^2.0.0",
@@ -26,15 +26,15 @@
"enquire-js": "^0.2.1", "enquire-js": "^0.2.1",
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"exifr": "^7.1.3", "exifr": "^7.1.3",
"firebase": "^9.0.2", "firebase": "^9.1.0",
"graphql": "^15.5.3", "graphql": "^15.6.0",
"i18next": "^21.0.0", "i18next": "^21.1.1",
"i18next-browser-languagedetector": "^6.1.2", "i18next-browser-languagedetector": "^6.1.2",
"jsoneditor": "^9.5.4", "jsoneditor": "^9.5.6",
"jsreport-browser-client-dist": "^1.3.0", "jsreport-browser-client-dist": "^1.3.0",
"libphonenumber-js": "^1.9.34", "libphonenumber-js": "^1.9.34",
"logrocket": "^2.0.0", "logrocket": "^2.0.0",
"markerjs2": "^2.11.2", "markerjs2": "^2.12.0",
"moment-business-days": "^1.2.0", "moment-business-days": "^1.2.0",
"phone": "^3.1.8", "phone": "^3.1.8",
"preval.macro": "^5.0.0", "preval.macro": "^5.0.0",
@@ -59,13 +59,13 @@
"react-scripts": "^4.0.3", "react-scripts": "^4.0.3",
"react-sublime-video": "^0.2.5", "react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3", "react-virtualized": "^9.22.3",
"recharts": "^2.1.3", "recharts": "^2.1.4",
"redux": "^4.1.1", "redux": "^4.1.1",
"redux-persist": "^6.0.0", "redux-persist": "^6.0.0",
"redux-saga": "^1.1.3", "redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.2", "redux-state-sync": "^3.1.2",
"reselect": "^4.0.0", "reselect": "^4.0.0",
"sass": "^1.41.1", "sass": "^1.42.1",
"socket.io-client": "^4.2.0", "socket.io-client": "^4.2.0",
"styled-components": "^5.3.1", "styled-components": "^5.3.1",
"subscriptions-transport-ws": "^0.9.18", "subscriptions-transport-ws": "^0.9.18",

View File

@@ -11,7 +11,7 @@ import App from "./App";
import trackerGraphQL from "@openreplay/tracker-graphql"; import trackerGraphQL from "@openreplay/tracker-graphql";
//import trackerRedux from "@openreplay/tracker-redux"; //import trackerRedux from "@openreplay/tracker-redux";
import Tracker from "@openreplay/tracker"; import Tracker from "@openreplay/tracker";
import trackerAssist from "@openreplay/tracker-assist"; //import trackerAssist from "@openreplay/tracker-assist";
import { getCurrentUser } from "../firebase/firebase.utils"; import { getCurrentUser } from "../firebase/firebase.utils";
moment.locale("en-US"); moment.locale("en-US");
@@ -29,9 +29,9 @@ export const tracker = new Tracker({
}, },
}); });
tracker.use( // tracker.use(
trackerAssist({ confirmText: "Technical support is about to assist you." }) // trackerAssist({ confirmText: "Technical support is about to assist you." })
); // check the list of available options below // ); // check the list of available options below
export const recordGraphQL = tracker.use(trackerGraphQL()); export const recordGraphQL = tracker.use(trackerGraphQL());
tracker.start(); tracker.start();
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp"); if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 51 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 51 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.5 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.2 KiB

View File

@@ -9,8 +9,25 @@ import PayableExportAll from "../payable-export-all-button/payable-export-all-bu
import { DateFormatter } from "../../utils/DateFormatter"; import { DateFormatter } from "../../utils/DateFormatter";
import queryString from "query-string"; import queryString from "query-string";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
export default function AccountingPayablesTableComponent({ loading, bills }) { const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(AccountingPayablesTableComponent);
export function AccountingPayablesTableComponent({ bodyshop, loading, bills }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [selectedBills, setSelectedBills] = useState([]); const [selectedBills, setSelectedBills] = useState([]);
const [transInProgress, setTransInProgress] = useState(false); const [transInProgress, setTransInProgress] = useState(false);
@@ -166,6 +183,9 @@ export default function AccountingPayablesTableComponent({ loading, bills }) {
loadingCallback={setTransInProgress} loadingCallback={setTransInProgress}
completedCallback={setSelectedBills} completedCallback={setSelectedBills}
/> />
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
<QboAuthorizeComponent />
)}
<Input <Input
value={state.search} value={state.search}
onChange={handleSearch} onChange={handleSearch}

View File

@@ -11,6 +11,7 @@ import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-butto
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
@@ -206,6 +207,9 @@ export function AccountingReceivablesTableComponent({
completedCallback={setSelectedJobs} completedCallback={setSelectedJobs}
/> />
)} )}
{bodyshop.accountingconfig && bodyshop.accountingconfig.qbo && (
<QboAuthorizeComponent />
)}
<Input.Search <Input.Search
value={state.search} value={state.search}
onChange={handleSearch} onChange={handleSearch}

View File

@@ -1,11 +1,24 @@
import { useMutation } from "@apollo/client"; import { useMutation, useLazyQuery } from "@apollo/client";
import { Button, Card, Form, InputNumber, notification, Popover } from "antd"; import {
Button,
Card,
Form,
InputNumber,
notification,
Popover,
Space,
} from "antd";
import moment from "moment"; import moment from "moment";
import React, { useState } from "react"; import React, { useState, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils"; import { logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_SCOREBOARD_ENTRY } from "../../graphql/scoreboard.queries"; import {
INSERT_SCOREBOARD_ENTRY,
QUERY_SCOREBOARD_ENTRY,
UPDATE_SCOREBOARD_ENTRY,
} from "../../graphql/scoreboard.queries";
import FormDatePicker from "../form-date-picker/form-date-picker.component"; import FormDatePicker from "../form-date-picker/form-date-picker.component";
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
export default function ScoreboardAddButton({ export default function ScoreboardAddButton({
job, job,
@@ -14,17 +27,46 @@ export default function ScoreboardAddButton({
}) { }) {
const { t } = useTranslation(); const { t } = useTranslation();
const [insertScoreboardEntry] = useMutation(INSERT_SCOREBOARD_ENTRY); const [insertScoreboardEntry] = useMutation(INSERT_SCOREBOARD_ENTRY);
const [updateScoreboardEntry] = useMutation(UPDATE_SCOREBOARD_ENTRY);
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [form] = Form.useForm(); const [form] = Form.useForm();
const [visibility, setVisibility] = useState(false); const [visibility, setVisibility] = useState(false);
const [callQuery, { loading: entryLoading, data: entryData }] = useLazyQuery(
QUERY_SCOREBOARD_ENTRY
);
useEffect(() => {
if (visibility) {
callQuery({ variables: { jobid: job.id } });
}
}, [visibility, job.id, callQuery]);
useEffect(() => {
console.log("UE", entryData);
if (entryData && entryData.scoreboard && entryData.scoreboard[0]) {
console.log("Setting FOrm");
form.setFieldsValue(entryData.scoreboard[0]);
}
}, [entryData, form]);
const handleFinish = async (values) => { const handleFinish = async (values) => {
logImEXEvent("job_close_add_to_scoreboard"); logImEXEvent("job_close_add_to_scoreboard");
setLoading(true); setLoading(true);
const result = await insertScoreboardEntry({ let result;
variables: { sbInput: [{ jobid: job.id, ...values }] },
}); if (entryData && entryData.scoreboard && entryData.scoreboard[0]) {
result = await updateScoreboardEntry({
variables: {
sbId: entryData.scoreboard[0].id,
sbInput: values,
},
});
} else {
result = await insertScoreboardEntry({
variables: { sbInput: [{ jobid: job.id, ...values }] },
});
}
if (!!result.errors) { if (!!result.errors) {
notification["error"]({ notification["error"]({
@@ -44,53 +86,62 @@ export default function ScoreboardAddButton({
const overlay = ( const overlay = (
<Card> <Card>
<div> <div>
<Form {entryLoading ? (
form={form} <LoadingSpinner />
layout="vertical" ) : (
onFinish={handleFinish} <Form
initialValues={{}} form={form}
> layout="vertical"
<Form.Item onFinish={handleFinish}
label={t("scoreboard.fields.date")} initialValues={{}}
name="date"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
> >
<FormDatePicker /> <Form.Item
</Form.Item> label={t("scoreboard.fields.date")}
<Form.Item name="date"
label={t("scoreboard.fields.bodyhrs")} rules={[
name="bodyhrs" {
rules={[ required: true,
{ //message: t("general.validation.required"),
required: true, },
//message: t("general.validation.required"), ]}
}, >
]} <FormDatePicker />
> </Form.Item>
<InputNumber precision={1} /> <Form.Item
</Form.Item> label={t("scoreboard.fields.bodyhrs")}
<Form.Item name="bodyhrs"
label={t("scoreboard.fields.painthrs")} rules={[
name="painthrs" {
rules={[ required: true,
{ //message: t("general.validation.required"),
required: true, },
//message: t("general.validation.required"), ]}
}, >
]} <InputNumber precision={1} />
> </Form.Item>
<InputNumber precision={1} /> <Form.Item
</Form.Item> label={t("scoreboard.fields.painthrs")}
name="painthrs"
rules={[
{
required: true,
//message: t("general.validation.required"),
},
]}
>
<InputNumber precision={1} />
</Form.Item>
<Button type="primary" htmlType="submit"> <Space wrap>
{t("general.actions.save")} <Button type="primary" htmlType="submit">
</Button> {t("general.actions.save")}
</Form> </Button>
<Button onClick={() => setVisibility(false)}>
{t("general.actions.cancel")}
</Button>
</Space>
</Form>
)}
</div> </div>
</Card> </Card>
); );
@@ -99,7 +150,7 @@ export default function ScoreboardAddButton({
setLoading(true); setLoading(true);
const v = job.joblines.reduce( const v = job.joblines.reduce(
(acc, val) => { (acc, val) => {
if (val.mod_lbr_ty === "LAB") if (val.mod_lbr_ty !== "LAR")
acc = { ...acc, bodyhrs: acc.bodyhrs + val.mod_lb_hrs }; acc = { ...acc, bodyhrs: acc.bodyhrs + val.mod_lb_hrs };
if (val.mod_lbr_ty === "LAR") if (val.mod_lbr_ty === "LAR")
acc = { ...acc, painthrs: acc.painthrs + val.mod_lb_hrs }; acc = { ...acc, painthrs: acc.painthrs + val.mod_lb_hrs };

View File

@@ -99,7 +99,7 @@ export function JobsAvailableContainer({
return; return;
} }
//IO-539 Check for Parts Rate on PAL for SGI use case. //IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(estData, bodyshop); await CheckTaxRates(estData.est_data, bodyshop);
const newTotals = ( const newTotals = (
await Axios.post("/job/totals", { await Axios.post("/job/totals", {
@@ -140,16 +140,17 @@ export function JobsAvailableContainer({
: {}), : {}),
}; };
if (selectedOwner) {
newJob.ownerid = selectedOwner;
delete newJob.owner;
}
if (newJob.vehicleid) {
delete newJob.vehicle;
}
insertNewJob({ insertNewJob({
variables: { variables: {
job: selectedOwner job: newJob,
? Object.assign(
{},
newJob,
{ owner: null },
{ ownerid: selectedOwner }
)
: newJob,
}, },
}) })
.then((r) => { .then((r) => {
@@ -199,11 +200,10 @@ export function JobsAvailableContainer({
message: t("jobs.errors.creating", { error: "No job data present." }), message: t("jobs.errors.creating", { error: "No job data present." }),
}); });
} else { } else {
//IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(estData, bodyshop);
//create upsert job //create upsert job
let supp = replaceEmpty({ ...estData.est_data }); let supp = replaceEmpty({ ...estData.est_data });
//IO-539 Check for Parts Rate on PAL for SGI use case.
await CheckTaxRates(supp, bodyshop);
delete supp.owner; delete supp.owner;
delete supp.vehicle; delete supp.vehicle;
@@ -391,101 +391,104 @@ function replaceEmpty(someObj, replaceValue = null) {
value === "" ? replaceValue || null : value; value === "" ? replaceValue || null : value;
//^ because you seem to want to replace (strings) "null" or "undefined" too //^ because you seem to want to replace (strings) "null" or "undefined" too
const temp = JSON.stringify(someObj, replacer); const temp = JSON.stringify(someObj, replacer);
console.log("Parsed", JSON.parse(temp));
return JSON.parse(temp); return JSON.parse(temp);
} }
async function CheckTaxRates(estData, bodyshop) { async function CheckTaxRates(estData, bodyshop) {
console.log(
"🚀 ~ file: jobs-available-table.container.jsx ~ line 398 ~ estData",
estData
);
//LKQ Check //LKQ Check
if ( if (
!estData.est_data.parts_tax_rates?.PAL || !estData.parts_tax_rates?.PAL ||
estData.est_data.parts_tax_rates?.PAL?.prt_tax_rt === null || estData.parts_tax_rates?.PAL?.prt_tax_rt === null ||
estData.est_data.parts_tax_rates?.PAL?.prt_tax_rt === 0 estData.parts_tax_rates?.PAL?.prt_tax_rt === 0
) { ) {
const res = await confirmDialog( const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.` `ImEX Online has detected that there is a missing tax rate for LKQ parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
); );
if (res) { if (res) {
if (!estData.est_data.parts_tax_rates.PAL) { if (!estData.parts_tax_rates.PAL) {
estData.est_data.parts_tax_rates.PAL = { estData.parts_tax_rates.PAL = {
prt_discp: 0, prt_discp: 0,
prt_mktyp: true, prt_mktyp: true,
prt_mkupp: 0, prt_mkupp: 0,
prt_type: "PAL", prt_type: "PAL",
}; };
} }
estData.est_data.parts_tax_rates.PAL.prt_tax_rt = estData.parts_tax_rates.PAL.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100; bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAL.prt_tax_in = true; estData.parts_tax_rates.PAL.prt_tax_in = true;
} }
} }
//PAC Check //PAC Check
if ( if (
!estData.est_data.parts_tax_rates?.PAC || !estData.parts_tax_rates?.PAC ||
estData.est_data.parts_tax_rates?.PAC?.prt_tax_rt === null || estData.parts_tax_rates?.PAC?.prt_tax_rt === null ||
estData.est_data.parts_tax_rates?.PAC?.prt_tax_rt === 0 estData.parts_tax_rates?.PAC?.prt_tax_rt === 0
) { ) {
const res = await confirmDialog( const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.` `ImEX Online has detected that there is a missing tax rate for rechromed parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
); );
if (res) { if (res) {
if (!estData.est_data.parts_tax_rates.PAC) { if (!estData.parts_tax_rates.PAC) {
estData.est_data.parts_tax_rates.PAC = { estData.parts_tax_rates.PAC = {
prt_discp: 0, prt_discp: 0,
prt_mktyp: true, prt_mktyp: true,
prt_mkupp: 0, prt_mkupp: 0,
prt_type: "PAC", prt_type: "PAC",
}; };
} }
estData.est_data.parts_tax_rates.PAC.prt_tax_rt = estData.parts_tax_rates.PAC.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100; bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAC.prt_tax_in = true; estData.parts_tax_rates.PAC.prt_tax_in = true;
} }
} }
//PAM Check //PAM Check
if ( if (
!estData.est_data.parts_tax_rates?.PAM || !estData.parts_tax_rates?.PAM ||
estData.est_data.parts_tax_rates?.PAM?.prt_tax_rt === null || estData.parts_tax_rates?.PAM?.prt_tax_rt === null ||
estData.est_data.parts_tax_rates?.PAM?.prt_tax_rt === 0 estData.parts_tax_rates?.PAM?.prt_tax_rt === 0
) { ) {
const res = await confirmDialog( const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.` `ImEX Online has detected that there is a missing tax rate for remanufactured parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
); );
if (res) { if (res) {
if (!estData.est_data.parts_tax_rates.PAM) { if (!estData.parts_tax_rates.PAM) {
estData.est_data.parts_tax_rates.PAM = { estData.parts_tax_rates.PAM = {
prt_discp: 0, prt_discp: 0,
prt_mktyp: true, prt_mktyp: true,
prt_mkupp: 0, prt_mkupp: 0,
prt_type: "PAM", prt_type: "PAM",
}; };
} }
estData.est_data.parts_tax_rates.PAM.prt_tax_rt = estData.parts_tax_rates.PAM.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100; bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAM.prt_tax_in = true; estData.parts_tax_rates.PAM.prt_tax_in = true;
} }
} }
if ( if (
!estData.est_data.parts_tax_rates?.PAR || !estData.parts_tax_rates?.PAR ||
estData.est_data.parts_tax_rates?.PAR?.prt_tax_rt === null || estData.parts_tax_rates?.PAR?.prt_tax_rt === null ||
estData.est_data.parts_tax_rates?.PAR?.prt_tax_rt === 0 estData.parts_tax_rates?.PAR?.prt_tax_rt === 0
) { ) {
const res = await confirmDialog( const res = await confirmDialog(
`ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.` `ImEX Online has detected that there is a missing tax rate for recored parts. Pressing OK will set the tax rate to ${bodyshop.bill_tax_rates.state_tax_rate}% and enable the rate. Pressing cancel will keep the tax rate as is.`
); );
if (res) { if (res) {
if (!estData.est_data.parts_tax_rates.PAR) { if (!estData.parts_tax_rates.PAR) {
estData.est_data.parts_tax_rates.PAR = { estData.parts_tax_rates.PAR = {
prt_discp: 0, prt_discp: 0,
prt_mktyp: true, prt_mktyp: true,
prt_mkupp: 0, prt_mkupp: 0,
prt_type: "PAR", prt_type: "PAR",
}; };
} }
estData.est_data.parts_tax_rates.PAR.prt_tax_rt = estData.parts_tax_rates.PAR.prt_tax_rt =
bodyshop.bill_tax_rates.state_tax_rate / 100; bodyshop.bill_tax_rates.state_tax_rate / 100;
estData.est_data.parts_tax_rates.PAR.prt_tax_in = true; estData.parts_tax_rates.PAR.prt_tax_in = true;
} }
} }
} }

View File

@@ -34,6 +34,7 @@ export function JobsCloseExportButton({
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleQbxml = async () => { const handleQbxml = async () => {
//Check if it's a CDK setup.
if (bodyshop.cdk_dealerid) { if (bodyshop.cdk_dealerid) {
history.push(`/manage/dms?jobId=${jobId}`); history.push(`/manage/dms?jobId=${jobId}`);
return; return;
@@ -41,48 +42,58 @@ export function JobsCloseExportButton({
logImEXEvent("jobs_close_export"); logImEXEvent("jobs_close_export");
setLoading(true); setLoading(true);
let QbXmlResponse; //Check if it's a QBO Setup.
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/receivables",
{ jobIds: [jobId] },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("jobs.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
setLoading(false);
return;
}
let PartnerResponse; let PartnerResponse;
try { if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post( PartnerResponse = await axios.post(`/qbo/receivables`, {
"http://localhost:1337/qb/", withCredentials: true,
// "http://609feaeae986.ngrok.io/qb/", jobIds: [jobId],
QbXmlResponse.data,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("jobs.errors.exporting-partner"),
}); });
setLoading(false); } else {
return; //Default is QBD
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/receivables",
{ jobIds: [jobId] },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
console.log("handle -> XML", QbXmlResponse);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("jobs.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
setLoading(false);
return;
}
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
// "http://609feaeae986.ngrok.io/qb/",
QbXmlResponse.data,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("jobs.errors.exporting-partner"),
});
setLoading(false);
return;
}
} }
console.log("PartnerResponse", PartnerResponse); console.log("PartnerResponse", PartnerResponse);

View File

@@ -104,7 +104,7 @@ export function JobsDetailHeaderCsi({
replyTo: bodyshop.email, replyTo: bodyshop.email,
}, },
template: { template: {
name: TemplateList("job").csi_invitation.key, name: TemplateList("job_special").csi_invitation_action.key,
variables: { variables: {
id: result.data.insert_csi.returning[0].id, id: result.data.insert_csi.returning[0].id,
}, },

View File

@@ -34,53 +34,61 @@ export function JobsExportAllButton({
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const handleQbxml = async () => { const handleQbxml = async () => {
logImEXEvent("jobs_export_all"); logImEXEvent("jobs_export_all");
setLoading(true);
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/receivables",
{ jobIds: jobIds },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("jobs.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
setLoading(false);
return;
}
let PartnerResponse; let PartnerResponse;
try { setLoading(true);
PartnerResponse = await axios.post( if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
"http://localhost:1337/qb/", PartnerResponse = await axios.post(`/qbo/receivables`, {
// "http://609feaeae986.ngrok.io/qb/", withCredentials: true,
QbXmlResponse.data, jobIds: jobIds,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("jobs.errors.exporting-partner"),
}); });
setLoading(false); } else {
return; let QbXmlResponse;
} try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/receivables",
{ jobIds: jobIds },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("jobs.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
setLoading(false);
return;
}
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
// "http://609feaeae986.ngrok.io/qb/",
QbXmlResponse.data,
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("jobs.errors.exporting-partner"),
});
setLoading(false);
return;
}
}
console.log("PartnerResponse", PartnerResponse); console.log("PartnerResponse", PartnerResponse);
const groupedData = _.groupBy(PartnerResponse.data, "id"); const groupedData = _.groupBy(
PartnerResponse.data,
bodyshop.accountingconfig.qbo ? "jobid" : "id"
);
await Promise.all( await Promise.all(
Object.keys(groupedData).map(async (key) => { Object.keys(groupedData).map(async (key) => {
@@ -157,6 +165,7 @@ export function JobsExportAllButton({
if (!!completedCallback) completedCallback([]); if (!!completedCallback) completedCallback([]);
if (!!loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };

View File

@@ -35,52 +35,61 @@ export function PayableExportAll({
const handleQbxml = async () => { const handleQbxml = async () => {
logImEXEvent("accounting_payables_export_all"); logImEXEvent("accounting_payables_export_all");
let PartnerResponse;
setLoading(true); setLoading(true);
if (!!loadingCallback) loadingCallback(true); if (!!loadingCallback) loadingCallback(true);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
let QbXmlResponse; PartnerResponse = await axios.post(`/qbo/receivables`, {
try { withCredentials: true,
QbXmlResponse = await axios.post( bills: billids,
"/accounting/qbxml/payables",
{ bills: billids },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("bills.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
}); });
if (loadingCallback) loadingCallback(false); } else {
setLoading(false); let QbXmlResponse;
return; try {
} QbXmlResponse = await axios.post(
"/accounting/qbxml/payables",
{ bills: billids },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("bills.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
let PartnerResponse; try {
PartnerResponse = await axios.post(
try { "http://localhost:1337/qb/",
PartnerResponse = await axios.post( QbXmlResponse.data
"http://localhost:1337/qb/", );
QbXmlResponse.data } catch (error) {
); console.log("Error connecting to quickbooks or partner.", error);
} catch (error) { notification["error"]({
console.log("Error connecting to quickbooks or partner.", error); message: t("bills.errors.exporting-partner"),
notification["error"]({ });
message: t("bills.errors.exporting-partner"), if (!!loadingCallback) loadingCallback(false);
}); setLoading(false);
if (!!loadingCallback) loadingCallback(false); return;
setLoading(false); }
return;
} }
console.log("handleQbxml -> PartnerResponse", PartnerResponse); console.log("handleQbxml -> PartnerResponse", PartnerResponse);
const groupedData = _.groupBy(PartnerResponse.data, "id"); const groupedData = _.groupBy(
PartnerResponse.data,
bodyshop.accountingconfig.qbo ? "billid" : "id"
);
const proms = []; const proms = [];
Object.keys(groupedData).forEach((key) => { Object.keys(groupedData).forEach((key) => {
proms.push( proms.push(

View File

@@ -38,44 +38,53 @@ export function PayableExportButton({
setLoading(true); setLoading(true);
if (!!loadingCallback) loadingCallback(true); if (!!loadingCallback) loadingCallback(true);
let QbXmlResponse; //Check if it's a QBO Setup.
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/payables",
{ bills: [billId] },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("bills.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
let PartnerResponse; let PartnerResponse;
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
try { PartnerResponse = await axios.post(`/qbo/payables`, {
PartnerResponse = await axios.post( withCredentials: true,
"http://localhost:1337/qb/", bills: [billId],
QbXmlResponse.data
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("bills.errors.exporting-partner"),
}); });
if (!!loadingCallback) loadingCallback(false); } else {
setLoading(false); //Default is QBD
return;
let QbXmlResponse;
try {
QbXmlResponse = await axios.post(
"/accounting/qbxml/payables",
{ bills: [billId] },
{
headers: {
Authorization: `Bearer ${await auth.currentUser.getIdToken()}`,
},
}
);
} catch (error) {
console.log("Error getting QBXML from Server.", error);
notification["error"]({
message: t("bills.errors.exporting", {
error: "Unable to retrieve QBXML. " + JSON.stringify(error.message),
}),
});
if (loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
try {
PartnerResponse = await axios.post(
"http://localhost:1337/qb/",
QbXmlResponse.data
);
} catch (error) {
console.log("Error connecting to quickbooks or partner.", error);
notification["error"]({
message: t("bills.errors.exporting-partner"),
});
if (!!loadingCallback) loadingCallback(false);
setLoading(false);
return;
}
} }
console.log("handleQbxml -> PartnerResponse", PartnerResponse); console.log("handleQbxml -> PartnerResponse", PartnerResponse);
@@ -123,7 +132,14 @@ export function PayableExportButton({
}); });
const billUpdateResponse = await updateBill({ const billUpdateResponse = await updateBill({
variables: { variables: {
billIdList: successfulTransactions.map((st) => st.id), billIdList: successfulTransactions.map(
(st) =>
st[
bodyshop.accountingconfig && bodyshop.accountingconfig.qbo
? "billid"
: "id"
]
),
bill: { bill: {
exported: true, exported: true,
exported_at: new Date(), exported_at: new Date(),

View File

@@ -1,28 +1,35 @@
import { Card, Col, Input, Row, Space, Typography } from "antd"; import { Card, Col, Input, Row, Space, Typography } from "antd";
import _ from "lodash"; import _ from "lodash";
import React, { useState } from "react"; import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { selectPrintCenter } from "../../redux/modals/modals.selectors"; import { selectPrintCenter } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import Jobd3RdPartyModal from "../job-3rd-party-modal/job-3rd-party-modal.component"; import Jobd3RdPartyModal from "../job-3rd-party-modal/job-3rd-party-modal.component";
import PrintCenterItem from "../print-center-item/print-center-item.component"; import PrintCenterItem from "../print-center-item/print-center-item.component";
import PrintCenterSpeedPrint from "../print-center-speed-print/print-center-speed-print.component"; import PrintCenterSpeedPrint from "../print-center-speed-print/print-center-speed-print.component";
import { useTranslation } from "react-i18next";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
printCenterModal: selectPrintCenter, printCenterModal: selectPrintCenter,
bodyshop: selectBodyshop,
}); });
const mapDispatchToProps = (dispatch) => ({}); const mapDispatchToProps = (dispatch) => ({});
export function PrintCenterJobsComponent({ printCenterModal }) { export function PrintCenterJobsComponent({ printCenterModal, bodyshop }) {
const [search, setSearch] = useState(""); const [search, setSearch] = useState("");
const { id: jobId } = printCenterModal.context; const { id: jobId } = printCenterModal.context;
const tempList = TemplateList("job", {}); const tempList = TemplateList("job", {});
const { t } = useTranslation(); const { t } = useTranslation();
const JobsReportsList = Object.keys(tempList).map((key) => { const JobsReportsList = Object.keys(tempList)
return tempList[key]; .map((key) => {
}); return tempList[key];
})
.filter(
(temp) =>
!temp.regions || (temp.regions && temp.regions[bodyshop.region_config])
);
const filteredJobsReportsList = const filteredJobsReportsList =
search !== "" search !== ""

View File

@@ -1,19 +1,18 @@
import { Button, Space } from "antd"; import { Space, Tag } from "antd";
import Axios from "axios"; import Axios from "axios";
import React, { useEffect } from "react";
//import QboImg from "./qbo_signin.png";
import queryString from "query-string"; import queryString from "query-string";
import { useLocation } from "react-router-dom"; import React, { useEffect } from "react";
import { useCookies } from "react-cookie"; import { useCookies } from "react-cookie";
import { useHistory, useLocation } from "react-router-dom";
import QboSignIn from "../../assets/qbo/C2QB_green_btn_med_default.svg";
export default function QboAuthorizeComponent() { export default function QboAuthorizeComponent() {
const location = useLocation(); const location = useLocation();
const history = useHistory();
const [, setCookie] = useCookies(["access_token", "refresh_token"]); const [cookies, setCookie] = useCookies(["access_token", "refresh_token"]);
const handleQbSignIn = async () => { const handleQbSignIn = async () => {
const result = await Axios.post("/qbo/authorize"); const result = await Axios.post("/qbo/authorize");
console.log("pushing to history", result.data);
window.location.href = result.data; window.location.href = result.data;
}; };
const qs = queryString.parse(location.search); const qs = queryString.parse(location.search);
@@ -35,42 +34,24 @@ export default function QboAuthorizeComponent() {
path: "/", path: "/",
expires, expires,
}); });
history.push({ pathname: `/manage/accounting/receivables` });
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [qs, location, setCookie]); }, [qs, location, setCookie]);
return ( return (
<div> <Space>
<Space wrap> <img
<Button onClick={handleQbSignIn}> onClick={handleQbSignIn}
{/* <img alt="Sign In to QuickBooks Online"
src={QboImg} src={QboSignIn}
alt="Sign in with Intuit" style={{ cursor: "pointer" }}
onClick={handleQbSignIn} />
/> */} {!cookies.qbo_realmId && (
auth <Tag color="red">No QuickBooks company has been connected.</Tag>
</Button> )}
<Button
onClick={async () => {
const response = await Axios.get(`/qbo/refresh`, {
withCredentials: true,
});
console.log(response);
}}
>
Refresh Token
</Button>
<Button
onClick={async () => {
const response = await Axios.post(`/qbo/receivables`, {
withCredentials: true,
});
console.log(response);
}}
>
REC
</Button>
</Space>
{error && JSON.parse(decodeURIComponent(error)).error_description} {error && JSON.parse(decodeURIComponent(error)).error_description}
</div> </Space>
); );
} }

View File

@@ -18,6 +18,11 @@ import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util"; import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
import _ from "lodash"; import _ from "lodash";
const graphProps = {
strokeWidth: 3,
};
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
}); });
@@ -51,7 +56,7 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
} }
const theValue = { const theValue = {
date: moment(val).format("D dd"), date: moment(val).format("D ddd"),
paintHrs: _.round(dayhrs.painthrs, 1), paintHrs: _.round(dayhrs.painthrs, 1),
bodyHrs: _.round(dayhrs.bodyhrs, 1), bodyHrs: _.round(dayhrs.bodyhrs, 1),
accTargetHrs: _.round( accTargetHrs: _.round(
@@ -81,36 +86,37 @@ export function ScoreboardChart({ sbEntriesByDate, bodyshop }) {
margin={{ top: 20, right: 20, bottom: 20, left: 20 }} margin={{ top: 20, right: 20, bottom: 20, left: 20 }}
> >
<CartesianGrid stroke="#f5f5f5" /> <CartesianGrid stroke="#f5f5f5" />
<XAxis dataKey="date" /> <XAxis dataKey="date" strokeWidth={graphProps.strokeWidth} />
<YAxis /> <YAxis strokeWidth={graphProps.strokeWidth} />
<Tooltip /> <Tooltip />
<Legend /> <Legend />
<Area <Area
type="monotone" type="monotone"
name="Accumulated Hours" name="Accumulated Hours"
dataKey="accHrs" dataKey="accHrs"
fill="#8884d8" fill="lightgreen"
stroke="#8884d8" stroke="green"
/> />
<Bar <Bar
name="Body Hours" name="Body Hours"
dataKey="bodyHrs" dataKey="bodyHrs"
stackId="day" stackId="day"
barSize={20} barSize={20}
fill="#cecece" fill="darkblue"
/> />
<Bar <Bar
name="Paint Hours" name="Paint Hours"
dataKey="paintHrs" dataKey="paintHrs"
stackId="day" stackId="day"
barSize={20} barSize={20}
fill="#413ea0" fill="darkred"
/> />
<Line <Line
name="Target Hours" name="Target Hours"
type="monotone" type="monotone"
dataKey="accTargetHrs" dataKey="accTargetHrs"
stroke="#ff7300" stroke="#ff7300"
strokeWidth={graphProps.strokeWidth}
/> />
</ComposedChart> </ComposedChart>
</ResponsiveContainer> </ResponsiveContainer>

View File

@@ -1,12 +1,33 @@
import { Col, Row } from "antd"; import { Col, Row } from "antd";
import React from "react"; import React, { useEffect } from "react";
import ScoreboardChart from "../scoreboard-chart/scoreboard-chart.component"; import ScoreboardChart from "../scoreboard-chart/scoreboard-chart.component";
import ScoreboardLastDays from "../scoreboard-last-days/scoreboard-last-days.component"; import ScoreboardLastDays from "../scoreboard-last-days/scoreboard-last-days.component";
import ScoreboardTargetsTable from "../scoreboard-targets-table/scoreboard-targets-table.component"; import ScoreboardTargetsTable from "../scoreboard-targets-table/scoreboard-targets-table.component";
export default function ScoreboardDisplayComponent({ scoreboardSubscription }) { import { connect } from "react-redux";
const { data } = scoreboardSubscription; import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import moment from "moment";
import { useApolloClient } from "@apollo/client";
import { GET_BLOCKED_DAYS } from "../../graphql/scoreboard.queries";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(
mapStateToProps,
mapDispatchToProps
)(ScoreboardDisplayComponent);
export function ScoreboardDisplayComponent({
bodyshop,
scoreboardSubscription,
}) {
const { data } = scoreboardSubscription;
const client = useApolloClient();
const scoreBoardlist = (data && data.scoreboard) || []; const scoreBoardlist = (data && data.scoreboard) || [];
const sbEntriesByDate = {}; const sbEntriesByDate = {};
@@ -19,6 +40,29 @@ export default function ScoreboardDisplayComponent({ scoreboardSubscription }) {
sbEntriesByDate[entryDate].push(i); sbEntriesByDate[entryDate].push(i);
}); });
useEffect(() => {
//Update the locals.
async function setMomentSettings() {
const {
data: { appointments },
} = await client.query({
query: GET_BLOCKED_DAYS,
variables: {
start: moment().startOf("month"),
end: moment().endOf("month"),
},
});
moment.updateLocale("ca", {
workingWeekdays: translateSettingsToWorkingDays(bodyshop.workingdays),
holidays: appointments.map((h) => moment(h.start).format("MM-DD-YYYY")),
holidayFormat: "MM-DD-YYYY",
});
}
setMomentSettings();
}, [client, bodyshop]);
return ( return (
<Row gutter={[16, 16]}> <Row gutter={[16, 16]}>
<Col span={24}> <Col span={24}>
@@ -35,3 +79,30 @@ export default function ScoreboardDisplayComponent({ scoreboardSubscription }) {
</Row> </Row>
); );
} }
function translateSettingsToWorkingDays(workingdays) {
const days = [];
if (workingdays.monday) {
days.push(1);
}
if (workingdays.tuesday) {
days.push(2);
}
if (workingdays.wednesday) {
days.push(3);
}
if (workingdays.thursday) {
days.push(4);
}
if (workingdays.friday) {
days.push(5);
}
if (workingdays.saturday) {
days.push(6);
}
if (workingdays.sunday) {
days.push(0);
}
return days;
}

View File

@@ -1,8 +1,8 @@
import moment from "moment-business-days"; import moment from "moment-business-days";
moment.updateLocale("ca", { // moment.updateLocale("ca", {
workingWeekdays: [1, 2, 3, 4, 5], // workingWeekdays: [1, 2, 3, 4, 5, 6],
}); // });
export const CalculateWorkingDaysThisMonth = () => { export const CalculateWorkingDaysThisMonth = () => {
return moment().endOf("month").businessDaysIntoMonth(); return moment().endOf("month").businessDaysIntoMonth();

View File

@@ -121,6 +121,13 @@ export default function ShopInfoGeneral({ form }) {
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow header={t("bodyshop.labels.accountingsetup")}> <LayoutFormRow header={t("bodyshop.labels.accountingsetup")}>
<Form.Item
label={t("bodyshop.labels.qbo")}
valuePropName="checked"
name={["accountingconfig", "qbo"]}
>
<Switch />
</Form.Item>
<Form.Item <Form.Item
label={t("bodyshop.labels.accountingtiers")} label={t("bodyshop.labels.accountingtiers")}
rules={[ rules={[

View File

@@ -63,12 +63,12 @@ export const logImEXEvent = (eventName, additionalParams, stateProp = null) => {
null, null,
...additionalParams, ...additionalParams,
}; };
console.log( // console.log(
"%c[Analytics]", // "%c[Analytics]",
"background-color: green ;font-weight:bold;", // "background-color: green ;font-weight:bold;",
eventName, // eventName,
eventParams // eventParams
); // );
logEvent(analytics, eventName, eventParams); logEvent(analytics, eventName, eventParams);
//Log event to OpenReplay server. //Log event to OpenReplay server.

View File

@@ -77,6 +77,7 @@ export const QUERY_PARTS_QUEUE = gql`
) { ) {
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_co_nm
ownr_ph1 ownr_ph1
ownr_ea ownr_ea
plate_no plate_no
@@ -98,6 +99,7 @@ export const QUERY_PARTS_QUEUE = gql`
status status
updated_at updated_at
vehicleid vehicleid
ownerid
} }
} }
`; `;
@@ -110,6 +112,7 @@ export const SUBSCRIPTION_JOBS_IN_PRODUCTION = gql`
ro_number ro_number
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_co_nm
v_model_yr v_model_yr
v_model_desc v_model_desc
clm_no clm_no
@@ -540,6 +543,7 @@ export const GET_JOB_BY_PK = gql`
db_ref db_ref
manual_line manual_line
prt_dsmk_p prt_dsmk_p
prt_dsmk_m
billlines(limit: 1, order_by: { bill: { date: desc } }) { billlines(limit: 1, order_by: { bill: { date: desc } }) {
id id
quantity quantity
@@ -1636,6 +1640,7 @@ export const QUERY_ALL_JOBS_PAGINATED_STATUS_FILTERED = gql`
) { ) {
ownr_fn ownr_fn
ownr_ln ownr_ln
ownr_co_nm
ownerid ownerid
ownr_ph1 ownr_ph1
ownr_ea ownr_ea

View File

@@ -51,3 +51,34 @@ export const UPDATE_SCOREBOARD_ENTRY = gql`
} }
} }
`; `;
export const QUERY_SCOREBOARD_ENTRY = gql`
query QUERY_SCOREBOARD_ENTRY($jobid: uuid!) {
scoreboard(where: { jobid: { _eq: $jobid } }) {
bodyhrs
date
id
painthrs
}
}
`;
export const GET_BLOCKED_DAYS = gql`
query GET_BLOCKED_DAYS($start: timestamptz, $end: timestamptz) {
appointments(
where: {
_and: [
{ block: { _eq: true } }
{ canceled: { _eq: false } }
{ start: { _gte: $start } }
{ end: { _lte: $end } }
]
}
) {
id
block
start
end
}
}
`;

View File

@@ -85,7 +85,6 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
}); });
}; };
console.log("Manual State", state);
const handleFinish = (values) => { const handleFinish = (values) => {
let job = Object.assign( let job = Object.assign(
{}, {},
@@ -142,6 +141,10 @@ function JobsCreateContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
} }
job = { ...job, ...ownerData }; job = { ...job, ...ownerData };
if (job.owner === null) delete job.owner;
if (job.vehicle === null) delete job.vehicle;
runInsertJob(job); runInsertJob(job);
}; };

View File

@@ -116,8 +116,8 @@ export function PartsQueuePageComponent({ bodyshop }) {
// sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln), // sorter: (a, b) => alphaSort(a.ownr_ln, b.ownr_ln),
// sortOrder: sortcolumn === "owner" && sortorder, // sortOrder: sortcolumn === "owner" && sortorder,
render: (text, record) => { render: (text, record) => {
return record.owner ? ( return record.ownerid ? (
<Link to={"/manage/owners/" + record.owner.id}> <Link to={"/manage/owners/" + record.ownerid}>
{`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${ {`${record.ownr_fn || ""} ${record.ownr_ln || ""} ${
record.ownr_co_nm || "" record.ownr_co_nm || ""
}`} }`}

View File

@@ -509,6 +509,7 @@
"orderstatuses": "Order Statuses", "orderstatuses": "Order Statuses",
"partslocations": "Parts Locations", "partslocations": "Parts Locations",
"printlater": "Print Later", "printlater": "Print Later",
"qbo": "Use QuickBooks Online?",
"rbac": "Role Based Access Control", "rbac": "Role Based Access Control",
"responsibilitycenters": { "responsibilitycenters": {
"costs": "Cost Centers", "costs": "Cost Centers",
@@ -1952,6 +1953,7 @@
"coversheet_landscape": "Coversheet (Landscape)", "coversheet_landscape": "Coversheet (Landscape)",
"coversheet_portrait": "Coversheet Portrait", "coversheet_portrait": "Coversheet Portrait",
"csi_invitation": "CSI Invitation", "csi_invitation": "CSI Invitation",
"csi_invitation_action": "CSI Invite",
"diagnostic_authorization": "Diagnostic Authorization", "diagnostic_authorization": "Diagnostic Authorization",
"estimate": "Estimate Only", "estimate": "Estimate Only",
"estimate_detail": "Estimate Details", "estimate_detail": "Estimate Details",
@@ -1982,6 +1984,8 @@
"qc_sheet": "Quality Control Sheet", "qc_sheet": "Quality Control Sheet",
"ro_totals": "RO Totals", "ro_totals": "RO Totals",
"ro_with_description": "RO Summary with Descriptions", "ro_with_description": "RO Summary with Descriptions",
"sgi_certificate_of_repairs": "SGI - Certificate of Repairs",
"sgi_windshield_auth": "SGI - Windshield Authorization",
"stolen_recovery_checklist": "Stolen Recovery Checklist", "stolen_recovery_checklist": "Stolen Recovery Checklist",
"supplement_request": "Supplement Request", "supplement_request": "Supplement Request",
"thank_you_ro": "Thank You Letter", "thank_you_ro": "Thank You Letter",

View File

@@ -509,6 +509,7 @@
"orderstatuses": "", "orderstatuses": "",
"partslocations": "", "partslocations": "",
"printlater": "", "printlater": "",
"qbo": "",
"rbac": "", "rbac": "",
"responsibilitycenters": { "responsibilitycenters": {
"costs": "", "costs": "",
@@ -1952,6 +1953,7 @@
"coversheet_landscape": "", "coversheet_landscape": "",
"coversheet_portrait": "", "coversheet_portrait": "",
"csi_invitation": "", "csi_invitation": "",
"csi_invitation_action": "",
"diagnostic_authorization": "", "diagnostic_authorization": "",
"estimate": "", "estimate": "",
"estimate_detail": "", "estimate_detail": "",
@@ -1982,6 +1984,8 @@
"qc_sheet": "", "qc_sheet": "",
"ro_totals": "", "ro_totals": "",
"ro_with_description": "", "ro_with_description": "",
"sgi_certificate_of_repairs": "",
"sgi_windshield_auth": "",
"stolen_recovery_checklist": "", "stolen_recovery_checklist": "",
"supplement_request": "", "supplement_request": "",
"thank_you_ro": "", "thank_you_ro": "",

View File

@@ -509,6 +509,7 @@
"orderstatuses": "", "orderstatuses": "",
"partslocations": "", "partslocations": "",
"printlater": "", "printlater": "",
"qbo": "",
"rbac": "", "rbac": "",
"responsibilitycenters": { "responsibilitycenters": {
"costs": "", "costs": "",
@@ -1952,6 +1953,7 @@
"coversheet_landscape": "", "coversheet_landscape": "",
"coversheet_portrait": "", "coversheet_portrait": "",
"csi_invitation": "", "csi_invitation": "",
"csi_invitation_action": "",
"diagnostic_authorization": "", "diagnostic_authorization": "",
"estimate": "", "estimate": "",
"estimate_detail": "", "estimate_detail": "",
@@ -1982,6 +1984,8 @@
"qc_sheet": "", "qc_sheet": "",
"ro_totals": "", "ro_totals": "",
"ro_with_description": "", "ro_with_description": "",
"sgi_certificate_of_repairs": "",
"sgi_windshield_auth": "",
"stolen_recovery_checklist": "", "stolen_recovery_checklist": "",
"supplement_request": "", "supplement_request": "",
"thank_you_ro": "", "thank_you_ro": "",

View File

@@ -38,9 +38,9 @@ const roundTripLink = new ApolloLink((operation, forward) => {
return forward(operation).map((data) => { return forward(operation).map((data) => {
// Called after server responds // Called after server responds
const time = new Date() - operation.getContext().start; const time = new Date() - operation.getContext().start;
console.log( // console.log(
`Operation ${operation.operationName} took ${time} to complete` // `Operation ${operation.operationName} took ${time} to complete`
); // );
TrackExecutionTime(operation.operationName, time); TrackExecutionTime(operation.operationName, time);
return data; return data;
}); });

View File

@@ -352,6 +352,28 @@ export const TemplateList = (type, context) => {
disabled: false, disabled: false,
group: "ro", group: "ro",
}, },
sgi_certificate_of_repairs: {
title: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"),
description: "Thank You Letter by RO",
key: "sgi_certificate_of_repairs",
subject: i18n.t("printcenter.jobs.sgi_certificate_of_repairs"),
disabled: false,
group: "ro",
regions: {
CA_SK: true,
},
},
sgi_windshield_auth: {
title: i18n.t("printcenter.jobs.sgi_windshield_auth"),
description: "Thank You Letter by RO",
key: "sgi_windshield_auth",
subject: i18n.t("printcenter.jobs.sgi_windshield_auth"),
disabled: false,
group: "pre",
regions: {
CA_SK: true,
},
},
// parts_label_multi: { // parts_label_multi: {
// title: i18n.t("printcenter.jobs.parts_label_multi"), // title: i18n.t("printcenter.jobs.parts_label_multi"),
// description: "Thank You Letter by RO", // description: "Thank You Letter by RO",
@@ -370,6 +392,13 @@ export const TemplateList = (type, context) => {
key: "special_thirdpartypayer", key: "special_thirdpartypayer",
disabled: false, disabled: false,
}, },
csi_invitation_action: {
title: i18n.t("printcenter.jobs.csi_invitation_action"),
description: "CSI invite",
key: "csi_invitation_action",
subject: i18n.t("printcenter.jobs.csi_invitation_action"),
disabled: false,
},
} }
: {}), : {}),
...(!type || type === "appointment" ...(!type || type === "appointment"

File diff suppressed because it is too large Load Diff

View File

@@ -1,2 +1,7 @@
version: 2
endpoint: https://bodyshop-dev-db.herokuapp.com endpoint: https://bodyshop-dev-db.herokuapp.com
admin_secret: Dev-BodyShopApp! admin_secret: Dev-BodyShopApp!
metadata_directory: metadata
actions:
kind: synchronous
handler_webhook_baseurl: http://localhost:3000

View File

View File

@@ -0,0 +1,6 @@
actions: []
custom_types:
enums: []
input_objects: []
objects: []
scalars: []

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1,27 @@
- function:
schema: public
name: search_bills
- function:
schema: public
name: search_cccontracts
- function:
schema: public
name: search_dms_vehicles
- function:
schema: public
name: search_exportlog
- function:
schema: public
name: search_jobs
- function:
schema: public
name: search_owners
- function:
schema: public
name: search_payments
- function:
schema: public
name: search_phonebook
- function:
schema: public
name: search_vehicles

View File

@@ -0,0 +1 @@
[]

View File

@@ -0,0 +1 @@
[]

4775
hasura/metadata/tables.yaml Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1 @@
version: 2

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" DROP COLUMN "website";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" ADD COLUMN "website" text NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."documents" DROP COLUMN "takenat";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."documents" ADD COLUMN "takenat" timestamptz NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" ADD CONSTRAINT "bodyshops_autohouseid_key" UNIQUE ("autohouseid");

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" DROP CONSTRAINT "bodyshops_autohouseid_key";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" DROP COLUMN "jc_hourly_rates";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" ADD COLUMN "jc_hourly_rates" jsonb NULL DEFAULT jsonb_build_object();

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."vehicles" ALTER COLUMN "v_vin" SET NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."vehicles" ALTER COLUMN "v_vin" DROP NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE ONLY "public"."joblines" ALTER COLUMN "prt_dsmk_p" DROP DEFAULT;

View File

@@ -0,0 +1,2 @@
ALTER TABLE ONLY "public"."joblines" ALTER COLUMN "prt_dsmk_p" SET DEFAULT 0;

View File

@@ -0,0 +1,3 @@
update joblines
set prt_dsmk_p = 0 where joblines.prt_dsmk_p is null;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."conversations" DROP COLUMN "archived";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."conversations" ADD COLUMN "archived" boolean NOT NULL DEFAULT false;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."parts_orders" DROP COLUMN "orderedby";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."parts_orders" ADD COLUMN "orderedby" text NULL;

View File

@@ -0,0 +1,2 @@
alter table "public"."parts_orders" drop constraint "parts_orders_orderedby_fkey";

View File

@@ -0,0 +1,6 @@
alter table "public"."parts_orders"
add constraint "parts_orders_orderedby_fkey"
foreign key ("orderedby")
references "public"."users"
("email") on update set null on delete set null;

View File

@@ -0,0 +1,8 @@
alter table "public"."exportlog" drop constraint "exportlog_billid_fkey",
add constraint "exportlog_billid_fkey"
foreign key ("billid")
references "public"."bills"
("id")
on update restrict
on delete restrict;

View File

@@ -0,0 +1,6 @@
alter table "public"."exportlog" drop constraint "exportlog_billid_fkey",
add constraint "exportlog_billid_fkey"
foreign key ("billid")
references "public"."bills"
("id") on update cascade on delete cascade;

View File

@@ -0,0 +1,3 @@
ALTER TABLE ONLY "public"."users" ALTER COLUMN "dashboardlayout" SET DEFAULT jsonb_build_array();
ALTER TABLE "public"."users" ALTER COLUMN "dashboardlayout" SET NOT NULL;

View File

@@ -0,0 +1,3 @@
ALTER TABLE "public"."users" ALTER COLUMN "dashboardlayout" DROP DEFAULT;
ALTER TABLE "public"."users" ALTER COLUMN "dashboardlayout" DROP NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" DROP COLUMN "md_jobline_presets";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" ADD COLUMN "md_jobline_presets" jsonb NULL DEFAULT jsonb_build_array();

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" DROP COLUMN "cdk_dealerid";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" ADD COLUMN "cdk_dealerid" text NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" DROP COLUMN "features";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" ADD COLUMN "features" jsonb NULL DEFAULT jsonb_build_object();

View File

@@ -0,0 +1,30 @@
CREATE OR REPLACE FUNCTION public.search_payments(search text)
RETURNS SETOF payments
LANGUAGE plpgsql
STABLE
AS $function$
BEGIN
if search = '' then
return query select * from payments ;
else
return query SELECT
p.*
FROM
payments p, jobs j
WHERE
p.jobid = j.id AND
(
search <% p.paymentnum OR
search <% j.ownr_fn OR
search <% j.ownr_ln OR
search <% j.ownr_co_nm OR
search <% j.ro_number OR
search <% (p.payer) OR
search <% (p.transactionid) OR
search <% (p.memo));
end if;
END
$function$;

View File

@@ -0,0 +1,2 @@
alter table "public"."vehicles" add constraint "vehicles_v_vin_shopid_key" unique ("v_vin", "shopid");

View File

@@ -0,0 +1,2 @@
alter table "public"."vehicles" drop constraint "vehicles_v_vin_shopid_key";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" DROP COLUMN "attach_pdf_to_email";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."bodyshops" ADD COLUMN "attach_pdf_to_email" boolean NOT NULL DEFAULT False;

View File

@@ -0,0 +1,3 @@
ALTER TABLE "public"."audit_trail" ADD COLUMN "schemaname" text;
ALTER TABLE "public"."audit_trail" ALTER COLUMN "schemaname" DROP NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."audit_trail" DROP COLUMN "schemaname" CASCADE;

View File

@@ -0,0 +1,3 @@
ALTER TABLE "public"."audit_trail" ADD COLUMN "tabname" text;
ALTER TABLE "public"."audit_trail" ALTER COLUMN "tabname" DROP NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."audit_trail" DROP COLUMN "tabname" CASCADE;

View File

@@ -0,0 +1,3 @@
ALTER TABLE "public"."audit_trail" ADD COLUMN "recordid" uuid;
ALTER TABLE "public"."audit_trail" ALTER COLUMN "recordid" DROP NOT NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."audit_trail" DROP COLUMN "recordid" CASCADE;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."audit_trail" DROP COLUMN "jobid";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."audit_trail" ADD COLUMN "jobid" uuid NULL;

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."audit_trail" DROP COLUMN "billid";

View File

@@ -0,0 +1,2 @@
ALTER TABLE "public"."audit_trail" ADD COLUMN "billid" uuid NULL;

View File

@@ -0,0 +1,2 @@
alter table "public"."audit_trail" drop constraint "audit_trail_billid_fkey";

View File

@@ -0,0 +1,6 @@
alter table "public"."audit_trail"
add constraint "audit_trail_billid_fkey"
foreign key ("billid")
references "public"."bills"
("id") on update cascade on delete cascade;

View File

@@ -0,0 +1,8 @@
alter table "public"."audit_trail" drop constraint "audit_trail_billid_fkey",
add constraint "audit_trail_billid_fkey"
foreign key ("billid")
references "public"."bills"
("id")
on update cascade
on delete cascade;

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