Merge branch 'master-AIO' into feature/IO-3092-imgproxy

This commit is contained in:
Patrick Fic
2025-02-21 13:03:37 -08:00
16 changed files with 11819 additions and 11418 deletions

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 BabelEdit project file
@@ -6453,6 +6453,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>operation</name> <name>operation</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -6474,6 +6495,48 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>value</name> <name>value</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -11943,6 +12006,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>shopinfo</name> <name>shopinfo</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -12312,6 +12396,37 @@
</concept_node> </concept_node>
</children> </children>
</folder_node> </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> <folder_node>
<name>validation</name> <name>validation</name>
<children> <children>
@@ -19091,6 +19206,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>previous</name> <name>previous</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -19385,6 +19521,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>submit</name> <name>submit</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -43090,6 +43247,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>print</name> <name>print</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -48557,6 +48735,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>vertical</name> <name>vertical</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -52732,6 +52931,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> <concept_node>
<name>purchases_by_date_range_detail</name> <name>purchases_by_date_range_detail</name>
<definition_loaded>false</definition_loaded> <definition_loaded>false</definition_loaded>
@@ -54483,6 +54703,27 @@
</translation> </translation>
</translations> </translations>
</concept_node> </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> </children>
</folder_node> </folder_node>
<folder_node> <folder_node>

View File

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

View File

