diff --git a/bodyshop_translations.babel b/bodyshop_translations.babel
index 9565bcccb..7d1d27464 100644
--- a/bodyshop_translations.babel
+++ b/bodyshop_translations.babel
@@ -3665,6 +3665,27 @@
+
+ addpartsrule
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
addspeedprint
false
@@ -5453,6 +5474,53 @@
+
+ md_parts_scan
+
+
+ expression
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+ flags
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
+
+
md_payment_types
false
@@ -9630,6 +9698,27 @@
+
+ partsscan
+ false
+
+
+
+
+
+ en-US
+ false
+
+
+ es-MX
+ false
+
+
+ fr-CA
+ false
+
+
+
printlater
false
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 66dcc78d5..89debdf45 100644
--- a/client/src/components/job-detail-lines/job-lines.component.jsx
+++ b/client/src/components/job-detail-lines/job-lines.component.jsx
@@ -348,7 +348,7 @@ export function JobLinesComponent({
onClick={() => {
setJobLineEditContext({
actions: { refetch: refetch, submit: form && form.submit },
- context: record,
+ context: { ...record, jobid: job.id },
});
}}
>
diff --git a/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx b/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx
index f4c7d87a4..7c55046b9 100644
--- a/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx
+++ b/client/src/components/job-lines-upsert-modal/job-lines-upsert-modal.container.jsx
@@ -14,8 +14,12 @@ import UndefinedToNull from "../../utils/undefinedtonull";
import JobLinesUpdsertModal from "./job-lines-upsert-modal.component";
import Axios from "axios";
import Dinero from "dinero.js";
+import CriticalPartsScan from "../../utils/criticalPartsScan";
+import { selectBodyshop } from "../../redux/user/user.selectors";
+import { useTreatments } from "@splitsoftware/splitio-react";
const mapStateToProps = createStructuredSelector({
jobLineEditModal: selectJobLineEditModal,
+ bodyshop: selectBodyshop,
});
const mapDispatchToProps = (dispatch) => ({
toggleModalVisible: () => dispatch(toggleModalVisible("jobLineEdit")),
@@ -24,7 +28,13 @@ const mapDispatchToProps = (dispatch) => ({
function JobLinesUpsertModalContainer({
jobLineEditModal,
toggleModalVisible,
+ bodyshop,
}) {
+ const { CriticalPartsScanning } = useTreatments(
+ ["CriticalPartsScanning"],
+ {},
+ bodyshop.imexshopid
+ );
const { t } = useTranslation();
const [insertJobLine] = useMutation(INSERT_NEW_JOB_LINE);
const [updateJobLine] = useMutation(UPDATE_JOB_LINE);
@@ -109,6 +119,9 @@ function JobLinesUpsertModalContainer({
}
toggleModalVisible();
}
+ if (CriticalPartsScanning.treatment === "on") {
+ CriticalPartsScan(jobLineEditModal.context.jobid);
+ }
setLoading(false);
};
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 1aba56a47..d632324b5 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
@@ -3,8 +3,9 @@ import {
useApolloClient,
useLazyQuery,
useMutation,
- useQuery,
+ useQuery
} from "@apollo/client";
+import { useTreatments } from "@splitsoftware/splitio-react";
import { Col, notification, Row } from "antd";
import Axios from "axios";
import Dinero from "dinero.js";
@@ -19,7 +20,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import {
DELETE_AVAILABLE_JOB,
QUERY_AVAILABLE_JOBS,
- QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK,
+ QUERY_AVAILABLE_NEW_JOBS_EST_DATA_BY_PK
} from "../../graphql/available-jobs.queries";
import { INSERT_NEW_JOB, UPDATE_JOB } from "../../graphql/jobs.queries";
import { INSERT_NEW_NOTE } from "../../graphql/notes.queries";
@@ -27,10 +28,11 @@ import { SEARCH_VEHICLE_BY_VIN } from "../../graphql/vehicles.queries";
import { insertAuditTrail } from "../../redux/application/application.actions";
import {
selectBodyshop,
- selectCurrentUser,
+ selectCurrentUser
} from "../../redux/user/user.selectors";
import confirmDialog from "../../utils/asyncConfirm";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
+import CriticalPartsScan from "../../utils/criticalPartsScan";
import AlertComponent from "../alert/alert.component";
import JobsAvailableScan from "../jobs-available-scan/jobs-available-scan.component";
import JobsFindModalContainer from "../jobs-find-modal/jobs-find-modal.container";
@@ -53,6 +55,11 @@ export function JobsAvailableContainer({
currentUser,
insertAuditTrail,
}) {
+ const { CriticalPartsScanning } = useTreatments(
+ ["CriticalPartsScanning"],
+ {},
+ bodyshop.imexshopid
+ );
const { loading, error, data, refetch } = useQuery(QUERY_AVAILABLE_JOBS, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
@@ -155,6 +162,9 @@ export function JobsAvailableContainer({
},
})
.then((r) => {
+ if (CriticalPartsScanning.treatment === "on") {
+ CriticalPartsScan(r.data.insert_jobs.returning[0].id);
+ }
notification["success"]({
message: t("jobs.successes.created"),
onClick: () => {
@@ -241,7 +251,9 @@ export function JobsAvailableContainer({
},
},
});
-
+ if (CriticalPartsScanning.treatment === "on") {
+ CriticalPartsScan(updateResult.data.update_jobs.returning[0].id);
+ }
if (updateResult.errors) {
//error while inserting
notification["error"]({
diff --git a/client/src/components/shop-info/shop-info.component.jsx b/client/src/components/shop-info/shop-info.component.jsx
index d166ae5f1..9baeb2c58 100644
--- a/client/src/components/shop-info/shop-info.component.jsx
+++ b/client/src/components/shop-info/shop-info.component.jsx
@@ -1,3 +1,4 @@
+import { useTreatments } from "@splitsoftware/splitio-react";
import { Button, Card, Tabs } from "antd";
import React from "react";
import { useTranslation } from "react-i18next";
@@ -24,6 +25,11 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(ShopInfoComponent);
export function ShopInfoComponent({ bodyshop, form, saveLoading }) {
+ const { CriticalPartsScanning } = useTreatments(
+ ["CriticalPartsScanning"],
+ {},
+ bodyshop.imexshopid
+ );
const { t } = useTranslation();
return (
-
-
-
+ {CriticalPartsScanning.treatment === "on" && (
+
+
+
+ )}
);
diff --git a/client/src/translations/en_us/common.json b/client/src/translations/en_us/common.json
index 29847872b..ab797cddc 100644
--- a/client/src/translations/en_us/common.json
+++ b/client/src/translations/en_us/common.json
@@ -230,6 +230,7 @@
"addapptcolor": "Add Appointment Color",
"addbucket": "Add Definition",
"addpartslocation": "Add Parts Location",
+ "addpartsrule": "Add Parts Scan Rule",
"addspeedprint": "Add Speed Print",
"addtemplate": "Add Template",
"newlaborrate": "New Labor Rate",
@@ -331,6 +332,10 @@
"md_jobline_presets": "Jobline Presets",
"md_lost_sale_reasons": "Lost Sale Reasons",
"md_parts_order_comment": "Parts Orders Comments",
+ "md_parts_scan": {
+ "expression": "RegEX Expression",
+ "flags": "Flags"
+ },
"md_payment_types": "Payment Types",
"md_referral_sources": "Referral Sources",
"messaginglabel": "Messaging Preset Label",
@@ -581,6 +586,7 @@
"notespresets": "Notes Presets",
"orderstatuses": "Order Statuses",
"partslocations": "Parts Locations",
+ "partsscan": "Critical Parts Scanning",
"printlater": "Print Later",
"qbo": "Use QuickBooks Online?",
"qbo_departmentid": "QBO Department ID",
diff --git a/client/src/translations/es/common.json b/client/src/translations/es/common.json
index 1ad5220b7..375272c4b 100644
--- a/client/src/translations/es/common.json
+++ b/client/src/translations/es/common.json
@@ -230,6 +230,7 @@
"addapptcolor": "",
"addbucket": "",
"addpartslocation": "",
+ "addpartsrule": "",
"addspeedprint": "",
"addtemplate": "",
"newlaborrate": "",
@@ -331,6 +332,10 @@
"md_jobline_presets": "",
"md_lost_sale_reasons": "",
"md_parts_order_comment": "",
+ "md_parts_scan": {
+ "expression": "",
+ "flags": ""
+ },
"md_payment_types": "",
"md_referral_sources": "",
"messaginglabel": "",
@@ -581,6 +586,7 @@
"notespresets": "",
"orderstatuses": "",
"partslocations": "",
+ "partsscan": "",
"printlater": "",
"qbo": "",
"qbo_departmentid": "",
diff --git a/client/src/translations/fr/common.json b/client/src/translations/fr/common.json
index b6012465a..b727dc0de 100644
--- a/client/src/translations/fr/common.json
+++ b/client/src/translations/fr/common.json
@@ -230,6 +230,7 @@
"addapptcolor": "",
"addbucket": "",
"addpartslocation": "",
+ "addpartsrule": "",
"addspeedprint": "",
"addtemplate": "",
"newlaborrate": "",
@@ -331,6 +332,10 @@
"md_jobline_presets": "",
"md_lost_sale_reasons": "",
"md_parts_order_comment": "",
+ "md_parts_scan": {
+ "expression": "",
+ "flags": ""
+ },
"md_payment_types": "",
"md_referral_sources": "",
"messaginglabel": "",
@@ -581,6 +586,7 @@
"notespresets": "",
"orderstatuses": "",
"partslocations": "",
+ "partsscan": "",
"printlater": "",
"qbo": "",
"qbo_departmentid": "",
diff --git a/client/src/utils/criticalPartsScan.js b/client/src/utils/criticalPartsScan.js
new file mode 100644
index 000000000..11e96b69b
--- /dev/null
+++ b/client/src/utils/criticalPartsScan.js
@@ -0,0 +1,12 @@
+import axios from "axios";
+import { notification } from "antd";
+
+async function CriticalPartsScan(jobid) {
+ try {
+ await axios.post("/job/partsscan", { jobid });
+ } catch (error) {
+ notification.open({ type: "error", message: JSON.stringify(error) });
+ }
+}
+
+export default CriticalPartsScan;
diff --git a/server/parts-scan/parts-scan.js b/server/parts-scan/parts-scan.js
index d3305f9c6..c5b619303 100644
--- a/server/parts-scan/parts-scan.js
+++ b/server/parts-scan/parts-scan.js
@@ -3,6 +3,7 @@ const queries = require("../graphql-client/queries");
const { job } = require("../scheduling/scheduling-job");
const GraphQLClient = require("graphql-request").GraphQLClient;
const logger = require("../utils/logger");
+const _ = require("lodash");
// Dinero.defaultCurrency = "USD";
// Dinero.globalLocale = "en-CA";
@@ -17,43 +18,41 @@ exports.partsScan = async function (req, res) {
});
try {
+ //Query all jobline data using the user's authorization.
const data = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QUERY_PARTS_SCAN, {
id: jobid,
});
+ //Create RegExps once for better performance.
const IdsToMarkCritical = [];
const RegExpressions = data.jobs_by_pk.bodyshop.md_parts_scan.map(
(r) => new RegExp(r.expression, r.flags)
);
+ //Check each line against each regex rule.
data.jobs_by_pk.joblines.forEach((jobline) => {
RegExpressions.forEach((rExp) => {
if (jobline.line_desc.match(rExp)) {
- console.log("Critical Parts Match", jobline.line_desc, rExp);
IdsToMarkCritical.push(jobline);
}
});
});
- console.log(IdsToMarkCritical);
-
- //Mark off ids not critical and critical.
-
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.UPDATE_PARTS_CRITICAL, {
- IdsToMarkCritical: IdsToMarkCritical.map((i) => i.id),
+ IdsToMarkCritical: _.uniqBy(IdsToMarkCritical, "id").map((i) => i.id),
jobid: jobid,
});
res.status(200).json(result);
} catch (error) {
- logger.log("job-parts-scan-error", "ERROR", req.user.email, id, {
- jobid: id,
+ logger.log("job-parts-scan-error", "ERROR", req.user.email, jobid, {
+ jobid,
error,
});
- res.status(503).send();
+ res.status(400).json(JSON.stringify(error));
}
};