Compare commits

...

27 Commits

Author SHA1 Message Date
Dave Richer
f53fcc345e hotfix/IO-3148-Error-In-Email-Bounce-Route: Hot Fix for Broken Import 2025-02-20 14:01:11 -05:00
Dave Richer
67e904e121 Merged in release/2025-02-28 (pull request #2120)
feature/IO-3146-Hotfix-For-Email-Translations
2025-02-19 15:41:14 +00:00
Dave Richer
83ea51157d Merged in feature/IO-3146-Hotfix-For-Email-Translation (pull request #2118)
feature/IO-3146-Hotfix-For-Email-Translations
2025-02-19 15:40:05 +00:00
Dave Richer
9f207f0946 feature/IO-3146-Hotfix-For-Email-Translations 2025-02-19 10:38:32 -05:00
Dave Richer
8a88a241d6 Merged in release/2025-02-14 (pull request #2117)
Release/2025-02-14 - IO-3127 IO-3128 IO-3077 IO-3131 IO-3139 IO-3140
2025-02-16 01:20:59 +00:00
Allan Carr
4684bada1e Merged in feature/IO-3140-Job-Close-Print-Center (pull request #2115)
IO-3140 Job Close Print Center

Approved-by: Dave Richer
2025-02-13 01:05:17 +00:00
Allan Carr
163354f4b4 IO-3140 Job Close Print Center
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-02-12 16:19:17 -08:00
Dave Richer
f3b2edea1c Merged in feature/IO-3139-Header-Deprecation-Bug (pull request #2114)
feature/IO-3139-Header-Deprecation-Bug - Quick fix for header deprecation.
2025-02-12 20:21:12 +00:00
Dave Richer
01e103fd0e feature/IO-3139-Header-Deprecation-Bug - Quick fix for header deprecation. 2025-02-12 15:20:17 -05:00
Dave Richer
4b184d1d42 Merged in hotfix/IO-3128-Unread-Messages-Not-Updating (pull request #2112)
hotfix/IO-3128-Unread-Messages-Not-Updating - Initial fix just to make sure clients see messages, will poll and update every 60 seconds if the chat window is closed and has never been opened.
2025-02-12 19:06:13 +00:00
Patrick Fic
8c541dad05 Merge branch 'release/2025-02-14' of bitbucket.org:snaptsoft/bodyshop into release/2025-02-14 2025-02-12 07:42:20 -08:00
Patrick Fic
921cca86c1 Remove patrick from support emails. 2025-02-12 07:41:52 -08:00
Allan Carr
841312ebcd Merged in feature/IO-3131-Crisp-Segment-for-BASIC (pull request #2111)
IO-3131 Crisp Segment for Basic

Approved-by: Dave Richer
2025-02-12 14:55:08 +00:00
Allan Carr
5ed00eaffe IO-3131 Crisp Segment for Basic
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-02-11 20:26:44 -08:00
Patrick Fic
024b4fe21b Merged in feature/IO-3077-import-rules-engine (pull request #2110)
Feature/IO-3077 import rules engine
2025-02-11 18:52:08 +00:00
Patrick Fic
40aca91c76 IO-3077 Move parts scan order to correctly update totals. 2025-02-11 10:51:26 -08:00
Dave Richer
abe4f4fb3d feature/IO-3077-import-rules-engine - Fix Translations 2025-02-11 12:34:28 -05:00
Patrick Fic
35a3726cf0 IO-3077 Implement import rules engine. 2025-02-10 14:24:50 -08:00
Patrick Fic
95a592fb9a Merged in feature/IO-3127-Dashboard-Schedule-Translations (pull request #2106)
IO-3127 Dashboard Schedule Translations

Approved-by: Dave Richer
2025-02-10 14:44:02 +00:00
Dave Richer
760f2ac7f9 hotfix/IO-3128-Unread-Messages-Not-Updating - Initial fix just to make sure clients see messages, will poll and update every 60 seconds if the chat window is closed and has never been opened. 2025-02-06 12:49:51 -05:00
Allan Carr
872e36a61a Merged in hotfix/2025-02-06 (pull request #2108)
IO-3121 Adjust Footer
2025-02-06 16:33:52 +00:00
Allan Carr
779f608506 Merged in feature/IO-3121-Generic-Report-Header (pull request #2107)
IO-3121 Adjust Footer
2025-02-06 16:32:50 +00:00
Patrick Fic
14e362ec3f Merged in release/2025-01-31 (pull request #2105)
Release/2025 01 31 - IO-1582, IO-2676, IO-2681, IO-2825, IO-2952, IO-2970, IO-3074, IO-3075, IO-3076, IO-3096, IO-3101, IO-3103, IO-3114, IO-3115, IO-3116, IO-3121, IO-3123
2025-02-06 04:01:53 +00:00
Patrick Fic
c213e13624 Resolve CI issues. 2025-02-05 20:00:48 -08:00
Patrick Fic
dae7642a8c Merged in release/2025-01-31 (pull request #2104)
Release/2025 01 31 - IO-1582, IO-2676, IO-2681, IO-2825, IO-2952, IO-2970, IO-3074, IO-3075, IO-3076, IO-3096, IO-3101, IO-3103, IO-3114, IO-3115, IO-3116, IO-3121, IO-3123
2025-02-06 03:56:56 +00:00
Allan Carr
c751f0cba4 IO-3127 Dashboard Schedule Translations
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2025-02-05 15:10:18 -08:00
Allan Carr
e128c108f8 Merged in feature/IO-3121-Generic-Report-Header (pull request #2102)
IO-3121 Generic Report Header

Approved-by: Dave Richer
2025-02-05 15:18:45 +00:00
16 changed files with 11802 additions and 11421 deletions

View File

@@ -15,7 +15,7 @@ jobs:
- eb/setup
- run:
command: |
eb init imex-online-production-api -r ca-central-1 -p "Node.js 22 running on 64bit Amazon Linux 2"
eb init imex-online-production-api -r ca-central-1 -p "Node.js 22 running on 64bit Amazon Linux 2023"
eb status --verbose
eb deploy
eb status
@@ -114,7 +114,7 @@ jobs:
- eb/setup
- run:
command: |
eb init romeonline-productionapi -r us-east-2 -p "Node.js 22 on 64bit Amazon Linux 2"
eb init romeonline-productionapi -r us-east-2 -p "Node.js 22 running on 64bit Amazon Linux 2023"
eb status --verbose
eb deploy
eb status

View File

@@ -1,4 +1,4 @@
<babeledit_project version="1.2" be_version="2.7.1">
<babeledit_project be_version="2.7.1" version="1.2">
<!--
BabelEdit project file
@@ -6453,6 +6453,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>mark_critical</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>operation</name>
<definition_loaded>false</definition_loaded>
@@ -6474,6 +6495,48 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>update_field</name>
<definition_loaded>false</definition_loaded>
<description></description>
<comment></comment>
<default_text></default_text>
<translations>
<translation>
<language>en-US</language>
<approved>false</approved>
</translation>
<translation>
<language>es-MX</language>
<approved>false</approved>
</translation>
<translation>
<language>fr-CA</language>
<approved>false</approved>
</translation>
</translations>
</concept_node>
<concept_node>
<name>update_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>
<concept_node>
<name>value</name>
<definition_loaded>false</definition_loaded>
@@ -11943,6 +12006,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>shop_enabled_features</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>shopinfo</name>
<definition_loaded>false</definition_loaded>
@@ -12312,6 +12396,37 @@
</concept_node>
</children>
</folder_node>
<folder_node>
<name>tooltips</name>
<children>
<folder_node>
<name>md_parts_scan</name>
<children>
<concept_node>
<name>update_value_tooltip</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>
</children>
</folder_node>
<folder_node>
<name>validation</name>
<children>
@@ -19091,6 +19206,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>ok</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>previous</name>
<definition_loaded>false</definition_loaded>
@@ -19385,6 +19521,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>sharetoteams</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>submit</name>
<definition_loaded>false</definition_loaded>
@@ -43090,6 +43247,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>parts_returns</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>print</name>
<definition_loaded>false</definition_loaded>
@@ -48557,6 +48735,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>unassigned</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>vertical</name>
<definition_loaded>false</definition_loaded>
@@ -52732,6 +52931,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>purchases_by_date_excel</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>purchases_by_date_range_detail</name>
<definition_loaded>false</definition_loaded>
@@ -54483,6 +54703,27 @@
</translation>
</translations>
</concept_node>
<concept_node>
<name>view</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>

View File

@@ -42,8 +42,7 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh
const { data: unreadData } = useQuery(UNREAD_CONVERSATION_COUNT, {
fetchPolicy: "network-only",
nextFetchPolicy: "network-only",
skip: chatVisible, // Skip when chat is visible
...(pollInterval > 0 ? { pollInterval } : {})
pollInterval: 60 * 1000 // TODO: This is a fix for now, should be coming from sockets
});
// Socket connection status
@@ -85,29 +84,25 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh
// Get unread count from the cache
const unreadCount = (() => {
if (chatVisible) {
try {
const cachedData = client.readQuery({
query: CONVERSATION_LIST_QUERY,
variables: { offset: 0 }
});
try {
const cachedData = client.readQuery({
query: CONVERSATION_LIST_QUERY,
variables: { offset: 0 }
});
if (!cachedData?.conversations) return 0;
// Aggregate unread message count
return cachedData.conversations.reduce((total, conversation) => {
const unread = conversation.messages_aggregate?.aggregate?.count || 0;
return total + unread;
}, 0);
} catch (error) {
console.warn("Unread count not found in cache:", error);
return 0; // Fallback if not in cache
if (!cachedData?.conversations) {
return unreadData?.messages_aggregate?.aggregate?.count;
}
} else if (unreadData?.messages_aggregate?.aggregate?.count) {
// Use the unread count from the query result
return unreadData.messages_aggregate.aggregate.count;
// Aggregate unread message count
return cachedData.conversations.reduce((total, conversation) => {
const unread = conversation.messages_aggregate?.aggregate?.count || 0;
return total + unread;
}, 0);
} catch (error) {
console.warn("Unread count not found in cache:", error);
return 0; // Fallback if not in cache
}
return 0;
})();
return (

View File

@@ -1,16 +1,15 @@
import i18next from "i18next";
import React from "react";
import { connect } from "react-redux";
import { setUserLanguage } from "../../redux/user/user.actions";
import HeaderComponent from "./header.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapDispatchToProps = (dispatch) => ({
setUserLanguage: (language) => dispatch(setUserLanguage(language))
});
// const mapDispatchToProps = (dispatch) => ({
// setUserLanguage: (language) => dispatch(setUserLanguage(language))
// });
export function HeaderContainer({ setUserLanguage }) {
const handleMenuClick = (e) => {
// setUserLanguage was removed from signature because it is not used in the component, and it is throwing a deprecation warning
export function HeaderContainer() {
// Commented out the handleMenuClick function because it is not used in the component, and it is throwing a deprecation warning
/* const handleMenuClick = (e) => {
if (e.item.props.actiontype === "lang-select") {
i18next.changeLanguage(e.key, (err, t) => {
if (err) {
@@ -23,9 +22,10 @@ export function HeaderContainer({ setUserLanguage }) {
setUserLanguage(e.key);
});
}
};
};*/
// return <HeaderComponent handleMenuClick={handleMenuClick} />;
return <HeaderComponent handleMenuClick={handleMenuClick} />;
return <HeaderComponent />;
}
export default connect(null, mapDispatchToProps)(HeaderContainer);
export default connect(null, null)(HeaderContainer);

View File

@@ -62,6 +62,9 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
refetchQueries: ["GET_LINE_TICKET_BY_PK"]
});
if (!r.errors) {
if (CriticalPartsScanning.treatment === "on") {
await CriticalPartsScan(jobLineEditModal.context.jobid, notification);
}
await Axios.post("/job/totalsssu", {
id: jobLineEditModal.context.jobid
});
@@ -107,7 +110,9 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
})
});
}
if (CriticalPartsScanning.treatment === "on") {
await CriticalPartsScan(jobLineEditModal.context.jobid, notification);
}
if (jobLineEditModal.actions.submit) {
jobLineEditModal.actions.submit();
} else {
@@ -115,9 +120,7 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
}
toggleModalVisible();
}
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(jobLineEditModal.context.jobid, notification);
}
setLoading(false);
};

View File

@@ -172,13 +172,13 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
job: newJob
}
});
if (CriticalPartsScanning.treatment === "on") {
await CriticalPartsScan(r.data.insert_jobs.returning[0].id, notification);
}
await Axios.post("/job/totalsssu", {
id: r.data.insert_jobs.returning[0].id
});
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(r.data.insert_jobs.returning[0].id, notification);
}
notification["success"]({
message: t("jobs.successes.created"),
onClick: () => {
@@ -281,6 +281,7 @@ export function JobsAvailableContainer({ bodyshop, currentUser, insertAuditTrail
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(updateResult.data.update_jobs.returning[0].id, notification);
}
if (updateResult.errors) {
//error while inserting
notification["error"]({

View File

@@ -1,16 +1,27 @@
import {DeleteFilled} from "@ant-design/icons";
import {Button, Col, Form, Input, Row, Select, Space, Switch} from "antd";
import React, {useMemo} from "react";
import {useTranslation} from "react-i18next";
import { DeleteFilled } from "@ant-design/icons";
import { Button, Col, Form, Input, Row, Select, Space, Switch } from "antd";
import { useMemo } from "react";
import { useTranslation } from "react-i18next";
import FormListMoveArrows from "../form-list-move-arrows/form-list-move-arrows.component";
import LayoutFormRow from "../layout-form-row/layout-form-row.component";
import i18n from "i18next";
const predefinedPartTypes = [
"PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG"
];
const predefinedPartTypes = ["PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG"];
const predefinedModLbrTypes = [
"LAA", "LAB", "LAD", "LAE", "LAF", "LAG", "LAM", "LAR", "LAS", "LAU",
"LA1", "LA2", "LA3", "LA4"
"LAA",
"LAB",
"LAD",
"LAE",
"LAF",
"LAG",
"LAM",
"LAR",
"LAS",
"LAU",
"LA1",
"LA2",
"LA3",
"LA4"
];
const getFieldType = (field) => {
@@ -20,30 +31,46 @@ const getFieldType = (field) => {
return null;
};
export default function ShopInfoPartsScan({form}) {
const {t} = useTranslation();
const fieldSelectOptions = [
{ label: i18n.t("joblines.fields.line_desc"), value: "line_desc" },
{ label: i18n.t("joblines.fields.part_type"), value: "part_type" },
{ label: i18n.t("joblines.fields.act_price"), value: "act_price" },
{ label: i18n.t("joblines.fields.part_qty"), value: "part_qty" },
{ label: i18n.t("joblines.fields.mod_lbr_ty"), value: "mod_lbr_ty" },
{
label: `${i18n.t("joblines.fields.oem_partno")} / ${i18n.t("joblines.fields.alt_partno")}`,
value: "part_number"
},
{ label: i18n.t("joblines.fields.op_code_desc"), value: "op_code_desc" }
];
export default function ShopInfoPartsScan({ form }) {
const { t } = useTranslation();
const watchedFields = Form.useWatch("md_parts_scan", form);
const operationOptions = useMemo(() => ({
string: [
{label: t("bodyshop.operations.contains"), value: "contains"},
{label: t("bodyshop.operations.equals"), value: "equals"},
{label: t("bodyshop.operations.starts_with"), value: "startsWith"},
{label: t("bodyshop.operations.ends_with"), value: "endsWith"},
],
number: [
{label: t("bodyshop.operations.equals"), value: "="},
{label: t("bodyshop.operations.greater_than"), value: ">"},
{label: t("bodyshop.operations.less_than"), value: "<"},
],
}), [t]);
const operationOptions = useMemo(
() => ({
string: [
{ label: t("bodyshop.operations.contains"), value: "contains" },
{ label: t("bodyshop.operations.equals"), value: "equals" },
{ label: t("bodyshop.operations.starts_with"), value: "startsWith" },
{ label: t("bodyshop.operations.ends_with"), value: "endsWith" }
],
number: [
{ label: t("bodyshop.operations.equals"), value: "=" },
{ label: t("bodyshop.operations.greater_than"), value: ">" },
{ label: t("bodyshop.operations.less_than"), value: "<" }
]
}),
[t]
);
return (
<div>
<LayoutFormRow header={t("bodyshop.labels.md_parts_scan")}>
<Form.List name={["md_parts_scan"]}>
{(fields, {add, remove, move}) => (
{(fields, { add, remove, move }) => (
<div>
{fields.map((field, index) => {
const selectedField = watchedFields?.[index]?.field || "line_desc";
@@ -61,28 +88,17 @@ export default function ShopInfoPartsScan({form}) {
{
required: true,
message: t("general.validation.required", {
label: t("bodyshop.fields.md_parts_scan.field"),
}),
},
label: t("bodyshop.fields.md_parts_scan.field")
})
}
]}
>
<Select
options={[
{label: t("joblines.fields.line_desc"), value: "line_desc"},
{label: t("joblines.fields.part_type"), value: "part_type"},
{label: t("joblines.fields.act_price"), value: "act_price"},
{label: t("joblines.fields.part_qty"), value: "part_qty"},
{label: t("joblines.fields.mod_lbr_ty"), value: "mod_lbr_ty"},
{label: t("joblines.fields.mod_lb_hrs"), value: "mod_lb_hrs"},
{
label: `${t("joblines.fields.oem_partno")} / ${t("joblines.fields.alt_partno")}`,
value: "part_number"
},
]}
options={fieldSelectOptions}
onChange={() => {
form.setFields([
{name: ["md_parts_scan", index, "operation"], value: "contains"},
{name: ["md_parts_scan", index, "value"], value: undefined},
{ name: ["md_parts_scan", index, "operation"], value: "contains" },
{ name: ["md_parts_scan", index, "value"], value: undefined }
]);
}}
/>
@@ -99,12 +115,12 @@ export default function ShopInfoPartsScan({form}) {
{
required: true,
message: t("general.validation.required", {
label: t("bodyshop.fields.md_parts_scan.operation"),
}),
},
label: t("bodyshop.fields.md_parts_scan.operation")
})
}
]}
>
<Select options={operationOptions[fieldType]}/>
<Select options={operationOptions[fieldType]} />
</Form.Item>
</Col>
)}
@@ -119,9 +135,9 @@ export default function ShopInfoPartsScan({form}) {
{
required: true,
message: t("general.validation.required", {
label: t("bodyshop.fields.md_parts_scan.value"),
}),
},
label: t("bodyshop.fields.md_parts_scan.value")
})
}
]}
>
{fieldType === "predefined" ? (
@@ -129,17 +145,17 @@ export default function ShopInfoPartsScan({form}) {
options={
selectedField === "part_type"
? predefinedPartTypes.map((type) => ({
label: type,
value: type
}))
label: type,
value: type
}))
: predefinedModLbrTypes.map((type) => ({
label: type,
value: type
}))
label: type,
value: type
}))
}
/>
) : (
<Input/>
<Input />
)}
</Form.Item>
</Col>
@@ -152,19 +168,70 @@ export default function ShopInfoPartsScan({form}) {
label={t("bodyshop.fields.md_parts_scan.caseInsensitive")}
name={[field.name, "caseInsensitive"]}
valuePropName="checked"
labelCol={{span: 14}}
wrapperCol={{span: 10}}
initialValue={true}
labelCol={{ span: 14 }}
wrapperCol={{ span: 10 }}
>
<Switch defaultChecked={true}/>
<Switch />
</Form.Item>
</Col>
)}
{/* Mark Line as Critical */}
<Col span={4}>
<Form.Item
label={t("bodyshop.fields.md_parts_scan.mark_critical")}
name={[field.name, "mark_critical"]}
valuePropName="checked"
initialValue={true}
labelCol={{ span: 14 }}
wrapperCol={{ span: 10 }}
>
<Switch />
</Form.Item>
</Col>
{/* Update Field */}
<Col span={4}>
<Form.Item
label={t("bodyshop.fields.md_parts_scan.update_field")}
name={[field.name, "update_field"]}
>
<Select
options={fieldSelectOptions}
allowClear
onClear={() =>
form.setFields([{ name: ["md_parts_scan", index, "update_field"], value: null }])
}
/>
</Form.Item>
</Col>
{/* Update Field */}
<Col span={4}>
<Form.Item
label={t("bodyshop.fields.md_parts_scan.update_value")}
name={[field.name, "update_value"]}
dependencies={[["md_parts_scan", index, "update_field"]]}
tooltip={t("bodyshop.tooltips.md_parts_scan.update_value_tooltip")}
rules={[
{
required: form.getFieldValue(["md_parts_scan", index, "update_field"]),
message: t("general.validation.required", {
label: t("bodyshop.fields.md_parts_scan.update_value")
})
}
]}
>
<Input />
</Form.Item>
</Col>
{/* Actions */}
<Col span={2}>
<Space>
<DeleteFilled onClick={() => remove(field.name)}/>
<FormListMoveArrows move={move} index={index} total={fields.length}/>
<DeleteFilled onClick={() => remove(field.name)} />
<FormListMoveArrows move={move} index={index} total={fields.length} />
</Space>
</Col>
</Row>
@@ -175,8 +242,8 @@ export default function ShopInfoPartsScan({form}) {
<Form.Item>
<Button
type="dashed"
onClick={() => add({field: "line_desc", operation: "contains"})}
style={{width: "100%"}}
onClick={() => add({ field: "line_desc", operation: "contains" })}
style={{ width: "100%" }}
>
{t("bodyshop.actions.addpartsrule")}
</Button>

View File

@@ -1890,6 +1890,7 @@ export const QUERY_JOB_CLOSE_DETAILS = gql`
kmout
qb_multiple_payers
lbr_adjustments
ownr_ea
payments {
amount
created_at

View File

@@ -347,6 +347,9 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
window.$zoho.salesiq.visitor.info({ "Shop Name": payload.shopname });
}
});
payload.features?.allAccess === true
? window.$crisp.push(["set", "session:segments", [["allAccess"]]])
: window.$crisp.push(["set", "session:segments", [["basic"]]]);
} catch (error) {
console.error("Couldnt find $crisp.");
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -55,7 +55,7 @@ exports.default = async (req, res) => {
const csv = converter.json2csv(shopList, { emptyFieldValue: "" });
emailer
.sendTaskEmail({
to: ["patrick.fic@convenient-brands.com", "bradley.rhoades@convenient-brands.com"],
to: ["patrick.fic@convenient-brands.com", "bradley.rhoades@convenient-brands.com", "jrome@rometech.com"],
subject: `RO Usage Report - ${moment().format("MM/DD/YYYY")}`,
text: `
Usage Report for ${moment().format("MM/DD/YYYY")} for Rome Online Customers.

View File

@@ -55,7 +55,7 @@ const sendServerEmail = async ({ subject, text }) => {
imex: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
rome: `Rome Online API - ${process.env.NODE_ENV} <noreply@romeonline.io>`
}),
to: ["patrick@imexsystems.ca", "support@thinkimex.com"],
to: ["support@thinkimex.com"],
subject: subject,
text: text,
ses: {
@@ -92,7 +92,7 @@ const sendTaskEmail = async ({ to, subject, type = "text", html, text, attachmen
},
(err, info) => {
// (message, type, user, record, meta
logger.log("server-email", err ? "error" : "debug", null, null, { message: err || info });
logger.log("server-email", err ? "error" : "debug", null, null, { message: err ? err?.message : info });
}
);
} catch (error) {
@@ -239,24 +239,24 @@ const emailBounce = async (req, res) => {
return;
}
//If it's bounced, log it as bounced in audit log. Send an email to the user.
const result = await client.request(queries.UPDATE_EMAIL_AUDIT, {
await client.request(queries.UPDATE_EMAIL_AUDIT, {
sesid: messageId,
status: "Bounced",
context: message.bounce?.bouncedRecipients
});
mailer.sendMail(
{
from: InstanceMgr({
from: InstanceManager({
imex: `ImEX Online <noreply@imex.online>`,
rome: `Rome Online <noreply@romeonline.io>`
}),
to: replyTo,
//bcc: "patrick@snapt.ca",
subject: `${InstanceMgr({
subject: `${InstanceManager({
imex: "ImEX Online",
rome: "Rome Online"
})} Bounced Email - RE: ${subject}`,
text: `${InstanceMgr({
text: `${InstanceManager({
imex: "ImEX Online",
rome: "Rome Online"
})} has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error.
@@ -270,7 +270,7 @@ ${body.bounce?.bouncedRecipients.map(
},
(err, info) => {
logger.log("sns-error", err ? "error" : "debug", "api", null, {
message: err ? JSON.stringify(error) : info
message: err ? err?.message : info
});
}
);

View File

@@ -2241,6 +2241,7 @@ exports.QUERY_PARTS_SCAN = `query QUERY_PARTS_SCAN ($id: uuid!) {
mod_lb_hrs
oem_partno
alt_partno
op_code_desc
}
}
}`;
@@ -2252,7 +2253,7 @@ exports.UPDATE_PARTS_CRITICAL = `mutation UPDATE_PARTS_CRITICAL ($IdsToMarkCriti
notcritical: update_joblines(where: {id: {_nin: $IdsToMarkCritical}, jobid: {_eq: $jobid}}, _set: {critical: false}) {
affected_rows
}
}`
}`;
exports.ACTIVE_SHOP_BY_USER = `query ACTIVE_SHOP_BY_USER($user: String) {
associations(where: {active: {_eq: true}, useremail: {_eq: $user}}) {
@@ -2618,7 +2619,6 @@ 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
@@ -2689,4 +2689,19 @@ exports.STATUS_UPDATE = `query STATUS_UPDATE($period: timestamptz!, $today: time
}
}
}
`
`;
exports.INSERT_AUDIT_TRAIL = `
mutation INSERT_AUDIT_TRAIL($auditObj: audit_trail_insert_input!) {
insert_audit_trail_one(object: $auditObj) {
id
jobid
billid
bodyshopid
created
operation
type
useremail
}
}
`;

View File

@@ -1,16 +1,25 @@
const queries = require("../graphql-client/queries");
const logger = require("../utils/logger");
const predefinedPartTypes = [
"PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG",
];
const predefinedPartTypes = ["PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG"];
const predefinedModLbrTypes = [
"LAA", "LAB", "LAD", "LAE", "LAF", "LAG", "LAM", "LAR", "LAS", "LAU",
"LA1", "LA2", "LA3", "LA4",
"LAA",
"LAB",
"LAD",
"LAE",
"LAF",
"LAG",
"LAM",
"LAR",
"LAS",
"LAU",
"LA1",
"LA2",
"LA3",
"LA4"
];
exports.partsScan = async function (req, res) {
console.log('hello')
const { jobid } = req.body;
const BearerToken = req.BearerToken;
const client = req.userGraphQLClient;
@@ -18,10 +27,9 @@ exports.partsScan = async function (req, res) {
logger.log("job-parts-scan", "DEBUG", req.user?.email, jobid, null);
try {
const data = await client.setHeaders({ Authorization: BearerToken }).request(
queries.QUERY_PARTS_SCAN,
{ id: jobid }
);
const data = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.QUERY_PARTS_SCAN, { id: jobid });
const rules = data.jobs_by_pk.bodyshop.md_parts_scan || [];
if (!Array.isArray(rules)) {
@@ -36,21 +44,22 @@ exports.partsScan = async function (req, res) {
? new RegExp(rule.value, "i")
: typeof rule.value === "string"
? new RegExp(rule.value)
: null,
: null
}));
const criticalIds = new Set();
for (const jobline of data.jobs_by_pk.joblines) {
for (const { field, regex, operation, value } of compiledRules) {
if (criticalIds.has(jobline.id)) break; // Skip further evaluation if already critical
for (const { field, regex, operation, value, mark_critical, update_field, update_value } of compiledRules) {
// IO-3077 - Remove skip as this is extended to include line updates.
// if (criticalIds.has(jobline.id)) break; // Skip further evaluation if already critical
let jobValue = jobline[field];
let match = false;
if (field === "part_number") {
match = regex
? regex.test(jobline.oem_partno || '') || regex.test(jobline.alt_partno || '')
? regex.test(jobline.oem_partno || "") || regex.test(jobline.alt_partno || "")
: jobline.oem_partno === value || jobline.alt_partno === value;
} else if (field === "part_type") {
match = predefinedPartTypes.includes(value) && value === jobValue;
@@ -68,22 +77,44 @@ exports.partsScan = async function (req, res) {
}
if (match) {
criticalIds.add(jobline.id);
break; // No need to evaluate further rules for this jobline
if (mark_critical) {
//Could technically lead to duplicates, but they'd only be n positives, ultimately leading a positive.
criticalIds.add(jobline.id);
}
if (update_field && update_value) {
const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.UPDATE_JOB_LINE, {
lineId: jobline.id,
line: { [update_field]: update_value, manual_line: true }
});
const auditResult = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.INSERT_AUDIT_TRAIL, {
auditObj: {
bodyshopid: data.jobs_by_pk.bodyshop.id,
jobid,
operation: `Jobline (#${jobline.line_no} ${jobline.line_desc}/${jobline.id}) ${update_field} updated from ${jobline[update_field]} to ${update_value}. Lined marked as manual line.`,
type: "partscanupdate",
useremail: req.user.email
}
});
}
//break; // No need to evaluate further rules for this jobline
}
}
}
const result = await client.setHeaders({ Authorization: BearerToken }).request(
queries.UPDATE_PARTS_CRITICAL,
{ IdsToMarkCritical: Array.from(criticalIds), jobid }
);
const result = await client
.setHeaders({ Authorization: BearerToken })
.request(queries.UPDATE_PARTS_CRITICAL, { IdsToMarkCritical: Array.from(criticalIds), jobid });
res.status(200).json(result);
} catch (error) {
logger.log("job-parts-scan-error", "ERROR", req.user?.email, jobid, {
jobid,
error: error.message,
stack: error.stack
});
res.status(400).json(JSON.stringify({ message: error?.message }));
}