@@ -1,16 +1,15 @@
import i18next from "i18next";
import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { setUserLanguage } from "../../redux/user/user.actions";
import HeaderComponent from "./header.component"; import HeaderComponent from "./header.component";
import { logImEXEvent } from "../../firebase/firebase.utils";
const mapDispatchToProps = (dispatch) => ({ // const mapDispatchToProps = (dispatch) => ({
setUserLanguage: (language) => dispatch(setUserLanguage(language)) // setUserLanguage: (language) => dispatch(setUserLanguage(language))
}); // });
export function HeaderContainer({ setUserLanguage }) { // setUserLanguage was removed from signature because it is not used in the component, and it is throwing a deprecation warning
const handleMenuClick = (e) => { 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") { if (e.item.props.actiontype === "lang-select") {
i18next.changeLanguage(e.key, (err, t) => { i18next.changeLanguage(e.key, (err, t) => {
if (err) { if (err) {
@@ -23,9 +22,10 @@ export function HeaderContainer({ setUserLanguage }) {
setUserLanguage(e.key); 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"] refetchQueries: ["GET_LINE_TICKET_BY_PK"]
}); });
if (!r.errors) { if (!r.errors) {
if (CriticalPartsScanning.treatment === "on") {
await CriticalPartsScan(jobLineEditModal.context.jobid, notification);
}
await Axios.post("/job/totalsssu", { await Axios.post("/job/totalsssu", {
id: jobLineEditModal.context.jobid 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) { if (jobLineEditModal.actions.submit) {
jobLineEditModal.actions.submit(); jobLineEditModal.actions.submit();
} else { } else {
@@ -115,9 +120,7 @@ function JobLinesUpsertModalContainer({ jobLineEditModal, toggleModalVisible, bo
} }
toggleModalVisible(); toggleModalVisible();
} }
if (CriticalPartsScanning.treatment === "on") {
CriticalPartsScan(jobLineEditModal.context.jobid, notification);
}
setLoading(false); setLoading(false);
}; };

View File

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

View File

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

View File

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

View File

@@ -347,6 +347,9 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
window.$zoho.salesiq.visitor.info({ "Shop Name": payload.shopname }); 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) { } catch (error) {
console.error("Couldnt find $crisp."); 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: "" }); const csv = converter.json2csv(shopList, { emptyFieldValue: "" });
emailer emailer
.sendTaskEmail({ .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")}`, subject: `RO Usage Report - ${moment().format("MM/DD/YYYY")}`,
text: ` text: `
Usage Report for ${moment().format("MM/DD/YYYY")} for Rome Online Customers. 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>`, imex: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
rome: `Rome Online API - ${process.env.NODE_ENV} <noreply@romeonline.io>` rome: `Rome Online API - ${process.env.NODE_ENV} <noreply@romeonline.io>`
}), }),
to: ["patrick@imexsystems.ca", "support@thinkimex.com"], to: ["support@thinkimex.com"],
subject: subject, subject: subject,
text: text, text: text,
ses: { ses: {
@@ -92,7 +92,7 @@ const sendTaskEmail = async ({ to, subject, type = "text", html, text, attachmen
}, },
(err, info) => { (err, info) => {
// (message, type, user, record, meta // (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) { } catch (error) {
@@ -239,24 +239,24 @@ const emailBounce = async (req, res) => {
return; return;
} }
//If it's bounced, log it as bounced in audit log. Send an email to the user. //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, sesid: messageId,
status: "Bounced", status: "Bounced",
context: message.bounce?.bouncedRecipients context: message.bounce?.bouncedRecipients
}); });
mailer.sendMail( mailer.sendMail(
{ {
from: InstanceMgr({ from: InstanceManager({
imex: `ImEX Online <noreply@imex.online>`, imex: `ImEX Online <noreply@imex.online>`,
rome: `Rome Online <noreply@romeonline.io>` rome: `Rome Online <noreply@romeonline.io>`
}), }),
to: replyTo, to: replyTo,
//bcc: "patrick@snapt.ca", //bcc: "patrick@snapt.ca",
subject: `${InstanceMgr({ subject: `${InstanceManager({
imex: "ImEX Online", imex: "ImEX Online",
rome: "Rome Online" rome: "Rome Online"
})} Bounced Email - RE: ${subject}`, })} Bounced Email - RE: ${subject}`,
text: `${InstanceMgr({ text: `${InstanceManager({
imex: "ImEX Online", imex: "ImEX Online",
rome: "Rome Online" rome: "Rome Online"
})} has tried to deliver an email with the subject: ${subject} to the intended recipients but encountered an error. })} 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) => { (err, info) => {
logger.log("sns-error", err ? "error" : "debug", "api", null, { 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 mod_lb_hrs
oem_partno oem_partno
alt_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}) { notcritical: update_joblines(where: {id: {_nin: $IdsToMarkCritical}, jobid: {_eq: $jobid}}, _set: {critical: false}) {
affected_rows affected_rows
} }
}`; }`;;
exports.ACTIVE_SHOP_BY_USER = `query ACTIVE_SHOP_BY_USER($user: String) { exports.ACTIVE_SHOP_BY_USER = `query ACTIVE_SHOP_BY_USER($user: String) {
associations(where: {active: {_eq: true}, useremail: {_eq: $user}}) { associations(where: {active: {_eq: true}, useremail: {_eq: $user}}) {
@@ -2690,6 +2691,23 @@ 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
}
}
`;
exports.GET_DOCUMENTS_BY_JOB = ` exports.GET_DOCUMENTS_BY_JOB = `
query GET_DOCUMENTS_BY_JOB($jobId: uuid!) { query GET_DOCUMENTS_BY_JOB($jobId: uuid!) {
jobs_by_pk(id: $jobId) { jobs_by_pk(id: $jobId) {

View File

@@ -99,6 +99,7 @@ async function OpenSearchUpdateHandler(req, res) {
break; break;
case "payments": case "payments":
//Query to get the job and RO number //Query to get the job and RO number
const payment = await client.request( const payment = await client.request(
`query ADMIN_GET_PAYMENT_BY_ID($paymentId: uuid!) { `query ADMIN_GET_PAYMENT_BY_ID($paymentId: uuid!) {
payments_by_pk(id: $paymentId) { payments_by_pk(id: $paymentId) {
@@ -147,11 +148,25 @@ async function OpenSearchUpdateHandler(req, res) {
body: document body: document
}; };
logger.log("os-handler", "DEBUG", null, null, {
id: req.body.event.data.new.id,
index: req.body.table.name,
bodyshopid: payload.body.bodyshopid
// body: document
});
const response = await osClient.index(payload); const response = await osClient.index(payload);
//console.log(response.body); //console.log(response.body);
res.status(200).json(response.body); res.status(200).json(response.body);
} }
} catch (error) { } catch (error) {
logger.log("os-handler-error", "ERROR", null, null, {
id: req.body.event.data.new.id,
index: req.body.table.name,
message: error.message,
stack: error.stack
// body: document
});
res.status(400).json(JSON.stringify(error)); res.status(400).json(JSON.stringify(error));
} }
} }
@@ -166,7 +181,9 @@ async function OpenSearchSearchHandler(req, res) {
} }
logger.log("os-search", "DEBUG", req.user.email, null, { logger.log("os-search", "DEBUG", req.user.email, null, {
search search,
index,
bodyshopid
}); });
const BearerToken = req.BearerToken; const BearerToken = req.BearerToken;

View File

@@ -1,16 +1,25 @@
const queries = require("../graphql-client/queries"); const queries = require("../graphql-client/queries");
const logger = require("../utils/logger"); const logger = require("../utils/logger");
const predefinedPartTypes = [ const predefinedPartTypes = ["PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG"];
"PAN", "PAC", "PAR", "PAL", "PAA", "PAM", "PAP", "PAS", "PASL", "PAG",
];
const predefinedModLbrTypes = [ const predefinedModLbrTypes = [
"LAA", "LAB", "LAD", "LAE", "LAF", "LAG", "LAM", "LAR", "LAS", "LAU", "LAA",
"LA1", "LA2", "LA3", "LA4", "LAB",
"LAD",
"LAE",
"LAF",
"LAG",
"LAM",
"LAR",
"LAS",
"LAU",
"LA1",
"LA2",
"LA3",
"LA4"
]; ];
exports.partsScan = async function (req, res) { exports.partsScan = async function (req, res) {
console.log('hello')
const { jobid } = req.body; const { jobid } = req.body;
const BearerToken = req.BearerToken; const BearerToken = req.BearerToken;
const client = req.userGraphQLClient; 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); logger.log("job-parts-scan", "DEBUG", req.user?.email, jobid, null);
try { try {
const data = await client.setHeaders({ Authorization: BearerToken }).request( const data = await client
queries.QUERY_PARTS_SCAN, .setHeaders({ Authorization: BearerToken })
{ id: jobid } .request(queries.QUERY_PARTS_SCAN, { id: jobid });
);
const rules = data.jobs_by_pk.bodyshop.md_parts_scan || []; const rules = data.jobs_by_pk.bodyshop.md_parts_scan || [];
if (!Array.isArray(rules)) { if (!Array.isArray(rules)) {
@@ -36,21 +44,22 @@ exports.partsScan = async function (req, res) {
? new RegExp(rule.value, "i") ? new RegExp(rule.value, "i")
: typeof rule.value === "string" : typeof rule.value === "string"
? new RegExp(rule.value) ? new RegExp(rule.value)
: null, : null
})); }));
const criticalIds = new Set(); const criticalIds = new Set();
for (const jobline of data.jobs_by_pk.joblines) { for (const jobline of data.jobs_by_pk.joblines) {
for (const { field, regex, operation, value } of compiledRules) { for (const { field, regex, operation, value, mark_critical, update_field, update_value } of compiledRules) {
if (criticalIds.has(jobline.id)) break; // Skip further evaluation if already critical // 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 jobValue = jobline[field];
let match = false; let match = false;
if (field === "part_number") { if (field === "part_number") {
match = regex 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; : jobline.oem_partno === value || jobline.alt_partno === value;
} else if (field === "part_type") { } else if (field === "part_type") {
match = predefinedPartTypes.includes(value) && value === jobValue; match = predefinedPartTypes.includes(value) && value === jobValue;
@@ -68,22 +77,44 @@ exports.partsScan = async function (req, res) {
} }
if (match) { if (match) {
criticalIds.add(jobline.id); if (mark_critical) {
break; // No need to evaluate further rules for this jobline //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( const result = await client
queries.UPDATE_PARTS_CRITICAL, .setHeaders({ Authorization: BearerToken })
{ IdsToMarkCritical: Array.from(criticalIds), jobid } .request(queries.UPDATE_PARTS_CRITICAL, { IdsToMarkCritical: Array.from(criticalIds), jobid });
);
res.status(200).json(result); res.status(200).json(result);
} catch (error) { } catch (error) {
logger.log("job-parts-scan-error", "ERROR", req.user?.email, jobid, { logger.log("job-parts-scan-error", "ERROR", req.user?.email, jobid, {
jobid, jobid,
error: error.message, error: error.message,
stack: error.stack
}); });
res.status(400).json(JSON.stringify({ message: error?.message })); res.status(400).json(JSON.stringify({ message: error?.message }));
} }