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,14 +84,15 @@ 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 // Aggregate unread message count
return cachedData.conversations.reduce((total, conversation) => { return cachedData.conversations.reduce((total, conversation) => {
@@ -103,11 +103,6 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh
console.warn("Unread count not found in cache:", error); console.warn("Unread count not found in cache:", error);
return 0; // Fallback if not in cache return 0; // Fallback if not in cache
} }
} else if (unreadData?.messages_aggregate?.aggregate?.count) {
// Use the unread count from the query result
return unreadData.messages_aggregate.aggregate.count;
}
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: [ string: [
{label: t("bodyshop.operations.contains"), value: "contains"}, { label: t("bodyshop.operations.contains"), value: "contains" },
{label: t("bodyshop.operations.equals"), value: "equals"}, { label: t("bodyshop.operations.equals"), value: "equals" },
{label: t("bodyshop.operations.starts_with"), value: "startsWith"}, { label: t("bodyshop.operations.starts_with"), value: "startsWith" },
{label: t("bodyshop.operations.ends_with"), value: "endsWith"}, { label: t("bodyshop.operations.ends_with"), value: "endsWith" }
], ],
number: [ number: [
{label: t("bodyshop.operations.equals"), value: "="}, { label: t("bodyshop.operations.equals"), value: "=" },
{label: t("bodyshop.operations.greater_than"), value: ">"}, { label: t("bodyshop.operations.greater_than"), value: ">" },
{label: t("bodyshop.operations.less_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" ? (
@@ -139,7 +155,7 @@ export default function ShopInfoPartsScan({form}) {
} }
/> />
) : ( ) : (
<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.");
} }

View File

@@ -383,7 +383,10 @@
"expression": "", "expression": "",
"field": "Field", "field": "Field",
"flags": "", "flags": "",
"mark_critical": "Mark Line as Critical?",
"operation": "Operation", "operation": "Operation",
"update_field": "Field to Update",
"update_value": "Update Value",
"value": "Value" "value": "Value"
}, },
"md_payment_types": "Payment Types", "md_payment_types": "Payment Types",
@@ -686,7 +689,7 @@
"notespresets": "Notes Presets", "notespresets": "Notes Presets",
"orderstatuses": "Order Statuses", "orderstatuses": "Order Statuses",
"partslocations": "Parts Locations", "partslocations": "Parts Locations",
"partsscan": "Critical Parts Scanning", "partsscan": "Parts Scanning",
"printlater": "Print Later", "printlater": "Print Later",
"qbo": "Use QuickBooks Online?", "qbo": "Use QuickBooks Online?",
"qbo_departmentid": "QBO Department ID", "qbo_departmentid": "QBO Department ID",
@@ -707,13 +710,13 @@
"romepay": "Rome Pay", "romepay": "Rome Pay",
"scheduling": "SMART Scheduling", "scheduling": "SMART Scheduling",
"scoreboardsetup": "Scoreboard Setup", "scoreboardsetup": "Scoreboard Setup",
"shop_enabled_features": "Shop Enabled Features",
"shopinfo": "Shop Information", "shopinfo": "Shop Information",
"speedprint": "Speed Print Configuration", "speedprint": "Speed Print Configuration",
"ssbuckets": "Job Size Definitions", "ssbuckets": "Job Size Definitions",
"systemsettings": "System Settings", "systemsettings": "System Settings",
"task-presets": "Task Presets", "task-presets": "Task Presets",
"workingdays": "Working Days", "workingdays": "Working Days"
"shop_enabled_features": "Shop Enabled Features"
}, },
"operations": { "operations": {
"contains": "Contains", "contains": "Contains",
@@ -730,6 +733,11 @@
"save": "Shop configuration saved successfully. ", "save": "Shop configuration saved successfully. ",
"unsavedchanges": "Unsaved changes will be lost. Are you sure you want to continue?" "unsavedchanges": "Unsaved changes will be lost. Are you sure you want to continue?"
}, },
"tooltips": {
"md_parts_scan": {
"update_value_tooltip": "Some fields require coded values in order to function properly (e.g. labor and part types). Please reach out to support if you have any questions."
}
},
"validation": { "validation": {
"centermustexist": "The chosen responsibility center does not exist.", "centermustexist": "The chosen responsibility center does not exist.",
"larsplit": "Refinish hour split must add up to 1.", "larsplit": "Refinish hour split must add up to 1.",
@@ -970,10 +978,10 @@
"productiondollars": "Total Dollars in Production", "productiondollars": "Total Dollars in Production",
"productionhours": "Total Hours in Production", "productionhours": "Total Hours in Production",
"projectedmonthlysales": "Projected Monthly Sales", "projectedmonthlysales": "Projected Monthly Sales",
"scheduledindate": "Sheduled In Today: {{date}}", "scheduledindate": "Scheduled In Today: {{date}}",
"scheduledintoday": "Sheduled In Today", "scheduledintoday": "Scheduled In Today",
"scheduledoutdate": "Sheduled Out Today: {{date}}", "scheduledoutdate": "Scheduled Out Today: {{date}}",
"scheduledouttoday": "Sheduled Out Today", "scheduledouttoday": "Scheduled Out Today",
"tasks": "Tasks" "tasks": "Tasks"
} }
}, },
@@ -1177,6 +1185,7 @@
"edit": "Edit", "edit": "Edit",
"login": "Login", "login": "Login",
"next": "Next", "next": "Next",
"ok": "Ok",
"previous": "Previous", "previous": "Previous",
"print": "Print", "print": "Print",
"refresh": "Refresh", "refresh": "Refresh",
@@ -1191,12 +1200,11 @@
"send": "Send", "send": "Send",
"sendbysms": "Send by SMS", "sendbysms": "Send by SMS",
"senderrortosupport": "Send Error to Support", "senderrortosupport": "Send Error to Support",
"sharetoteams": "Share to Teams",
"submit": "Submit", "submit": "Submit",
"tryagain": "Try Again", "tryagain": "Try Again",
"view": "View", "view": "View",
"viewreleasenotes": "See What's Changed", "viewreleasenotes": "See What's Changed"
"sharetoteams": "Share to Teams",
"ok": "Ok"
}, },
"errors": { "errors": {
"fcm": "You must allow notification permissions to have real time messaging. Click to try again.", "fcm": "You must allow notification permissions to have real time messaging. Click to try again.",
@@ -2858,10 +2866,10 @@
"tasks": "Tasks", "tasks": "Tasks",
"totalhours": "Total Hrs ", "totalhours": "Total Hrs ",
"touchtime": "T/T", "touchtime": "T/T",
"unassigned": "Unassigned",
"vertical": "Vertical", "vertical": "Vertical",
"viewname": "View Name", "viewname": "View Name",
"wide": "Wide", "wide": "Wide"
"unassigned": "Unassigned"
}, },
"options": { "options": {
"horizontal": "Horizontal", "horizontal": "Horizontal",

View File

@@ -383,7 +383,10 @@
"expression": "", "expression": "",
"field": "", "field": "",
"flags": "", "flags": "",
"mark_critical": "",
"operation": "", "operation": "",
"update_field": "",
"update_value": "",
"value": "" "value": ""
}, },
"md_payment_types": "", "md_payment_types": "",
@@ -707,13 +710,13 @@
"romepay": "", "romepay": "",
"scheduling": "", "scheduling": "",
"scoreboardsetup": "", "scoreboardsetup": "",
"shop_enabled_features": "",
"shopinfo": "", "shopinfo": "",
"speedprint": "", "speedprint": "",
"ssbuckets": "", "ssbuckets": "",
"systemsettings": "", "systemsettings": "",
"task-presets": "", "task-presets": "",
"workingdays": "", "workingdays": ""
"shop_enabled_features": ""
}, },
"operations": { "operations": {
"contains": "", "contains": "",
@@ -730,6 +733,11 @@
"save": "", "save": "",
"unsavedchanges": "" "unsavedchanges": ""
}, },
"tooltips": {
"md_parts_scan": {
"update_value_tooltip": ""
}
},
"validation": { "validation": {
"centermustexist": "", "centermustexist": "",
"larsplit": "", "larsplit": "",
@@ -1177,6 +1185,7 @@
"edit": "Editar", "edit": "Editar",
"login": "", "login": "",
"next": "", "next": "",
"ok": "",
"previous": "", "previous": "",
"print": "", "print": "",
"refresh": "", "refresh": "",
@@ -1191,12 +1200,11 @@
"send": "", "send": "",
"sendbysms": "", "sendbysms": "",
"senderrortosupport": "", "senderrortosupport": "",
"sharetoteams": "",
"submit": "", "submit": "",
"tryagain": "", "tryagain": "",
"view": "", "view": "",
"viewreleasenotes": "", "viewreleasenotes": ""
"sharetoteams": "",
"ok": ""
}, },
"errors": { "errors": {
"fcm": "", "fcm": "",
@@ -2858,10 +2866,10 @@
"tasks": "", "tasks": "",
"totalhours": "", "totalhours": "",
"touchtime": "", "touchtime": "",
"unassigned": "",
"vertical": "", "vertical": "",
"viewname": "", "viewname": "",
"wide": "", "wide": ""
"unassigned": ""
}, },
"options": { "options": {
"horizontal": "", "horizontal": "",

View File

@@ -383,7 +383,10 @@
"expression": "", "expression": "",
"field": "", "field": "",
"flags": "", "flags": "",
"mark_critical": "",
"operation": "", "operation": "",
"update_field": "",
"update_value": "",
"value": "" "value": ""
}, },
"md_payment_types": "", "md_payment_types": "",
@@ -707,13 +710,13 @@
"romepay": "", "romepay": "",
"scheduling": "", "scheduling": "",
"scoreboardsetup": "", "scoreboardsetup": "",
"shop_enabled_features": "",
"shopinfo": "", "shopinfo": "",
"speedprint": "", "speedprint": "",
"ssbuckets": "", "ssbuckets": "",
"systemsettings": "", "systemsettings": "",
"task-presets": "", "task-presets": "",
"workingdays": "", "workingdays": ""
"shop_enabled_features": ""
}, },
"operations": { "operations": {
"contains": "", "contains": "",
@@ -730,6 +733,11 @@
"save": "", "save": "",
"unsavedchanges": "" "unsavedchanges": ""
}, },
"tooltips": {
"md_parts_scan": {
"update_value_tooltip": ""
}
},
"validation": { "validation": {
"centermustexist": "", "centermustexist": "",
"larsplit": "", "larsplit": "",
@@ -1177,6 +1185,7 @@
"edit": "modifier", "edit": "modifier",
"login": "", "login": "",
"next": "", "next": "",
"ok": "",
"previous": "", "previous": "",
"print": "", "print": "",
"refresh": "", "refresh": "",
@@ -1191,12 +1200,11 @@
"send": "", "send": "",
"sendbysms": "", "sendbysms": "",
"senderrortosupport": "", "senderrortosupport": "",
"sharetoteams": "",
"submit": "", "submit": "",
"tryagain": "", "tryagain": "",
"view": "", "view": "",
"viewreleasenotes": "", "viewreleasenotes": ""
"sharetoteams": "",
"ok": ""
}, },
"errors": { "errors": {
"fcm": "", "fcm": "",
@@ -2858,10 +2866,10 @@
"tasks": "", "tasks": "",
"totalhours": "", "totalhours": "",
"touchtime": "", "touchtime": "",
"unassigned": "",
"vertical": "", "vertical": "",
"viewname": "", "viewname": "",
"wide": "", "wide": ""
"unassigned": ""
}, },
"options": { "options": {
"horizontal": "", "horizontal": "",

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) {
if (mark_critical) {
//Could technically lead to duplicates, but they'd only be n positives, ultimately leading a positive.
criticalIds.add(jobline.id); criticalIds.add(jobline.id);
break; // No need to evaluate further rules for this jobline }
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 }));
} }