Compare commits

..

1 Commits

Author SHA1 Message Date
Dave Richer
2c8f5c7184 feature/IO-3060-Realtime-Notification-System: Weird sql Lind endings 2025-01-15 11:11:03 -08:00
46 changed files with 11347 additions and 11942 deletions

1
.gitattributes vendored
View File

@@ -67,6 +67,7 @@
*.rb text eol=lf *.rb text eol=lf
*.java text eol=lf *.java text eol=lf
*.php text eol=lf *.php text eol=lf
*.sql text eol=lf
# Git configuration files # Git configuration files
.gitattributes text eol=lf .gitattributes text eol=lf

View File

@@ -6369,27 +6369,6 @@
<folder_node> <folder_node>
<name>md_parts_scan</name> <name>md_parts_scan</name>
<children> <children>
<concept_node>
<name>caseInsensitive</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>expression</name> <name>expression</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -6411,27 +6390,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>field</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>flags</name> <name>flags</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -6453,48 +6411,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>operation</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>value</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> </children>
</folder_node> </folder_node>
<concept_node> <concept_node>
@@ -12071,158 +11987,6 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </folder_node>
<folder_node>
<name>operations</name>
<children>
<concept_node>
<name>contains</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>ends_with</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>equals</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>greater_than</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>less_than</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>not_equals</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>starts_with</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
</children>
</folder_node>
<folder_node> <folder_node>
<name>successes</name> <name>successes</name>
<children> <children>
@@ -23367,27 +23131,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>alt_partno</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>amount</name> <name>amount</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -23493,27 +23236,6 @@
</translation> </translation>
</translations> </translations>
</concept_node> </concept_node>
<concept_node>
<name>include_in_part_cnt</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<folder_node> <folder_node>
<name>lbr_types</name> <name>lbr_types</name>
<children> <children>

View File

