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
@@ -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}}) {
@@ -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 = `
query GET_DOCUMENTS_BY_JOB($jobId: uuid!) {
jobs_by_pk(id: $jobId) {

View File

@@ -99,6 +99,7 @@ async function OpenSearchUpdateHandler(req, res) {
break;
case "payments":
//Query to get the job and RO number
const payment = await client.request(
`query ADMIN_GET_PAYMENT_BY_ID($paymentId: uuid!) {
payments_by_pk(id: $paymentId) {
@@ -147,11 +148,25 @@ async function OpenSearchUpdateHandler(req, res) {
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);
//console.log(response.body);
res.status(200).json(response.body);
}
} 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));
}
}
@@ -166,7 +181,9 @@ async function OpenSearchSearchHandler(req, res) {
}
logger.log("os-search", "DEBUG", req.user.email, null, {
search
search,
index,
bodyshopid
});
const BearerToken = req.BearerToken;

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 }));
}