diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index 8c1cb3ed7..189eb825b 100644
--- a/bodyshop_translations.babel
+++ b/bodyshop_translations.babel
@@ -2779,6 +2779,27 @@
+
+ savewithdiscrepancy
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
state_tax
false
@@ -11095,6 +11116,27 @@
+
+ sold
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
@@ -11267,6 +11309,27 @@
+
+ created_at
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
@@ -33621,6 +33684,37 @@
+
+ subjects
+
+
+ jobs
+
+
+ parts_order
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
+
+
vendors
@@ -34503,6 +34597,116 @@
+
+ groups
+
+
+ customers
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ jobs
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ payroll
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ purchases
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ sales
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
key
false
@@ -34569,6 +34773,27 @@
+
+ csi
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
exportlogs
false
@@ -34828,6 +35053,27 @@
+
+ csi
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
estimator_detail
false
diff --git a/client/package.json b/client/package.json
index 8cdb60e5b..3ec7c8907 100644
--- a/client/package.json
+++ b/client/package.json
@@ -4,16 +4,16 @@
"private": true,
"proxy": "http://localhost:5000",
"dependencies": {
- "@apollo/client": "^3.4.10",
- "@craco/craco": "^6.2.0",
+ "@apollo/client": "^3.4.13",
+ "@craco/craco": "^6.3.0",
"@fingerprintjs/fingerprintjs": "^3.3.0",
"@lourenci/react-kanban": "^2.1.0",
"@openreplay/tracker": "^3.3.1",
"@openreplay/tracker-assist": "^3.1.1",
"@openreplay/tracker-graphql": "^3.0.0",
"@openreplay/tracker-redux": "^3.0.0",
- "@sentry/react": "^6.11.0",
- "@sentry/tracing": "^6.11.0",
+ "@sentry/react": "^6.13.0",
+ "@sentry/tracing": "^6.13.0",
"@stripe/react-stripe-js": "^1.4.0",
"@stripe/stripe-js": "^1.17.1",
"@tanem/react-nprogress": "^3.0.79",
@@ -26,17 +26,17 @@
"enquire-js": "^0.2.1",
"env-cmd": "^10.1.0",
"exifr": "^7.1.3",
- "firebase": "^9.0.0",
+ "firebase": "^9.0.2",
"graphql": "^15.5.3",
- "i18next": "^20.4.0",
+ "i18next": "^21.0.0",
"i18next-browser-languagedetector": "^6.1.2",
"jsoneditor": "^9.5.4",
"jsreport-browser-client-dist": "^1.3.0",
- "libphonenumber-js": "^1.9.26",
+ "libphonenumber-js": "^1.9.34",
"logrocket": "^2.0.0",
"markerjs2": "^2.11.2",
"moment-business-days": "^1.2.0",
- "phone": "^3.1.6",
+ "phone": "^3.1.8",
"preval.macro": "^5.0.0",
"prop-types": "^15.7.2",
"query-string": "^7.0.1",
@@ -50,7 +50,7 @@
"react-drag-listview": "^0.1.8",
"react-grid-gallery": "^0.5.5",
"react-grid-layout": "^1.3.0",
- "react-i18next": "^11.11.4",
+ "react-i18next": "^11.12.0",
"react-icons": "^4.2.0",
"react-number-format": "^4.7.3",
"react-redux": "^7.2.5",
@@ -59,29 +59,29 @@
"react-scripts": "^4.0.3",
"react-sublime-video": "^0.2.5",
"react-virtualized": "^9.22.3",
- "recharts": "^2.1.2",
+ "recharts": "^2.1.3",
"redux": "^4.1.1",
"redux-persist": "^6.0.0",
"redux-saga": "^1.1.3",
"redux-state-sync": "^3.1.2",
"reselect": "^4.0.0",
- "sass": "^1.38.2",
+ "sass": "^1.41.1",
"socket.io-client": "^4.2.0",
"styled-components": "^5.3.1",
"subscriptions-transport-ws": "^0.9.18",
"web-vitals": "^2.1.0",
- "workbox-background-sync": "^6.2.4",
- "workbox-broadcast-update": "^6.2.4",
- "workbox-cacheable-response": "^6.2.4",
- "workbox-core": "^6.2.4",
- "workbox-expiration": "^6.2.4",
- "workbox-google-analytics": "^6.2.4",
- "workbox-navigation-preload": "^6.2.4",
- "workbox-precaching": "^6.2.4",
- "workbox-range-requests": "^6.2.4",
- "workbox-routing": "^6.2.4",
- "workbox-strategies": "^6.2.4",
- "workbox-streams": "^6.2.4"
+ "workbox-background-sync": "^6.3.0",
+ "workbox-broadcast-update": "^6.3.0",
+ "workbox-cacheable-response": "^6.3.0",
+ "workbox-core": "^6.3.0",
+ "workbox-expiration": "^6.3.0",
+ "workbox-google-analytics": "^6.3.0",
+ "workbox-navigation-preload": "^6.3.0",
+ "workbox-precaching": "^6.3.0",
+ "workbox-range-requests": "^6.3.0",
+ "workbox-routing": "^6.3.0",
+ "workbox-strategies": "^6.3.0",
+ "workbox-streams": "^6.3.0"
},
"scripts": {
"postinstall": "patch-package",
diff --git a/client/src/App/App.container.jsx b/client/src/App/App.container.jsx
index 8f60c0163..2f05560ef 100644
--- a/client/src/App/App.container.jsx
+++ b/client/src/App/App.container.jsx
@@ -11,7 +11,8 @@ import App from "./App";
import trackerGraphQL from "@openreplay/tracker-graphql";
//import trackerRedux from "@openreplay/tracker-redux";
import Tracker from "@openreplay/tracker";
-//import trackerAssist from "@openreplay/tracker-assist";
+import trackerAssist from "@openreplay/tracker-assist";
+import { getCurrentUser } from "../firebase/firebase.utils";
moment.locale("en-US");
export const tracker = new Tracker({
@@ -21,12 +22,16 @@ export const tracker = new Tracker({
? { __DISABLE_SECURE_MODE: true }
: {}),
// beaconSize: 10485760,
- onStart: ({ sessionID }) => console.log("ORS SESSION ", sessionID),
+ onStart: async ({ sessionID }) => {
+ const user = await getCurrentUser();
+ tracker.setUserID(user.email);
+ console.log("ORS SESSION ", sessionID, user.email);
+ },
});
-// tracker.use(
-// trackerAssist({ confirmText: "Technical support is about to assist you." })
-// ); // check the list of available options below
+tracker.use(
+ trackerAssist({ confirmText: "Technical support is about to assist you." })
+); // check the list of available options below
export const recordGraphQL = tracker.use(trackerGraphQL());
tracker.start();
if (process.env.NODE_ENV === "production") LogRocket.init("gvfvfw/bodyshopapp");
diff --git a/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx b/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx
index bd45d8c5e..83ff2a7b2 100644
--- a/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx
+++ b/client/src/components/bill-enter-modal/bill-enter-modal.container.jsx
@@ -18,8 +18,10 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
+import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import BillFormContainer from "../bill-form/bill-form.container";
+import { CalculateBillTotal } from "../bill-form/bill-form.totals.utility";
import { handleUpload } from "../documents-upload/documents-upload.utility";
const mapStateToProps = createStructuredSelector({
@@ -48,7 +50,31 @@ function BillEnterModalContainer({
const [loading, setLoading] = useState(false);
const client = useApolloClient();
+ const formValues = useMemo(() => {
+ return {
+ ...billEnterModal.context.bill,
+ jobid:
+ (billEnterModal.context.job && billEnterModal.context.job.id) || null,
+ federal_tax_rate:
+ (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.federal_tax_rate) ||
+ 0,
+ state_tax_rate:
+ (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.state_tax_rate) ||
+ 0,
+ local_tax_rate:
+ (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.local_tax_rate) ||
+ 0,
+ };
+ }, [billEnterModal, bodyshop]);
+
const handleFinish = async (values) => {
+ let totals = CalculateBillTotal(values);
+ if (totals.discrepancy.getAmount() !== 0) {
+ if (!(await confirmDialog(t("bills.labels.savewithdiscrepancy")))) {
+ return;
+ }
+ }
+
setLoading(true);
const { upload, location, ...remainingValues } = values;
@@ -190,7 +216,7 @@ function BillEnterModalContainer({
if (enterAgain) {
form.resetFields();
- form.setFieldsValue({ billlines: [] });
+ form.setFieldsValue(formValues);
} else {
toggleModalVisible();
}
@@ -208,23 +234,6 @@ function BillEnterModalContainer({
if (enterAgain) form.submit();
}, [enterAgain, form]);
- const formValues = useMemo(() => {
- return {
- ...billEnterModal.context.bill,
- jobid:
- (billEnterModal.context.job && billEnterModal.context.job.id) || null,
- federal_tax_rate:
- (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.federal_tax_rate) ||
- 0,
- state_tax_rate:
- (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.state_tax_rate) ||
- 0,
- local_tax_rate:
- (bodyshop.bill_tax_rates && bodyshop.bill_tax_rates.local_tax_rate) ||
- 0,
- };
- }, [billEnterModal, bodyshop]);
-
useEffect(() => {
if (billEnterModal.visible) {
form.setFieldsValue(formValues);
diff --git a/client/src/components/courtesy-car-status-select/courtesy-car-status-select.component.jsx b/client/src/components/courtesy-car-status-select/courtesy-car-status-select.component.jsx
index c9953a3e7..9516013d3 100644
--- a/client/src/components/courtesy-car-status-select/courtesy-car-status-select.component.jsx
+++ b/client/src/components/courtesy-car-status-select/courtesy-car-status-select.component.jsx
@@ -31,6 +31,9 @@ const CourtesyCarStatusComponent = ({ value, onChange }, ref) => {
+
);
};
diff --git a/client/src/components/error-boundary/error-boundary.component.jsx b/client/src/components/error-boundary/error-boundary.component.jsx
index 3f6643e1e..b317df75a 100644
--- a/client/src/components/error-boundary/error-boundary.component.jsx
+++ b/client/src/components/error-boundary/error-boundary.component.jsx
@@ -9,6 +9,7 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
+import { tracker } from "../../App/App.container";
const mapStateToProps = createStructuredSelector({
currentUser: selectCurrentUser,
@@ -36,6 +37,7 @@ class ErrorBoundary extends React.Component {
componentDidCatch(error, info) {
console.log("Exception Caught by Error Boundary.", error, info);
this.setState({ ...this.state, error, info });
+ tracker.event("error_boundary", error, true);
}
handleErrorSubmit = () => {
diff --git a/client/src/components/job-detail-lines/job-lines.component.jsx b/client/src/components/job-detail-lines/job-lines.component.jsx
index 0d640fff1..6c978f826 100644
--- a/client/src/components/job-detail-lines/job-lines.component.jsx
+++ b/client/src/components/job-detail-lines/job-lines.component.jsx
@@ -400,7 +400,7 @@ export function JobLinesComponent({
setState({
...state,
filteredInfo: {
- part_type: ["PAN,PAC,PAR,PAL,PAA,PAM,PAP,PAS,PASL"],
+ part_type: ["PAN,PAC,PAR,PAL,PAA,PAM,PAP,PAO,PAS,PASL,PAG"],
},
});
}}
@@ -435,7 +435,7 @@ export function JobLinesComponent({
columns={columns}
rowKey="id"
loading={loading}
- pagination={{ position: "top", defaultPageSize: 50 }}
+ pagination={false}
dataSource={jobLines}
onChange={handleTableChange}
scroll={{
diff --git a/client/src/components/job-reconciliation-bills-table/job-reconciliation-bills-table.component.jsx b/client/src/components/job-reconciliation-bills-table/job-reconciliation-bills-table.component.jsx
index 40cd8120c..26ce3dd8e 100644
--- a/client/src/components/job-reconciliation-bills-table/job-reconciliation-bills-table.component.jsx
+++ b/client/src/components/job-reconciliation-bills-table/job-reconciliation-bills-table.component.jsx
@@ -21,7 +21,8 @@ export default function JobReconciliationBillsTable({
title: t("billlines.fields.line_desc"),
dataIndex: "line_desc",
key: "line_desc",
- width: "35%",
+ ellipsis: true,
+ minWidth: "65rem",
sorter: (a, b) => alphaSort(a.line_desc, b.line_desc),
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
@@ -30,7 +31,7 @@ export default function JobReconciliationBillsTable({
title: t("billlines.labels.from"),
dataIndex: "from",
key: "from",
- width: "20%",
+
ellipsis: true,
render: (text, record) =>
`${record.bill.vendor && record.bill.vendor.name} / ${
@@ -42,6 +43,7 @@ export default function JobReconciliationBillsTable({
dataIndex: "actual_price",
key: "actual_price",
sorter: (a, b) => a.actual_price - b.actual_price,
+ width: "7rem",
sortOrder:
state.sortedInfo.columnKey === "actual_price" && state.sortedInfo.order,
render: (text, record) => (
@@ -53,6 +55,7 @@ export default function JobReconciliationBillsTable({
dataIndex: "actual_cost",
key: "actual_cost",
sorter: (a, b) => a.actual_cost - b.actual_cost,
+ width: "7rem",
sortOrder:
state.sortedInfo.columnKey === "actual_cost" && state.sortedInfo.order,
render: (text, record) => (
@@ -64,6 +67,7 @@ export default function JobReconciliationBillsTable({
dataIndex: "quantity",
key: "quantity",
sorter: (a, b) => a.quantity - b.quantity,
+ width: "4rem",
sortOrder:
state.sortedInfo.columnKey === "quantity" && state.sortedInfo.order,
},
@@ -72,9 +76,11 @@ export default function JobReconciliationBillsTable({
dataIndex: "is_credit_memo",
key: "is_credit_memo",
sorter: (a, b) => a.bill.is_credit_memo - b.bill.is_credit_memo,
+ width: "8rem",
sortOrder:
state.sortedInfo.columnKey === "is_credit_memo" &&
state.sortedInfo.order,
+
render: (text, record) => (
),
@@ -94,7 +100,7 @@ export default function JobReconciliationBillsTable({
alphaSort(a.line_desc, b.line_desc),
+ ellipses: true,
sortOrder:
state.sortedInfo.columnKey === "line_desc" && state.sortedInfo.order,
},
@@ -57,6 +58,7 @@ export default function JobReconcilitionPartsTable({
dataIndex: "act_price",
key: "act_price",
sorter: (a, b) => a.act_price - b.act_price,
+ width: "7rem",
sortOrder:
state.sortedInfo.columnKey === "act_price" && state.sortedInfo.order,
@@ -68,10 +70,12 @@ export default function JobReconcilitionPartsTable({
title: t("joblines.fields.part_qty"),
dataIndex: "part_qty",
key: "part_qty",
+ width: "4rem",
},
{
title: t("joblines.fields.total"),
dataIndex: "total",
+ width: "7rem",
key: "total",
sorter: (a, b) => a.act_price * a.part_qty - b.act_price * b.part_qty,
sortOrder:
@@ -89,6 +93,7 @@ export default function JobReconcilitionPartsTable({
dataIndex: "status",
key: "status",
sorter: (a, b) => alphaSort(a.status, b.status),
+ width: "6rem",
sortOrder:
state.sortedInfo.columnKey === "status" && state.sortedInfo.order,
},
@@ -108,7 +113,7 @@ export default function JobReconcilitionPartsTable({
pagination={false}
columns={columns}
size="small"
- scroll={{ y: "80vh", x: true }}
+ scroll={{ y: "60vh" }}
rowKey="id"
dataSource={jobLineData}
onChange={handleTableChange}
diff --git a/client/src/components/jobs-available-table/jobs-available-table.container.jsx b/client/src/components/jobs-available-table/jobs-available-table.container.jsx
index d77d5c1d3..b4758f361 100644
--- a/client/src/components/jobs-available-table/jobs-available-table.container.jsx
+++ b/client/src/components/jobs-available-table/jobs-available-table.container.jsx
@@ -29,6 +29,7 @@ import {
selectBodyshop,
selectCurrentUser,
} from "../../redux/user/user.selectors";
+import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import AlertComponent from "../alert/alert.component";
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
@@ -394,14 +395,6 @@ function replaceEmpty(someObj, replaceValue = null) {
return JSON.parse(temp);
}
-function confirmDialog(msg) {
- return new Promise(function (resolve, reject) {
- let confirmed = window.confirm(msg);
-
- return confirmed ? resolve(true) : resolve(false);
- });
-}
-
async function CheckTaxRates(estData, bodyshop) {
//LKQ Check
if (
diff --git a/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx b/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx
index 83ade9d5c..bb9afbf51 100644
--- a/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx
+++ b/client/src/components/jobs-detail-header/jobs-detail-header.component.jsx
@@ -76,6 +76,9 @@ export function JobsDetailHeader({ job, bodyshop, disabled }) {
{job.ins_co_nm}
{job.clm_no}
+
+ {job.po_number}
+
{job.clm_total}
/
diff --git a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx
index 6c186b6c9..18cd7b105 100644
--- a/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx
+++ b/client/src/components/jobs-list-paginated/jobs-list-paginated.component.jsx
@@ -130,13 +130,10 @@ export function JobsList({ bodyshop, refetch, loading, jobs, total }) {
ellipsis: true,
sorter: true, //(a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder: sortcolumn === "clm_no" && sortorder,
- render: (text, record) => {
- return record.clm_no ? (
- {record.clm_no}
- ) : (
- t("general.labels.unknown")
- );
- },
+ render: (text, record) =>
+ `${record.clm_no || ""}${
+ record.po_number ? ` (PO: ${record.po_number})` : ""
+ }`,
},
{
title: t("jobs.fields.ins_co_nm"),
diff --git a/client/src/components/jobs-list/jobs-list.component.jsx b/client/src/components/jobs-list/jobs-list.component.jsx
index 04fb749f2..fe9077ae9 100644
--- a/client/src/components/jobs-list/jobs-list.component.jsx
+++ b/client/src/components/jobs-list/jobs-list.component.jsx
@@ -208,6 +208,10 @@ export function JobsList({ bodyshop }) {
sorter: (a, b) => alphaSort(a.clm_no, b.clm_no),
sortOrder:
state.sortedInfo.columnKey === "clm_no" && state.sortedInfo.order,
+ render: (text, record) =>
+ `${record.clm_no || ""}${
+ record.po_number ? ` (PO: ${record.po_number})` : ""
+ }`,
},
{
title: t("jobs.fields.ins_co_nm"),
diff --git a/client/src/components/parts-order-modal/parts-order-modal.container.jsx b/client/src/components/parts-order-modal/parts-order-modal.container.jsx
index c9e091780..02d2d7614 100644
--- a/client/src/components/parts-order-modal/parts-order-modal.container.jsx
+++ b/client/src/components/parts-order-modal/parts-order-modal.container.jsx
@@ -176,7 +176,7 @@ export function PartsOrderModalContainer({
if (refetch) refetch();
toggleModalVisible();
- const Templates = TemplateList("partsorder");
+ const Templates = TemplateList("partsorder", context);
if (sendType === "e") {
const matchingVendor = data.vendors.filter(
diff --git a/client/src/components/report-center-modal/report-center-modal.component.jsx b/client/src/components/report-center-modal/report-center-modal.component.jsx
index 536d5deed..f49836380 100644
--- a/client/src/components/report-center-modal/report-center-modal.component.jsx
+++ b/client/src/components/report-center-modal/report-center-modal.component.jsx
@@ -1,5 +1,16 @@
import { useLazyQuery } from "@apollo/client";
-import { Button, DatePicker, Form, Radio, Space } from "antd";
+import {
+ Button,
+ Card,
+ Col,
+ DatePicker,
+ Form,
+ Input,
+ Radio,
+ Row,
+ Typography,
+} from "antd";
+import _ from "lodash";
import moment from "moment";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
@@ -14,7 +25,6 @@ import { TemplateList } from "../../utils/TemplateConstants";
import EmployeeSearchSelect from "../employee-search-select/employee-search-select.component";
import VendorSearchSelect from "../vendor-search-select/vendor-search-select.component";
import "./report-center-modal.styles.scss";
-
const mapStateToProps = createStructuredSelector({
reportCenterModal: selectReportCenter,
});
@@ -28,9 +38,14 @@ export default connect(
export function ReportCenterModalComponent({ reportCenterModal }) {
const [form] = Form.useForm();
+ const [search, setSearch] = useState("");
+
const [loading, setLoading] = useState(false);
const { t } = useTranslation();
const Templates = TemplateList("report_center");
+ const ReportsList = Object.keys(Templates).map((key) => {
+ return Templates[key];
+ });
const { visible } = reportCenterModal;
const [callVendorQuery, { data: vendorData, called: vendorCalled }] =
@@ -67,6 +82,9 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
...(end
? { end: moment(end).endOf("day").format("YYYY-MM-DD") }
: {}),
+ ...(start ? { starttz: moment(start).startOf("day") } : {}),
+ ...(end ? { endtz: moment(end).endOf("day") } : {}),
+
...(id ? { id: id } : {}),
},
},
@@ -80,6 +98,17 @@ export function ReportCenterModalComponent({ reportCenterModal }) {
setLoading(false);
};
+ const FilteredReportsList =
+ search !== ""
+ ? ReportsList.filter((r) =>
+ r.title.toLowerCase().includes(search.toLowerCase())
+ )
+ : ReportsList;
+
+ //Group it, create cards, and then filter out.
+
+ const grouped = _.groupBy(FilteredReportsList, "group");
+
return (