Compare commits
58 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8cda88a33 | ||
|
|
71a74c5437 | ||
|
|
6fe4d982f5 | ||
|
|
5ec032d8d6 | ||
|
|
2718a66fb0 | ||
|
|
4c737371e3 | ||
|
|
ee7a3d0bdf | ||
|
|
181af581e5 | ||
|
|
11e2f5d83d | ||
|
|
cbc723fa38 | ||
|
|
95c310119f | ||
|
|
ebe1facbd1 | ||
|
|
b021992552 | ||
|
|
0e218abbf4 | ||
|
|
0cf7961d7d | ||
|
|
ca02937461 | ||
|
|
fa7e0a107b | ||
|
|
9ccffd73ba | ||
|
|
f68ad181e4 | ||
|
|
23bd6085a8 | ||
|
|
e4325e39bf | ||
|
|
584c2e5de2 | ||
|
|
eccc992cfa | ||
|
|
f293e80d0d | ||
|
|
eea2a758b0 | ||
|
|
8d7d4ab4ac | ||
|
|
f9105806ba | ||
|
|
acd278f5b4 | ||
|
|
a4a6fac224 | ||
|
|
b2b7064007 | ||
|
|
4a56fbb135 | ||
|
|
e1fcb0ecba | ||
|
|
ee5e091118 | ||
|
|
9f46f8ec31 | ||
|
|
8c737ff0ae | ||
|
|
a8ef681cf1 | ||
|
|
494c8b6867 | ||
|
|
262b5600d8 | ||
|
|
1861bd68d1 | ||
|
|
348561c812 | ||
|
|
bf4a52b3c1 | ||
|
|
1950023f37 | ||
|
|
a2798a02b3 | ||
|
|
5fcb5a3a3e | ||
|
|
ba41b29538 | ||
|
|
d187ed6f73 | ||
|
|
13a57406d9 | ||
|
|
68c1ac3e70 | ||
|
|
2f267a9f2c | ||
|
|
c7f293ceca | ||
|
|
66e60e96ad | ||
|
|
54a9beb37f | ||
|
|
779cc7d9e8 | ||
|
|
c1b3df9c3b | ||
|
|
80379cdd79 | ||
|
|
d6fbf02092 | ||
|
|
91c3ac56fa | ||
|
|
7d21cb7d70 |
@@ -6369,6 +6369,27 @@
|
|||||||
<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>
|
||||||
@@ -6390,6 +6411,27 @@
|
|||||||
</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>
|
||||||
@@ -6411,6 +6453,48 @@
|
|||||||
</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>
|
||||||
@@ -11987,6 +12071,158 @@
|
|||||||
</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>
|
||||||
@@ -23131,6 +23367,27 @@
|
|||||||
</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>
|
||||||
@@ -23236,6 +23493,27 @@
|
|||||||
</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>
|
||||||
|
|||||||
@@ -62,7 +62,17 @@
|
|||||||
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 () {
|
||||||
|
|||||||
@@ -85,6 +85,17 @@ 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",
|
||||||
|
|||||||
@@ -133,15 +133,19 @@ 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);
|
||||||
SetIntellipayCallbackFunctions();
|
pollForIntelliPay(() => {
|
||||||
window.intellipay.autoOpen();
|
SetIntellipayCallbackFunctions();
|
||||||
|
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);
|
||||||
SetIntellipayCallbackFunctions();
|
pollForIntelliPay(() => {
|
||||||
window.intellipay.isAutoOpen = true;
|
SetIntellipayCallbackFunctions();
|
||||||
window.intellipay.initialize();
|
window.intellipay.isAutoOpen = true;
|
||||||
|
window.intellipay.initialize();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
notification.open({
|
notification.open({
|
||||||
@@ -345,3 +349,27 @@ 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();
|
||||||
|
}
|
||||||
@@ -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,
|
||||||
@@ -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,28 +38,23 @@ class ErrorBoundary extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
handleErrorSubmit = () => {
|
handleErrorSubmit = () => {
|
||||||
InstanceRenderManager({
|
window.$crisp.push([
|
||||||
executeFunction: true,
|
"do",
|
||||||
args: [],
|
"message:send",
|
||||||
imex: () => {
|
[
|
||||||
window.$crisp.push([
|
"text",
|
||||||
"do",
|
`I hit the following error: \n\n
|
||||||
"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**
|
||||||
|
|
||||||
// ----
|
// ----
|
||||||
|
|||||||
@@ -650,14 +650,7 @@ function Header({
|
|||||||
icon: <Icon component={QuestionCircleFilled} />,
|
icon: <Icon component={QuestionCircleFilled} />,
|
||||||
label: t("menus.header.help"),
|
label: t("menus.header.help"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
window.open(
|
window.open("https://help.imex.online/", "_blank");
|
||||||
InstanceRenderManager({
|
|
||||||
imex: "https://help.imex.online/",
|
|
||||||
rome: "https://rometech.com//"
|
|
||||||
}),
|
|
||||||
|
|
||||||
"_blank"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
...(InstanceRenderManager({
|
...(InstanceRenderManager({
|
||||||
|
|||||||
@@ -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,6 +127,7 @@ 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} />
|
||||||
|
|||||||
@@ -519,6 +519,7 @@ 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({
|
||||||
@@ -541,6 +542,7 @@ 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,
|
||||||
@@ -554,7 +556,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>{t("jobs.actions.mark")}</Button>
|
<Button id="repair-data-mark-button">{t("jobs.actions.mark")}</Button>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
<Button
|
<Button
|
||||||
disabled={jobRO || technician}
|
disabled={jobRO || technician}
|
||||||
|
|||||||
@@ -192,6 +192,23 @@ 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>
|
||||||
|
|||||||
@@ -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}>
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary">
|
||||||
{t("jobs.actions.export")}
|
{t("jobs.actions.export")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
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 React, { useState } from "react";
|
import { some } from "lodash";
|
||||||
|
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";
|
||||||
@@ -18,7 +19,14 @@ const mapStateToProps = createStructuredSelector({
|
|||||||
jobRO: selectJobReadOnly
|
jobRO: selectJobReadOnly
|
||||||
});
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
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 }) {
|
export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTrail, parentFormIsFieldsTouched }) {
|
||||||
@@ -27,6 +35,7 @@ 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()) {
|
||||||
@@ -69,6 +78,8 @@ 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
|
||||||
@@ -77,9 +88,12 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
onFinish={handleConvert}
|
onFinish={handleConvert}
|
||||||
initialValues={{
|
initialValues={{
|
||||||
driveable: true,
|
driveable: true,
|
||||||
towin: false,
|
towin: job.towin,
|
||||||
|
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
|
||||||
@@ -209,7 +223,7 @@ export function JobsConvertButton({ bodyshop, job, refetch, jobRO, insertAuditTr
|
|||||||
<Switch />
|
<Switch />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Space wrap>
|
<Space wrap>
|
||||||
<Button type="primary" danger onClick={() => form.submit()} loading={loading}>
|
<Button disabled={submitDisabled()} 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>
|
||||||
@@ -231,11 +245,6 @@ 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")}
|
||||||
|
|||||||
@@ -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>
|
<LayoutFormRow header={t("jobs.labels.cieca_pfo")}>
|
||||||
<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>
|
||||||
|
|||||||
@@ -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 AuditTrailMapping from "../../utils/AuditTrailMappings";
|
|
||||||
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
|
||||||
|
import AuditTrailMapping from "../../utils/AuditTrailMappings";
|
||||||
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}>
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10} type="primary">
|
||||||
{t("jobs.actions.exportselected")}
|
{t("jobs.actions.exportselected")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
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";
|
||||||
@@ -9,11 +8,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
|
||||||
@@ -65,6 +64,7 @@ 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,6 +73,7 @@ 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)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -81,6 +82,7 @@ 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)}
|
||||||
@@ -100,17 +102,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 >= 0 ? "green" : "red"
|
color: record.difference.toFixed(1) >= 0 ? "green" : "red"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{_.round(record.difference, 1)}
|
{(Math.abs(record.difference) < 0.05 ? 0 : record.difference).toFixed(1)}
|
||||||
</strong>
|
</strong>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -129,7 +131,6 @@ 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",
|
||||||
@@ -187,7 +188,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 (summary.difference !== 0 && typeof warningCallback === "function") {
|
if (Math.abs(summary.difference.toFixed(1)) !== 0 && typeof warningCallback === "function") {
|
||||||
warningCallback({ key: "labor", warning: t("jobs.labels.outstandinghours") });
|
warningCallback({ key: "labor", warning: t("jobs.labels.outstandinghours") });
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,19 +218,21 @@ export function LaborAllocationsTable({
|
|||||||
summary={() => (
|
summary={() => (
|
||||||
<Table.Summary.Row>
|
<Table.Summary.Row>
|
||||||
<Table.Summary.Cell>
|
<Table.Summary.Cell>
|
||||||
<Typography.Title level={4}>{t("general.labels.totals")}</Typography.Title>
|
<Typography.Title level={4} style={{ margin: 0, lineHeight: 1 }}>
|
||||||
|
{t("general.labels.totals")}
|
||||||
|
</Typography.Title>
|
||||||
</Table.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
<Table.Summary.Cell>{summary.hrs_total.toFixed(1)}</Table.Summary.Cell>
|
<Table.Summary.Cell align="right">{summary.hrs_total.toFixed(1)}</Table.Summary.Cell>
|
||||||
<Table.Summary.Cell>{summary.hrs_claimed.toFixed(1)}</Table.Summary.Cell>
|
<Table.Summary.Cell align="right">{summary.hrs_claimed.toFixed(1)}</Table.Summary.Cell>
|
||||||
<Table.Summary.Cell>{summary.adjustments.toFixed(1)}</Table.Summary.Cell>
|
<Table.Summary.Cell align="right">{summary.adjustments.toFixed(1)}</Table.Summary.Cell>
|
||||||
<Table.Summary.Cell>
|
<Table.Summary.Cell align="right">
|
||||||
<Typography.Text
|
<Typography.Text
|
||||||
style={{
|
style={{
|
||||||
fontWeight: "bold",
|
fontWeight: "bold",
|
||||||
color: summary.difference >= 0 ? "green" : "red"
|
color: summary.difference.toFixed(1) >= 0 ? "green" : "red"
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{summary.difference.toFixed(1)}
|
{(Math.abs(summary.difference) < 0.05 ? 0 : summary.difference).toFixed(1)}
|
||||||
</Typography.Text>
|
</Typography.Text>
|
||||||
</Table.Summary.Cell>
|
</Table.Summary.Cell>
|
||||||
</Table.Summary.Row>
|
</Table.Summary.Row>
|
||||||
@@ -261,11 +264,10 @@ export function LaborAllocationsTable({
|
|||||||
</Card>
|
</Card>
|
||||||
</Col>
|
</Col>
|
||||||
)}
|
)}
|
||||||
{showWarning && summary.difference !== 0 && (
|
{showWarning && Math.abs(summary.difference.toFixed(1)) !== 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);
|
||||||
|
|||||||
@@ -38,7 +38,11 @@ export default function OwnerFindModalContainer({
|
|||||||
}, [callSearchowners, modalProps.open, owner]);
|
}, [callSearchowners, modalProps.open, owner]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal title={t("owners.labels.existing_owners")} width={"80%"} {...modalProps}>
|
<Modal
|
||||||
|
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 ? (
|
||||||
|
|||||||
@@ -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}>
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10} type="primary">
|
||||||
{t("jobs.actions.exportselected")}
|
{t("jobs.actions.exportselected")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}>
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary">
|
||||||
{t("jobs.actions.export")}
|
{t("jobs.actions.export")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}>
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled} type="primary">
|
||||||
{t("jobs.actions.export")}
|
{t("jobs.actions.export")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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}>
|
<Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10} type="primary">
|
||||||
{t("jobs.actions.exportselected")}
|
{t("jobs.actions.exportselected")}
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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_order" })}
|
label={t("bodyshop.fields.md_email_cc", { template: "parts_orders" })}
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
@@ -567,9 +567,7 @@ 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", {
|
label={t("bodyshop.fields.md_email_cc", { template: "parts_returns" })}
|
||||||
template: "parts_return_slip"
|
|
||||||
})}
|
|
||||||
rules={[
|
rules={[
|
||||||
{
|
{
|
||||||
//message: t("general.validation.required"),
|
//message: t("general.validation.required"),
|
||||||
@@ -1261,7 +1259,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={0} max={100} />
|
<InputNumber precision={0} min={-100} max={100} />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
label={t("joblines.fields.ah_detail_line")}
|
label={t("joblines.fields.ah_detail_line")}
|
||||||
|
|||||||
@@ -73,6 +73,7 @@ export const QUERY_PAYMENTS_FOR_EXPORT = gql`
|
|||||||
transactionid
|
transactionid
|
||||||
paymentnum
|
paymentnum
|
||||||
date
|
date
|
||||||
|
type
|
||||||
exportlogs {
|
exportlogs {
|
||||||
id
|
id
|
||||||
successful
|
successful
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ 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 {
|
||||||
|
|||||||
@@ -238,6 +238,7 @@ export const UPDATE_JOB_LINE = gql`
|
|||||||
convertedtolbr
|
convertedtolbr
|
||||||
convertedtolbr_data
|
convertedtolbr_data
|
||||||
assigned_team
|
assigned_team
|
||||||
|
include_in_part_cnt
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -581,6 +581,7 @@ 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
|
||||||
|
|||||||
@@ -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.")
|
() => import("../../components/card-payment-modal/card-payment-modal.container.jsx")
|
||||||
);
|
);
|
||||||
|
|
||||||
const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container"));
|
const JobsDetailPage = lazy(() => import("../jobs-detail/jobs-detail.page.container"));
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ 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";
|
||||||
@@ -47,7 +48,6 @@ 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,16 +234,18 @@ 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", "user:nickname", [payload.displayName || payload.email]]);
|
window.$crisp.push(["set", "session:segments", [["imex"]]]);
|
||||||
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
@@ -1444,7 +1444,8 @@ 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"),
|
||||||
@@ -1865,7 +1866,8 @@ 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"),
|
||||||
@@ -1901,7 +1903,8 @@ 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"),
|
||||||
@@ -1913,7 +1916,8 @@ 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"),
|
||||||
@@ -2225,7 +2229,19 @@ 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"
|
||||||
|
|||||||
@@ -2997,6 +2997,7 @@
|
|||||||
- est_seq
|
- est_seq
|
||||||
- glass_flag
|
- glass_flag
|
||||||
- id
|
- id
|
||||||
|
- include_in_part_cnt
|
||||||
- ioucreated
|
- ioucreated
|
||||||
- jobid
|
- jobid
|
||||||
- lbr_amt
|
- lbr_amt
|
||||||
@@ -3066,6 +3067,7 @@
|
|||||||
- est_seq
|
- est_seq
|
||||||
- glass_flag
|
- glass_flag
|
||||||
- id
|
- id
|
||||||
|
- include_in_part_cnt
|
||||||
- ioucreated
|
- ioucreated
|
||||||
- jobid
|
- jobid
|
||||||
- lbr_amt
|
- lbr_amt
|
||||||
@@ -3146,6 +3148,7 @@
|
|||||||
- est_seq
|
- est_seq
|
||||||
- glass_flag
|
- glass_flag
|
||||||
- id
|
- id
|
||||||
|
- include_in_part_cnt
|
||||||
- ioucreated
|
- ioucreated
|
||||||
- jobid
|
- jobid
|
||||||
- lbr_amt
|
- lbr_amt
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
-- 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';
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
alter table "public"."joblines" add column "include_in_part_cnt" boolean
|
||||||
|
not null default 'false';
|
||||||
10
hasura/migrations/1736890690896_run_sql_migration/down.sql
Normal file
10
hasura/migrations/1736890690896_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
-- 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;
|
||||||
8
hasura/migrations/1736890690896_run_sql_migration/up.sql
Normal file
8
hasura/migrations/1736890690896_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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;
|
||||||
10
hasura/migrations/1736890701752_run_sql_migration/down.sql
Normal file
10
hasura/migrations/1736890701752_run_sql_migration/down.sql
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
-- 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;
|
||||||
8
hasura/migrations/1736890701752_run_sql_migration/up.sql
Normal file
8
hasura/migrations/1736890701752_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
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;
|
||||||
@@ -329,7 +329,8 @@ 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
|
||||||
|
|||||||
@@ -3,3 +3,4 @@ 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;
|
||||||
90
server/data/usageReport.js
Normal file
90
server/data/usageReport.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
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 });
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -2617,3 +2617,76 @@ 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
@@ -818,6 +818,7 @@ 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(),
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
const express = require("express");
|
const express = require("express");
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
const { autohouse, claimscorp, chatter, kaizen } = require("../data/data");
|
const { autohouse, claimscorp, chatter, kaizen, usageReport } = 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;
|
||||||
|
|||||||
@@ -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.stackTrace });
|
res.status(500).json({ error: error.message, stack: error.stack });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user