@@ -62,17 +62,7 @@
t = d.getElementsByTagName("script")[0]; t = d.getElementsByTagName("script")[0];
t.parentNode.insertBefore(s, t); t.parentNode.insertBefore(s, t);
</script> </script>
<script type="text/javascript">
window.$crisp = [];
window.CRISP_WEBSITE_ID = "36724f62-2eb0-4b29-9cdd-9905fb99913e";
(function () {
d = document;
s = d.createElement("script");
s.src = "https://client.crisp.chat/l.js";
s.async = 1;
d.getElementsByTagName("head")[0].appendChild(s);
})();
</script>
<% } %> <% } %>
<script> <script>
!(function () { !(function () {

View File

@@ -85,17 +85,6 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
sortOrder: state.sortedInfo.columnKey === "amount" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "amount" && state.sortedInfo.order,
render: (text, record) => <CurrencyFormatter>{record.amount}</CurrencyFormatter> render: (text, record) => <CurrencyFormatter>{record.amount}</CurrencyFormatter>
}, },
{
title: t("payments.fields.type"),
dataIndex: "type",
key: "type",
sorter: (a, b) => a.type.localeCompare(b.type),
sortOrder: state.sortedInfo.columnKey === "type" && state.sortedInfo.order,
filters: bodyshop.md_payment_types.map((s) => {
return { text: s, value: [s] };
}),
onFilter: (value, record) => value.includes(record.type)
},
{ {
title: t("payments.fields.memo"), title: t("payments.fields.memo"),
dataIndex: "memo", dataIndex: "memo",

View File

@@ -133,19 +133,15 @@ const CardPaymentModalComponent = ({
if (window.intellipay) { if (window.intellipay) {
// eslint-disable-next-line no-eval // eslint-disable-next-line no-eval
eval(response.data); eval(response.data);
pollForIntelliPay(() => { SetIntellipayCallbackFunctions();
SetIntellipayCallbackFunctions(); window.intellipay.autoOpen();
window.intellipay.autoOpen();
});
} else { } else {
const rg = document.createRange(); const rg = document.createRange();
const node = rg.createContextualFragment(response.data); const node = rg.createContextualFragment(response.data);
document.documentElement.appendChild(node); document.documentElement.appendChild(node);
pollForIntelliPay(() => { SetIntellipayCallbackFunctions();
SetIntellipayCallbackFunctions(); window.intellipay.isAutoOpen = true;
window.intellipay.isAutoOpen = true; window.intellipay.initialize();
window.intellipay.initialize();
});
} }
} catch (error) { } catch (error) {
notification.open({ notification.open({
@@ -349,27 +345,3 @@ const CardPaymentModalComponent = ({
}; };
export default connect(mapStateToProps, mapDispatchToProps)(CardPaymentModalComponent); export default connect(mapStateToProps, mapDispatchToProps)(CardPaymentModalComponent);
//Poll for window.IntelliPay.fixAmount for 5 seconds. If it doesn't come up, just try anyways to force the possible error.
function pollForIntelliPay(callbackFunction) {
const timeout = 5000;
const interval = 150; // Poll every 100 milliseconds
const startTime = Date.now();
function checkFixAmount() {
if (window.intellipay && window.intellipay.fixAmount !== undefined) {
callbackFunction();
return;
}
if (Date.now() - startTime >= timeout) {
console.log("Stopped polling IntelliPay after 10 seconds. Attemping to set functions anyways.");
callbackFunction();
return;
}
setTimeout(checkFixAmount, interval);
}
checkFixAmount();
}

View File

@@ -6,7 +6,7 @@ import { createStructuredSelector } from "reselect";
import { toggleModalVisible } from "../../redux/modals/modals.actions"; import { toggleModalVisible } from "../../redux/modals/modals.actions";
import { selectCardPayment } from "../../redux/modals/modals.selectors"; import { selectCardPayment } from "../../redux/modals/modals.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CardPaymentModalComponent from "./card-payment-modal.component"; import CardPaymentModalComponent from "./card-payment-modal.component.";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
cardPaymentModal: selectCardPayment, cardPaymentModal: selectCardPayment,

View File

@@ -1,10 +1,10 @@
import * as Sentry from "@sentry/react";
import { Button, Col, Collapse, Result, Row, Space } from "antd"; import { Button, Col, Collapse, Result, Row, Space } from "antd";
import React from "react"; import React from "react";
import { withTranslation } from "react-i18next"; import { withTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import * as Sentry from "@sentry/react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors"; import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
@@ -38,23 +38,28 @@ class ErrorBoundary extends React.Component {
} }
handleErrorSubmit = () => { handleErrorSubmit = () => {
window.$crisp.push([ InstanceRenderManager({
"do", executeFunction: true,
"message:send", args: [],
[ imex: () => {
"text", window.$crisp.push([
`I hit the following error: \n\n "do",
"message:send",
[
"text",
`I hit the following error: \n\n
${this.state.error.message}\n\n ${this.state.error.message}\n\n
${this.state.error.stack}\n\n ${this.state.error.stack}\n\n
URL:${window.location} as ${this.props.currentUser.email} for ${ URL:${window.location} as ${this.props.currentUser.email} for ${
this.props.bodyshop && this.props.bodyshop.name this.props.bodyshop && this.props.bodyshop.name
} }
` `
] ]
]); ]);
window.$crisp.push(["do", "chat:open"]);
window.$crisp.push(["do", "chat:open"]);
}
});
// const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue** // const errorDescription = `**Please add relevant details about what you were doing before you encountered this issue**
// ---- // ----

View File

@@ -650,7 +650,14 @@ function Header({
icon: <Icon component={QuestionCircleFilled} />, icon: <Icon component={QuestionCircleFilled} />,
label: t("menus.header.help"), label: t("menus.header.help"),
onClick: () => { onClick: () => {
window.open("https://help.imex.online/", "_blank"); window.open(
InstanceRenderManager({
imex: "https://help.imex.online/",
rome: "https://rometech.com//"
}),
"_blank"
);
} }
}, },
...(InstanceRenderManager({ ...(InstanceRenderManager({

View File

@@ -1,30 +1,30 @@
import { AlertFilled } from "@ant-design/icons"; import { AlertFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, Divider, Dropdown, Form, Input, notification, Popover, Select, Space } from "antd"; import { Button, Divider, Dropdown, Form, Input, notification, Popover, Select, Space } from "antd";
import parsePhoneNumber from "libphonenumber-js"; import parsePhoneNumber from "libphonenumber-js";
import dayjs from "../../utils/day";
import queryString from "query-string"; import queryString from "query-string";
import React, { useContext, useState } from "react"; import React, { useContext, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom"; import { Link, useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions"; import { openChatByPhone, setMessage } from "../../redux/messaging/messaging.actions";
import { setModalContext } from "../../redux/modals/modals.actions"; import { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import dayjs from "../../utils/day";
import { GenerateDocument } from "../../utils/RenderTemplate"; import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants"; import { TemplateList } from "../../utils/TemplateConstants";
import ChatOpenButton from "../chat-open-button/chat-open-button.component"; import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.component"; import DataLabel from "../data-label/data-label.component";
import OwnerNameDisplay from "../owner-name-display/owner-name-display.component"; import OwnerNameDisplay from "../owner-name-display/owner-name-display.component";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component"; import ScheduleManualEvent from "../schedule-manual-event/schedule-manual-event.component";
import ScheduleAtChange from "./job-at-change.component"; import ScheduleAtChange from "./job-at-change.component";
import ScheduleEventColor from "./schedule-event.color.component"; import ScheduleEventColor from "./schedule-event.color.component";
import ScheduleEventNote from "./schedule-event.note.component"; import ScheduleEventNote from "./schedule-event.note.component";
import { useMutation } from "@apollo/client";
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop bodyshop: selectBodyshop
@@ -127,7 +127,6 @@ export function ScheduleEventComponent({
<DataLabel label={t("jobs.fields.ownr_ph2")}> <DataLabel label={t("jobs.fields.ownr_ph2")}>
<ChatOpenButton phone={event.job && event.job.ownr_ph2} jobid={event.job.id} /> <ChatOpenButton phone={event.job && event.job.ownr_ph2} jobid={event.job.id} />
</DataLabel> </DataLabel>
<DataLabel hideIfNull label={t("jobs.fields.loss_of_use")}>{(event.job && event.job.loss_of_use) || ""}</DataLabel>
<DataLabel label={t("jobs.fields.alt_transport")}> <DataLabel label={t("jobs.fields.alt_transport")}>
{(event.job && event.job.alt_transport) || ""} {(event.job && event.job.alt_transport) || ""}
<ScheduleAtChange job={event && event.job} /> <ScheduleAtChange job={event && event.job} />

View File

@@ -519,7 +519,6 @@ export function JobLinesComponent({
{selectedLines.length > 0 && ` (${selectedLines.length})`} {selectedLines.length > 0 && ` (${selectedLines.length})`}
</Button> </Button>
<Button <Button
id="job-lines-order-parts-button"
disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician} disabled={(job && !job.converted) || (selectedLines.length > 0 ? false : true) || jobRO || technician}
onClick={() => { onClick={() => {
setPartsOrderContext({ setPartsOrderContext({
@@ -542,7 +541,6 @@ export function JobLinesComponent({
{selectedLines.length > 0 && ` (${selectedLines.length})`} {selectedLines.length > 0 && ` (${selectedLines.length})`}
</Button> </Button>
<Button <Button
id="job-lines-filter-parts-only-button"
onClick={() => { onClick={() => {
setState((state) => ({ setState((state) => ({
...state, ...state,
@@ -556,7 +554,7 @@ export function JobLinesComponent({
<FilterFilled /> {t("jobs.actions.filterpartsonly")} <FilterFilled /> {t("jobs.actions.filterpartsonly")}
</Button> </Button>
<Dropdown menu={markMenu} trigger={["click"]}> <Dropdown menu={markMenu} trigger={["click"]}>
<Button id="repair-data-mark-button">{t("jobs.actions.mark")}</Button> <Button>{t("jobs.actions.mark")}</Button>
</Dropdown> </Dropdown>
<Button <Button
disabled={jobRO || technician} disabled={jobRO || technician}

View File

@@ -192,23 +192,6 @@ export function JobLinesUpsertModalComponent({ bodyshop, open, jobLine, handleCa
<Form.Item label={t("joblines.fields.tax_part")} name="tax_part" valuePropName="checked" initialValue={true}> <Form.Item label={t("joblines.fields.tax_part")} name="tax_part" valuePropName="checked" initialValue={true}>
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Form.Item dependencies={[["act_price"]]} noStyle>
{() => {
if (form.getFieldValue("act_price") === 0) {
return (
<Form.Item
label={t("joblines.fields.include_in_part_cnt")}
name="include_in_part_cnt"
valuePropName="checked"
>
<Switch />
</Form.Item>
);
} else {
return null;
}
}}
</Form.Item>
</LayoutFormRow> </LayoutFormRow>
</Form> </Form>
</Modal> </Modal>

View File

@@ -173,7 +173,7 @@ export function JobsCloseExportButton({
} }
}); });
if (!jobUpdateResponse.errors) { if (!!!jobUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "jobsuccessexport", key: "jobsuccessexport",
@@ -222,7 +222,7 @@ export function JobsCloseExportButton({
}; };
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary"> <Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")} {t("jobs.actions.export")}
</Button> </Button>
); );

View File

@@ -1,8 +1,7 @@
import { useMutation } from "@apollo/client"; import { useMutation } from "@apollo/client";
import { Button, Form, Input, notification, Popover, Select, Space, Switch } from "antd"; import { Button, Form, Input, notification, Popover, Select, Space, Switch } from "antd";
import axios from "axios"; import axios from "axios";
import { some } from "lodash"; import React, { useState } from "react";
import React, { useCallback, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { createStructuredSelector } from "reselect"; import { createStructuredSelector } from "reselect";
@@ -19,14 +18,7 @@ const mapStateToProps = createStructuredSelector({
jobRO: selectJobReadOnly jobRO: selectJobReadOnly
}); });
const mapDispatchToProps = (dispatch) => ({ const mapDispatchToProps = (dispatch) => ({
insertAuditTrail: ({ jobid, operation, type }) => insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
dispatch(
insertAuditTrail({
jobid,
operation,
type
})
)
}); });
export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTrail, parentFormIsFieldsTouched }) { export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTrail, parentFormIsFieldsTouched }) {
@@ -35,7 +27,6 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO); const [mutationConvertJob] = useMutation(CONVERT_JOB_TO_RO);
const { t } = useTranslation(); const { t } = useTranslation();
const [form] = Form.useForm(); const [form] = Form.useForm();
const allFormValues = Form.useWatch([], form);
const handleConvert = async ({ employee_csr, category, ...values }) => { const handleConvert = async ({ employee_csr, category, ...values }) => {
if (parentFormIsFieldsTouched()) { if (parentFormIsFieldsTouched()) {
@@ -78,8 +69,6 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
setLoading(false); setLoading(false);
}; };
const submitDisabled = useCallback(() => some(allFormValues, (v) => v === undefined), [allFormValues]);
const popMenu = ( const popMenu = (
<div> <div>
<Form <Form
@@ -88,12 +77,9 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
onFinish={handleConvert} onFinish={handleConvert}
initialValues={{ initialValues={{
driveable: true, driveable: true,
towin: job.towin, towin: false,
ca_gst_registrant: job.ca_gst_registrant,
employee_csr: job.employee_csr, employee_csr: job.employee_csr,
category: job.category, category: job.category
referral_source: job.referral_source,
referral_source_extra: job.referral_source_extra ?? ""
}} }}
> >
<Form.Item <Form.Item
@@ -223,7 +209,7 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
<Switch /> <Switch />
</Form.Item> </Form.Item>
<Space wrap> <Space wrap>
<Button disabled={submitDisabled()} type="primary" danger onClick={() => form.submit()} loading={loading}> <Button type="primary" danger onClick={() => form.submit()} loading={loading}>
{t("jobs.actions.convert")} {t("jobs.actions.convert")}
</Button> </Button>
<Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button> <Button onClick={() => setOpen(false)}>{t("general.actions.close")}</Button>
@@ -245,6 +231,11 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
loading={loading} loading={loading}
onClick={() => { onClick={() => {
setOpen(true); setOpen(true);
form.setFieldsValue({
driveable: true,
towin: false,
employee_csr: job.employee_csr
});
}} }}
> >
{t("jobs.actions.convert")} {t("jobs.actions.convert")}

View File

@@ -982,7 +982,7 @@ export function JobsDetailRatesParts({ jobRO, expanded, required = true, form })
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber min={0} max={100} precision={4} disabled={jobRO} />
</Form.Item> </Form.Item>
</LayoutFormRow> </LayoutFormRow>
<LayoutFormRow header={t("jobs.labels.cieca_pfo")}> <LayoutFormRow>
<Form.Item label={t("jobs.fields.tax_tow_rt")} name="tax_tow_rt"> <Form.Item label={t("jobs.fields.tax_tow_rt")} name="tax_tow_rt">
<InputNumber min={0} max={100} precision={4} disabled={jobRO} /> <InputNumber min={0} max={100} precision={4} disabled={jobRO} />
</Form.Item> </Form.Item>

View File

@@ -10,8 +10,8 @@ import { auth, logImEXEvent } from "../../firebase/firebase.utils";
import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries"; import { INSERT_EXPORT_LOG } from "../../graphql/accounting.queries";
import { UPDATE_JOBS } from "../../graphql/jobs.queries"; import { UPDATE_JOBS } from "../../graphql/jobs.queries";
import { insertAuditTrail } from "../../redux/application/application.actions"; import { insertAuditTrail } from "../../redux/application/application.actions";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import AuditTrailMapping from "../../utils/AuditTrailMappings"; import AuditTrailMapping from "../../utils/AuditTrailMappings";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import client from "../../utils/GraphQLClient"; import client from "../../utils/GraphQLClient";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
@@ -165,7 +165,7 @@ export function JobsExportAllButton({
} }
}); });
if (!jobUpdateResponse.errors) { if (!!!jobUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "jobsuccessexport", key: "jobsuccessexport",
@@ -213,13 +213,13 @@ export function JobsExportAllButton({
}) })
); );
if (completedCallback) completedCallback([]); if (!!completedCallback) completedCallback([]);
if (loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10} type="primary"> <Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10}>
{t("jobs.actions.exportselected")} {t("jobs.actions.exportselected")}
</Button> </Button>
); );

View File

@@ -1,5 +1,6 @@
import { EditFilled } from "@ant-design/icons"; import { EditFilled } from "@ant-design/icons";
import { Alert, Card, Col, Row, Space, Table, Typography } from "antd"; import { Alert, Card, Col, Row, Space, Table, Typography } from "antd";
import _ from "lodash";
import React, { useEffect, useMemo, useState } from "react"; import React, { useEffect, useMemo, useState } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { connect } from "react-redux"; import { connect } from "react-redux";
@@ -8,11 +9,11 @@ import { selectTechnician } from "../../redux/tech/tech.selectors";
import { selectBodyshop } from "../../redux/user/user.selectors"; import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter"; import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { alphaSort } from "../../utils/sorters"; import { alphaSort } from "../../utils/sorters";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component"; import LaborAllocationsAdjustmentEdit from "../labor-allocations-adjustment-edit/labor-allocations-adjustment-edit.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
import "./labor-allocations-table.styles.scss"; import "./labor-allocations-table.styles.scss";
import { CalculateAllocationsTotals } from "./labor-allocations-table.utility"; import { CalculateAllocationsTotals } from "./labor-allocations-table.utility";
import { HasFeatureAccess } from "../feature-wrapper/feature-wrapper.component";
import UpsellComponent, { upsellEnum } from "../upsell/upsell.component";
const mapStateToProps = createStructuredSelector({ const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop, bodyshop: selectBodyshop,
technician: selectTechnician technician: selectTechnician
@@ -64,7 +65,6 @@ export function LaborAllocationsTable({
key: "total", key: "total",
sorter: (a, b) => a.total - b.total, sorter: (a, b) => a.total - b.total,
sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "total" && state.sortedInfo.order,
align: "right",
render: (text, record) => record.total.toFixed(1) render: (text, record) => record.total.toFixed(1)
}, },
{ {
@@ -73,7 +73,6 @@ export function LaborAllocationsTable({
key: "hrs_claimed", key: "hrs_claimed",
sorter: (a, b) => a.claimed - b.claimed, sorter: (a, b) => a.claimed - b.claimed,
sortOrder: state.sortedInfo.columnKey === "claimed" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "claimed" && state.sortedInfo.order,
align: "right",
render: (text, record) => record.claimed && record.claimed.toFixed(1) render: (text, record) => record.claimed && record.claimed.toFixed(1)
}, },
{ {
@@ -82,7 +81,6 @@ export function LaborAllocationsTable({
key: "adjustments", key: "adjustments",
sorter: (a, b) => a.adjustments - b.adjustments, sorter: (a, b) => a.adjustments - b.adjustments,
sortOrder: state.sortedInfo.columnKey === "adjustments" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "adjustments" && state.sortedInfo.order,
align: "right",
render: (text, record) => ( render: (text, record) => (
<Space wrap> <Space wrap>
{record.adjustments.toFixed(1)} {record.adjustments.toFixed(1)}
@@ -102,17 +100,17 @@ export function LaborAllocationsTable({
{ {
title: t("jobs.labels.difference"), title: t("jobs.labels.difference"),
dataIndex: "difference", dataIndex: "difference",
key: "difference", key: "difference",
sorter: (a, b) => a.difference - b.difference, sorter: (a, b) => a.difference - b.difference,
sortOrder: state.sortedInfo.columnKey === "difference" && state.sortedInfo.order, sortOrder: state.sortedInfo.columnKey === "difference" && state.sortedInfo.order,
align: "right",
render: (text, record) => ( render: (text, record) => (
<strong <strong
style={{ style={{
color: record.difference.toFixed(1) >= 0 ? "green" : "red" color: record.difference >= 0 ? "green" : "red"
}} }}
> >
{(Math.abs(record.difference) < 0.05 ? 0 : record.difference).toFixed(1)} {_.round(record.difference, 1)}
</strong> </strong>
) )
} }
@@ -131,6 +129,7 @@ export function LaborAllocationsTable({
ellipsis: true, ellipsis: true,
render: (text, record) => `${record.op_code_desc || ""}${record.alt_partm ? ` ${record.alt_partm}` : ""}` render: (text, record) => `${record.op_code_desc || ""}${record.alt_partm ? ` ${record.alt_partm}` : ""}`
}, },
{ {
title: t("joblines.fields.act_price"), title: t("joblines.fields.act_price"),
dataIndex: "act_price", dataIndex: "act_price",
@@ -188,7 +187,7 @@ export function LaborAllocationsTable({
{ hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 } { hrs_total: 0, hrs_claimed: 0, adjustments: 0, difference: 0 }
); );
if (Math.abs(summary.difference.toFixed(1)) !== 0 && typeof warningCallback === "function") { if (summary.difference !== 0 && typeof warningCallback === "function") {
warningCallback({ key: "labor", warning: t("jobs.labels.outstandinghours") }); warningCallback({ key: "labor", warning: t("jobs.labels.outstandinghours") });
} }
@@ -218,21 +217,19 @@ export function LaborAllocationsTable({
summary={() => ( summary={() => (
<Table.Summary.Row> <Table.Summary.Row>
<Table.Summary.Cell> <Table.Summary.Cell>
<Typography.Title level={4} style={{ margin: 0, lineHeight: 1 }}> <Typography.Title level={4}>{t("general.labels.totals")}</Typography.Title>
{t("general.labels.totals")}
</Typography.Title>
</Table.Summary.Cell> </Table.Summary.Cell>
<Table.Summary.Cell align="right">{summary.hrs_total.toFixed(1)}</Table.Summary.Cell> <Table.Summary.Cell>{summary.hrs_total.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell align="right">{summary.hrs_claimed.toFixed(1)}</Table.Summary.Cell> <Table.Summary.Cell>{summary.hrs_claimed.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell align="right">{summary.adjustments.toFixed(1)}</Table.Summary.Cell> <Table.Summary.Cell>{summary.adjustments.toFixed(1)}</Table.Summary.Cell>
<Table.Summary.Cell align="right"> <Table.Summary.Cell>
<Typography.Text <Typography.Text
style={{ style={{
fontWeight: "bold", fontWeight: "bold",
color: summary.difference.toFixed(1) >= 0 ? "green" : "red" color: summary.difference >= 0 ? "green" : "red"
}} }}
> >
{(Math.abs(summary.difference) < 0.05 ? 0 : summary.difference).toFixed(1)} {summary.difference.toFixed(1)}
</Typography.Text> </Typography.Text>
</Table.Summary.Cell> </Table.Summary.Cell>
</Table.Summary.Row> </Table.Summary.Row>
@@ -264,10 +261,11 @@ export function LaborAllocationsTable({
</Card> </Card>
</Col> </Col>
)} )}
{showWarning && Math.abs(summary.difference.toFixed(1)) !== 0 && ( {showWarning && summary.difference !== 0 && (
<Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstandinghours")} /> <Alert style={{ margin: "8px 0px" }} type="warning" message={t("jobs.labels.outstandinghours")} />
)} )}
</Row> </Row>
); );
} }
export default connect(mapStateToProps, null)(LaborAllocationsTable); export default connect(mapStateToProps, null)(LaborAllocationsTable);

View File

@@ -38,11 +38,7 @@ export default function OwnerFindModalContainer({
}, [callSearchowners, modalProps.open, owner]); }, [callSearchowners, modalProps.open, owner]);
return ( return (
<Modal <Modal title={t("owners.labels.existing_owners")} width={"80%"} {...modalProps}>
title={<span id="owner-find-modal-title">{t("owners.labels.existing_owners")}</span>}
width={"80%"}
{...modalProps}
>
{loading ? <LoadingSpinner /> : null} {loading ? <LoadingSpinner /> : null}
{error ? <AlertComponent message={error.message} type="error" /> : null} {error ? <AlertComponent message={error.message} type="error" /> : null}
{owner ? ( {owner ? (

View File

@@ -48,7 +48,7 @@ export function PayableExportAll({
let PartnerResponse; let PartnerResponse;
setLoading(true); setLoading(true);
if (loadingCallback) loadingCallback(true); if (!!loadingCallback) loadingCallback(true);
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/payables`, { PartnerResponse = await axios.post(`/qbo/payables`, {
bills: billids, bills: billids,
@@ -85,7 +85,7 @@ export function PayableExportAll({
notification["error"]({ notification["error"]({
message: t("bills.errors.exporting-partner") message: t("bills.errors.exporting-partner")
}); });
if (loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
return; return;
} }
@@ -152,7 +152,7 @@ export function PayableExportAll({
} }
} }
}); });
if (!billUpdateResponse.errors) { if (!!!billUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "billsuccessexport", key: "billsuccessexport",
@@ -187,8 +187,8 @@ export function PayableExportAll({
}); });
await Promise.all(proms); await Promise.all(proms);
if (completedCallback) completedCallback([]); if (!!completedCallback) completedCallback([]);
if (loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };
@@ -200,7 +200,7 @@ export function PayableExportAll({
); );
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10} type="primary"> <Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10}>
{t("jobs.actions.exportselected")} {t("jobs.actions.exportselected")}
</Button> </Button>
); );

View File

@@ -46,7 +46,7 @@ export function PayableExportButton({
logImEXEvent("accounting_export_payable"); logImEXEvent("accounting_export_payable");
setLoading(true); setLoading(true);
if (loadingCallback) loadingCallback(true); if (!!loadingCallback) loadingCallback(true);
//Check if it's a QBO Setup. //Check if it's a QBO Setup.
let PartnerResponse; let PartnerResponse;
@@ -88,7 +88,7 @@ export function PayableExportButton({
notification["error"]({ notification["error"]({
message: t("bills.errors.exporting-partner") message: t("bills.errors.exporting-partner")
}); });
if (loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
return; return;
} }
@@ -149,7 +149,7 @@ export function PayableExportButton({
} }
} }
}); });
if (!billUpdateResponse.errors) { if (!!!billUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "billsuccessexport", key: "billsuccessexport",
@@ -186,7 +186,7 @@ export function PayableExportButton({
} }
} }
if (loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };
@@ -198,7 +198,7 @@ export function PayableExportButton({
); );
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary"> <Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")} {t("jobs.actions.export")}
</Button> </Button>
); );

View File

@@ -55,7 +55,7 @@ export function PaymentExportButton({
} else { } else {
//Default is QBD //Default is QBD
if (loadingCallback) loadingCallback(true); if (!!loadingCallback) loadingCallback(true);
let QbXmlResponse; let QbXmlResponse;
try { try {
@@ -88,7 +88,7 @@ export function PaymentExportButton({
notification["error"]({ notification["error"]({
message: t("payments.errors.exporting-partner") message: t("payments.errors.exporting-partner")
}); });
if (loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
return; return;
} }
@@ -148,7 +148,7 @@ export function PaymentExportButton({
} }
} }
}); });
if (!paymentUpdateResponse.errors) { if (!!!paymentUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "paymentsuccessexport", key: "paymentsuccessexport",
@@ -184,12 +184,12 @@ export function PaymentExportButton({
) )
]); ]);
} }
if (loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary"> <Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.export")} {t("jobs.actions.export")}
</Button> </Button>
); );

View File

@@ -44,7 +44,7 @@ export function PaymentsExportAllButton({
const handleQbxml = async () => { const handleQbxml = async () => {
setLoading(true); setLoading(true);
if (loadingCallback) loadingCallback(true); if (!!loadingCallback) loadingCallback(true);
let PartnerResponse; let PartnerResponse;
if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) { if (bodyshop.accountingconfig && bodyshop.accountingconfig.qbo) {
PartnerResponse = await axios.post(`/qbo/payments`, { PartnerResponse = await axios.post(`/qbo/payments`, {
@@ -76,7 +76,7 @@ export function PaymentsExportAllButton({
notification["error"]({ notification["error"]({
message: t("payments.errors.exporting-partner") message: t("payments.errors.exporting-partner")
}); });
if (loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
return; return;
} }
@@ -140,7 +140,7 @@ export function PaymentsExportAllButton({
} }
} }
}); });
if (!paymentUpdateResponse.errors) { if (!!!paymentUpdateResponse.errors) {
notification.open({ notification.open({
type: "success", type: "success",
key: "paymentsuccessexport", key: "paymentsuccessexport",
@@ -174,13 +174,13 @@ export function PaymentsExportAllButton({
); );
}); });
await Promise.all(proms); await Promise.all(proms);
if (completedCallback) completedCallback([]); if (!!completedCallback) completedCallback([]);
if (loadingCallback) loadingCallback(false); if (!!loadingCallback) loadingCallback(false);
setLoading(false); setLoading(false);
}; };
return ( return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10} type="primary"> <Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10}>
{t("jobs.actions.exportselected")} {t("jobs.actions.exportselected")}
</Button> </Button>
); );

View File

@@ -555,7 +555,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name={["md_email_cc", "parts_order"]} name={["md_email_cc", "parts_order"]}
label={t("bodyshop.fields.md_email_cc", { template: "parts_orders" })} label={t("bodyshop.fields.md_email_cc", { template: "parts_order" })}
rules={[ rules={[
{ {
//message: t("general.validation.required"), //message: t("general.validation.required"),
@@ -567,7 +567,9 @@ export function ShopInfoGeneral({ form, bodyshop }) {
</Form.Item> </Form.Item>
<Form.Item <Form.Item
name={["md_email_cc", "parts_return_slip"]} name={["md_email_cc", "parts_return_slip"]}
label={t("bodyshop.fields.md_email_cc", { template: "parts_returns" })} label={t("bodyshop.fields.md_email_cc", {
template: "parts_return_slip"
})}
rules={[ rules={[
{ {
//message: t("general.validation.required"), //message: t("general.validation.required"),
@@ -1259,7 +1261,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
key={`${index}prt_dsmk_p`} key={`${index}prt_dsmk_p`}
name={[field.name, "prt_dsmk_p"]} name={[field.name, "prt_dsmk_p"]}
> >
<InputNumber precision={0} min={-100} max={100} /> <InputNumber precision={0} min={0} max={100} />
</Form.Item> </Form.Item>
<Form.Item <Form.Item
label={t("joblines.fields.ah_detail_line")} label={t("joblines.fields.ah_detail_line")}

View File

@@ -73,7 +73,6 @@ export const QUERY_PAYMENTS_FOR_EXPORT = gql`
transactionid transactionid
paymentnum paymentnum
date date
type
exportlogs { exportlogs {
id id
successful successful

View File

@@ -49,7 +49,6 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
est_ct_fn est_ct_fn
est_ct_ln est_ct_ln
comment comment
loss_of_use
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) { labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
aggregate { aggregate {
sum { sum {

View File

@@ -238,7 +238,6 @@ export const UPDATE_JOB_LINE = gql`
convertedtolbr convertedtolbr
convertedtolbr_data convertedtolbr_data
assigned_team assigned_team
include_in_part_cnt
} }
} }
} }

View File

@@ -581,7 +581,6 @@ export const GET_JOB_BY_PK = gql`
status status
tax_part tax_part
unq_seq unq_seq
include_in_part_cnt
} }
kmin kmin
kmout kmout

View File

@@ -31,7 +31,7 @@ import { addAlerts } from "../../redux/application/application.actions.js";
const JobsPage = lazy(() => import("../jobs/jobs.page")); const JobsPage = lazy(() => import("../jobs/jobs.page"));
const CardPaymentModalContainer = lazy( const CardPaymentModalContainer = lazy(
() => import("../../components/card-payment-modal/card-payment-modal.container.jsx") () => import("../../components/card-payment-modal/card-payment-modal.container.")
); );
const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container")); const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container"));

View File

@@ -26,7 +26,6 @@ import {
updateCurrentUser updateCurrentUser
} from "../../firebase/firebase.utils"; } from "../../firebase/firebase.utils";
import { QUERY_EULA } from "../../graphql/bodyshop.queries"; import { QUERY_EULA } from "../../graphql/bodyshop.queries";
import cleanAxios from "../../utils/CleanAxios";
import client from "../../utils/GraphQLClient"; import client from "../../utils/GraphQLClient";
import dayjs from "../../utils/day"; import dayjs from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr"; import InstanceRenderManager from "../../utils/instanceRenderMgr";
@@ -48,6 +47,7 @@ import {
validatePasswordResetSuccess validatePasswordResetSuccess
} from "./user.actions"; } from "./user.actions";
import UserActionTypes from "./user.types"; import UserActionTypes from "./user.types";
import cleanAxios from "../../utils/CleanAxios";
const fpPromise = FingerprintJS.load(); const fpPromise = FingerprintJS.load();
@@ -234,18 +234,16 @@ export function* signInSuccessSaga({ payload }) {
LogRocket.identify(payload.email); LogRocket.identify(payload.email);
try { try {
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
window.$crisp.push(["set", "session:segments", [["user"]]]);
InstanceRenderManager({ InstanceRenderManager({
executeFunction: true, executeFunction: true,
args: [], args: [],
imex: () => { imex: () => {
window.$crisp.push(["set", "session:segments", [["imex"]]]); window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
window.$crisp.push(["set", "session:segments", [["user"]]]);
}, },
rome: () => { rome: () => {
window.$zoho.salesiq.visitor.name(payload.displayName || payload.email); window.$zoho.salesiq.visitor.name(payload.displayName || payload.email);
window.$zoho.salesiq.visitor.email(payload.email); window.$zoho.salesiq.visitor.email(payload.email);
window.$crisp.push(["set", "session:segments", [["rome"]]]);
} }
}); });
} catch (error) { } catch (error) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1444,8 +1444,7 @@ export const TemplateList = (type, context) => {
object: i18n.t("reportcenter.labels.objects.jobs"), object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_exported") field: i18n.t("jobs.fields.date_exported")
}, },
group: "sales", group: "sales"
featureNameRestricted: "export"
}, },
gsr_by_estimator: { gsr_by_estimator: {
title: i18n.t("reportcenter.templates.gsr_by_estimator"), title: i18n.t("reportcenter.templates.gsr_by_estimator"),
@@ -1866,8 +1865,7 @@ export const TemplateList = (type, context) => {
object: i18n.t("reportcenter.labels.objects.jobs"), object: i18n.t("reportcenter.labels.objects.jobs"),
field: i18n.t("jobs.fields.date_open") field: i18n.t("jobs.fields.date_open")
}, },
group: "jobs", group: "jobs"
featureNameRestricted: "bills"
}, },
psr_by_make: { psr_by_make: {
title: i18n.t("reportcenter.templates.psr_by_make"), title: i18n.t("reportcenter.templates.psr_by_make"),
@@ -1903,8 +1901,7 @@ export const TemplateList = (type, context) => {
object: i18n.t("reportcenter.labels.objects.parts_orders"), object: i18n.t("reportcenter.labels.objects.parts_orders"),
field: i18n.t("parts_orders.fields.order_date") field: i18n.t("parts_orders.fields.order_date")
}, },
group: "jobs", group: "jobs"
featureNameRestricted: "bills"
}, },
returns_grouped_by_vendor_detailed: { returns_grouped_by_vendor_detailed: {
title: i18n.t("reportcenter.templates.returns_grouped_by_vendor_detailed"), title: i18n.t("reportcenter.templates.returns_grouped_by_vendor_detailed"),
@@ -1916,8 +1913,7 @@ export const TemplateList = (type, context) => {
object: i18n.t("reportcenter.labels.objects.parts_orders"), object: i18n.t("reportcenter.labels.objects.parts_orders"),
field: i18n.t("parts_orders.fields.order_date") field: i18n.t("parts_orders.fields.order_date")
}, },
group: "jobs", group: "jobs"
featureNameRestricted: "bills"
}, },
scheduled_parts_list: { scheduled_parts_list: {
title: i18n.t("reportcenter.templates.scheduled_parts_list"), title: i18n.t("reportcenter.templates.scheduled_parts_list"),
@@ -2229,19 +2225,7 @@ export const TemplateList = (type, context) => {
field: i18n.t("jobs.fields.date_open") field: i18n.t("jobs.fields.date_open")
}, },
group: "jobs" group: "jobs"
}, }
purchases_by_date_excel: {
title: i18n.t("reportcenter.templates.purchases_by_date_excel"),
subject: i18n.t("reportcenter.templates.purchases_by_date_excel"),
key: "purchases_by_date_excel",
reporttype: "excel",
disabled: false,
rangeFilter: {
object: i18n.t("reportcenter.labels.objects.bills"),
field: i18n.t("bills.fields.date")
},
group: "purchases"
},
} }
: {}), : {}),
...(!type || type === "courtesycarcontract" ...(!type || type === "courtesycarcontract"

View File

@@ -2997,7 +2997,6 @@
- est_seq - est_seq
- glass_flag - glass_flag
- id - id
- include_in_part_cnt
- ioucreated - ioucreated
- jobid - jobid
- lbr_amt - lbr_amt
@@ -3067,7 +3066,6 @@
- est_seq - est_seq
- glass_flag - glass_flag
- id - id
- include_in_part_cnt
- ioucreated - ioucreated
- jobid - jobid
- lbr_amt - lbr_amt
@@ -3148,7 +3146,6 @@
- est_seq - est_seq
- glass_flag - glass_flag
- id - id
- include_in_part_cnt
- ioucreated - ioucreated
- jobid - jobid
- lbr_amt - lbr_amt

View File

@@ -1,4 +0,0 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- alter table "public"."joblines" add column "include_in_part_cnt" boolean
-- not null default 'false';

View File

@@ -1,2 +0,0 @@
alter table "public"."joblines" add column "include_in_part_cnt" boolean
not null default 'false';

View File

@@ -1,10 +0,0 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE OR REPLACE VIEW "public"."joblines_status" AS
-- SELECT j.jobid,
-- j.status,
-- count(1) AS count,
-- j.part_type
-- FROM joblines j
-- WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND ((j.part_qty)::numeric <> (0)::numeric) AND ((j.act_price <> (0)::numeric) OR (j.include_in_part_cnt is TRUE))AND (j.removed IS FALSE))
-- GROUP BY j.jobid, j.status, j.part_type;

View File

@@ -1,8 +0,0 @@
CREATE OR REPLACE VIEW "public"."joblines_status" AS
SELECT j.jobid,
j.status,
count(1) AS count,
j.part_type
FROM joblines j
WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND ((j.part_qty)::numeric <> (0)::numeric) AND ((j.act_price <> (0)::numeric) OR (j.include_in_part_cnt is TRUE))AND (j.removed IS FALSE))
GROUP BY j.jobid, j.status, j.part_type;

View File

@@ -1,10 +0,0 @@
-- Could not auto-generate a down migration.
-- Please write an appropriate down migration for the SQL below:
-- CREATE OR REPLACE VIEW "public"."joblines_status" AS
-- SELECT j.jobid,
-- j.status,
-- count(1) AS count,
-- j.part_type
-- FROM joblines j
-- WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND ((j.part_qty)::numeric <> (0)::numeric) AND ((j.act_price <> (0)::numeric) OR (j.include_in_part_cnt is TRUE))AND (j.removed IS FALSE))
-- GROUP BY j.jobid, j.status, j.part_type;

View File

@@ -1,8 +0,0 @@
CREATE OR REPLACE VIEW "public"."joblines_status" AS
SELECT j.jobid,
j.status,
count(1) AS count,
j.part_type
FROM joblines j
WHERE ((j.part_type IS NOT NULL) AND (j.part_type <> 'PAE'::text) AND (j.part_type <> 'PAS'::text) AND (j.part_type <> 'PASL'::text) AND ((j.part_qty)::numeric <> (0)::numeric) AND ((j.act_price <> (0)::numeric) OR (j.include_in_part_cnt is TRUE))AND (j.removed IS FALSE))
GROUP BY j.jobid, j.status, j.part_type;

View File

@@ -329,8 +329,7 @@ const main = async () => {
main().catch((error) => { main().catch((error) => {
logger.log(`Main-API-Error: Something was not caught in the application.`, "error", "api", null, { logger.log(`Main-API-Error: Something was not caught in the application.`, "error", "api", null, {
error: error.message, error: error.message,
errorjson: JSON.stringify(error), errorjson: JSON.stringify(error)
stack: error.stack
}); });
// Note: If we want the app to crash on all uncaught async operations, we would // Note: If we want the app to crash on all uncaught async operations, we would
// need to put a `process.exit(1);` here // need to put a `process.exit(1);` here

View File

@@ -3,4 +3,3 @@ exports.autohouse = require("./autohouse").default;
exports.chatter = require("./chatter").default; exports.chatter = require("./chatter").default;
exports.claimscorp = require("./claimscorp").default; exports.claimscorp = require("./claimscorp").default;
exports.kaizen = require("./kaizen").default; exports.kaizen = require("./kaizen").default;
exports.usageReport = require("./usageReport").default;

View File

@@ -1,90 +0,0 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const client = require("../graphql-client/graphql-client").client;
const emailer = require("../email/sendemail");
const moment = require("moment-timezone");
const converter = require("json-2-csv");
const logger = require("../utils/logger");
const queries = require("../graphql-client/queries");
const InstanceMgr = require("../utils/instanceMgr").default;
exports.default = async (req, res) => {
try {
logger.log("usage-report-email-start", "debug", req?.user?.email, null, {});
if (InstanceMgr({ rome: false, imex: true })) {
//Disable for ImEX at the moment.
res.sendStatus(403);
logger.log("usage-report-email-forbidden", "warn", req?.user?.email, null, {});
return;
}
if (process.env.NODE_ENV !== "production") {
res.sendStatus(403);
return;
}
// Validate using autohouse token header.
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
res.sendStatus(401);
logger.log("usage-report-email-forbidden", "warn", req?.user?.email, null, {});
return;
}
//Query the usage data.
const queryResults = await client.request(queries.STATUS_UPDATE, {
today: moment().startOf("day").subtract(7, "days"),
period: moment().subtract(90, "days").startOf("day")
});
//Massage the data.
const shopList = queryResults.bodyshops.map((shop) => ({
"Shop Name": shop.shopname,
"Days Since Creation": moment().diff(moment(shop.created_at), "days"),
"Jobs Created": shop.jobs_created.aggregate.count,
"Jobs Updated": shop.jobs_updated.aggregate.count,
"Owners Created": shop.owners_created.aggregate.count,
"Owners Updated": shop.owners_updated.aggregate.count,
"Vehicles Created": shop.vehicles_created.aggregate.count,
"Vehicles Updated": shop.vehicles_updated.aggregate.count,
"Tasks Created": shop.tasks_created.aggregate.count,
"Tasks Updated": shop.tasks_updated.aggregate.count
}));
const csv = converter.json2csv(shopList, { emptyFieldValue: "" });
emailer
.sendTaskEmail({
to: ["patrick.fic@convenient-brands.com", "bradley.rhoades@convenient-brands.com"],
subject: `RO Usage Report - ${moment().format("MM/DD/YYYY")}`,
text: `
Usage Report for ${moment().format("MM/DD/YYYY")} for Rome Online Customers.
Notes:
- Days Since Creation: The number of days since the shop was created. Only shops created in the last 90 days are included.
- Updated values should be higher than created values.
- Counts are inclusive of the last 7 days of data.
`,
attachments: [{ filename: `RO Usage Report ${moment().format("MM/DD/YYYY")}.csv`, content: csv }]
})
.then(() => {
logger.log("usage-report-email-success", "debug", req?.user?.email, null, {
csv
});
})
.catch((error) => {
logger.log("usage-report-email-send-error", "ERROR", req?.user?.email, null, {
error: error.message,
stack: error.stack
});
});
res.sendStatus(200);
return;
} catch (error) {
logger.log("usage-report-email-error", "ERROR", req?.user?.email, null, {
error: error.message,
stack: error.stack
});
res.status(500).json({ error: error.message, stack: error.stack });
}
};

View File

@@ -2617,76 +2617,3 @@ exports.CREATE_CONVERSATION = `mutation CREATE_CONVERSATION($conversation: [conv
} }
} }
`; `;
exports.STATUS_UPDATE = `query STATUS_UPDATE($period: timestamptz!, $today: timestamptz!) {
bodyshops(where: { created_at: { _gte: $period } }) {
shopname
id
created_at
jobs_created: jobs_aggregate(where: { created_at: { _gte: $today } }) {
aggregate {
count
}
}
jobs_updated: jobs_aggregate(where: { updated_at: { _gte: $today } }) {
aggregate {
count
}
}
owners_created: owners_aggregate(where: { created_at: { _gte: $today } }) {
aggregate {
count
}
}
owners_updated: owners_aggregate(where: { updated_at: { _gte: $today } }) {
aggregate {
count
}
}
vehicles_created: vehicles_aggregate(
where: { created_at: { _gte: $today } }
) {
aggregate {
count
}
}
vehicles_updated: vehicles_aggregate(
where: { updated_at: { _gte: $today } }
) {
aggregate {
count
}
}
tasks_created: tasks_aggregate(where: { created_at: { _gte: $today } }) {
aggregate {
count
}
}
tasks_updated: tasks_aggregate(where: { updated_at: { _gte: $today } }) {
aggregate {
count
}
}
jobs {
parts_orders_created: parts_orders_aggregate(
where: { created_at: { _gte: $today } }
) {
aggregate {
count
}
}
parts_orders_updated: parts_orders_aggregate(
where: { updated_at: { _gte: $today } }
) {
aggregate {
count
}
}
}
}
}
`

View File

@@ -818,7 +818,6 @@ function CalculateTaxesTotals(job, otherTotals) {
PAG: Dinero(), PAG: Dinero(),
PAO: Dinero(), PAO: Dinero(),
PAS: Dinero(), PAS: Dinero(),
PASL: Dinero(),
PAP: Dinero(), PAP: Dinero(),
PAM: Dinero(), PAM: Dinero(),

View File

@@ -1,11 +1,10 @@
const express = require("express"); const express = require("express");
const router = express.Router(); const router = express.Router();
const { autohouse, claimscorp, chatter, kaizen, usageReport } = require("../data/data"); const { autohouse, claimscorp, chatter, kaizen } = require("../data/data");
router.post("/ah", autohouse); router.post("/ah", autohouse);
router.post("/cc", claimscorp); router.post("/cc", claimscorp);
router.post("/chatter", chatter); router.post("/chatter", chatter);
router.post("/kaizen", kaizen); router.post("/kaizen", kaizen);
router.post("/usagereport", usageReport);
module.exports = router; module.exports = router;

View File

@@ -41,7 +41,7 @@ exports.taskHandler = async (req, res) => {
return res.status(200).send(csv); return res.status(200).send(csv);
} catch (error) { } catch (error) {
res.status(500).json({ error: error.message, stack: error.stack }); res.status(500).json({ error: error.message, stack: error.stackTrace });
} }
}; };