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
*.java text eol=lf
*.php text eol=lf
*.sql text eol=lf
# Git configuration files
.gitattributes text eol=lf

View File

@@ -6369,27 +6369,6 @@
<folder_node>
<name>md_parts_scan</name>
<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>
<name>expression</name>
<definition_loaded>false</definition_loaded>
@@ -6411,27 +6390,6 @@
</translation>
</translations>
</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>
<name>flags</name>
<definition_loaded>false</definition_loaded>
@@ -6453,48 +6411,6 @@
</translation>
</translations>
</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>
</folder_node>
<concept_node>
@@ -12071,158 +11987,6 @@
</concept_node>
</children>
</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>
<name>successes</name>
<children>
@@ -23367,27 +23131,6 @@
</translation>
</translations>
</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>
<name>amount</name>
<definition_loaded>false</definition_loaded>
@@ -23493,27 +23236,6 @@
</translation>
</translations>
</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>
<name>lbr_types</name>
<children>

View File

@@ -62,17 +62,7 @@
t = d.getElementsByTagName("script")[0];
t.parentNode.insertBefore(s, t);
</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>
!(function () {

View File

@@ -85,17 +85,6 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
sortOrder: state.sortedInfo.columnKey === "amount" && state.sortedInfo.order,
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"),
dataIndex: "memo",

View File

@@ -133,19 +133,15 @@ const CardPaymentModalComponent = ({
if (window.intellipay) {
// eslint-disable-next-line no-eval
eval(response.data);
pollForIntelliPay(() => {
SetIntellipayCallbackFunctions();
window.intellipay.autoOpen();
});
SetIntellipayCallbackFunctions();
window.intellipay.autoOpen();
} else {
const rg = document.createRange();
const node = rg.createContextualFragment(response.data);
document.documentElement.appendChild(node);
pollForIntelliPay(() => {
SetIntellipayCallbackFunctions();
window.intellipay.isAutoOpen = true;
window.intellipay.initialize();
});
SetIntellipayCallbackFunctions();
window.intellipay.isAutoOpen = true;
window.intellipay.initialize();
}
} catch (error) {
notification.open({
@@ -349,27 +345,3 @@ const 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 { selectCardPayment } from "../../redux/modals/modals.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({
cardPaymentModal: selectCardPayment,

View File

@@ -1,10 +1,10 @@
import * as Sentry from "@sentry/react";
import { Button, Col, Collapse, Result, Row, Space } from "antd";
import React from "react";
import { withTranslation } from "react-i18next";
import { logImEXEvent } from "../../firebase/firebase.utils";
import * as Sentry from "@sentry/react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
@@ -38,23 +38,28 @@ class ErrorBoundary extends React.Component {
}
handleErrorSubmit = () => {
window.$crisp.push([
"do",
"message:send",
[
"text",
`I hit the following error: \n\n
InstanceRenderManager({
executeFunction: true,
args: [],
imex: () => {
window.$crisp.push([
"do",
"message:send",
[
"text",
`I hit the following error: \n\n
${this.state.error.message}\n\n
${this.state.error.stack}\n\n
URL:${window.location} as ${this.props.currentUser.email} for ${
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**
// ----

View File

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

View File

@@ -1,30 +1,30 @@
import { AlertFilled } from "@ant-design/icons";
import { useMutation } from "@apollo/client";
import { Button, Divider, Dropdown, Form, Input, notification, Popover, Select, Space } from "antd";
import parsePhoneNumber from "libphonenumber-js";
import dayjs from "../../utils/day";
import queryString from "query-string";
import React, { useContext, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
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 { setModalContext } from "../../redux/modals/modals.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import dayjs from "../../utils/day";
import { GenerateDocument } from "../../utils/RenderTemplate";
import { TemplateList } from "../../utils/TemplateConstants";
import ChatOpenButton from "../chat-open-button/chat-open-button.component";
import DataLabel from "../data-label/data-label.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 ScheduleAtChange from "./job-at-change.component";
import ScheduleEventColor from "./schedule-event.color.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({
bodyshop: selectBodyshop
@@ -127,7 +127,6 @@ export function ScheduleEventComponent({
<DataLabel label={t("jobs.fields.ownr_ph2")}>
<ChatOpenButton phone={event.job && event.job.ownr_ph2} jobid={event.job.id} />
</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")}>
{(event.job && event.job.alt_transport) || ""}
<ScheduleAtChange job={event && event.job} />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -555,7 +555,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
</Form.Item>
<Form.Item
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={[
{
//message: t("general.validation.required"),
@@ -567,7 +567,9 @@ export function ShopInfoGeneral({ form, bodyshop }) {
</Form.Item>
<Form.Item
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={[
{
//message: t("general.validation.required"),
@@ -1259,7 +1261,7 @@ export function ShopInfoGeneral({ form, bodyshop }) {
key={`${index}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
label={t("joblines.fields.ah_detail_line")}

View File

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

View File

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

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ import { addAlerts } from "../../redux/application/application.actions.js";
const JobsPage = lazy(() => import("../jobs/jobs.page"));
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"));

View File

@@ -26,7 +26,6 @@ import {
updateCurrentUser
} from "../../firebase/firebase.utils";
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
import cleanAxios from "../../utils/CleanAxios";
import client from "../../utils/GraphQLClient";
import dayjs from "../../utils/day";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
@@ -48,6 +47,7 @@ import {
validatePasswordResetSuccess
} from "./user.actions";
import UserActionTypes from "./user.types";
import cleanAxios from "../../utils/CleanAxios";
const fpPromise = FingerprintJS.load();
@@ -234,18 +234,16 @@ export function* signInSuccessSaga({ payload }) {
LogRocket.identify(payload.email);
try {
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
window.$crisp.push(["set", "session:segments", [["user"]]]);
InstanceRenderManager({
executeFunction: true,
args: [],
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: () => {
window.$zoho.salesiq.visitor.name(payload.displayName || payload.email);
window.$zoho.salesiq.visitor.email(payload.email);
window.$crisp.push(["set", "session:segments", [["rome"]]]);
}
});
} 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"),
field: i18n.t("jobs.fields.date_exported")
},
group: "sales",
featureNameRestricted: "export"
group: "sales"
},
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"),
field: i18n.t("jobs.fields.date_open")
},
group: "jobs",
featureNameRestricted: "bills"
group: "jobs"
},
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"),
field: i18n.t("parts_orders.fields.order_date")
},
group: "jobs",
featureNameRestricted: "bills"
group: "jobs"
},
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"),
field: i18n.t("parts_orders.fields.order_date")
},
group: "jobs",
featureNameRestricted: "bills"
group: "jobs"
},
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")
},
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"

View File

@@ -2997,7 +2997,6 @@
- est_seq
- glass_flag
- id
- include_in_part_cnt
- ioucreated
- jobid
- lbr_amt
@@ -3067,7 +3066,6 @@
- est_seq
- glass_flag
- id
- include_in_part_cnt
- ioucreated
- jobid
- lbr_amt
@@ -3148,7 +3146,6 @@
- est_seq
- glass_flag
- id
- include_in_part_cnt
- ioucreated
- jobid
- 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) => {
logger.log(`Main-API-Error: Something was not caught in the application.`, "error", "api", null, {
error: error.message,
errorjson: JSON.stringify(error),
stack: error.stack
errorjson: JSON.stringify(error)
});
// Note: If we want the app to crash on all uncaught async operations, we would
// 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.claimscorp = require("./claimscorp").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(),
PAO: Dinero(),
PAS: Dinero(),
PASL: Dinero(),
PAP: Dinero(),
PAM: Dinero(),

View File

@@ -1,11 +1,10 @@
const express = require("express");
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("/cc", claimscorp);
router.post("/chatter", chatter);
router.post("/kaizen", kaizen);
router.post("/usagereport", usageReport);
module.exports = router;

View File

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