Compare commits

..

12 Commits

Author SHA1 Message Date
Patrick Fic
0ced053d21 IO-2935 Correct CORS entries. 2024-09-25 12:29:23 -07:00
Patrick Fic
ff72657a82 IO-2935 Resolve roll up issues. 2024-09-25 12:16:51 -07:00
Patrick Fic
b1a96d55ad IO-2935 Comment unused visualizer during build. 2024-09-25 12:04:24 -07:00
Patrick Fic
7094b6ffbf IO-2935 improve chunking for vite build. 2024-09-25 11:59:33 -07:00
Dave Richer
45a9e37342 IO-2935-Add-Enhanced-Websocket-Provider - Add Firebase token refresh
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-25 14:12:31 -04:00
Dave Richer
55a279a700 IO-2935-Add-Enhanced-Websocket-Provider - add emit from io-logging event to create additional useage
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-25 13:36:47 -04:00
Dave Richer
82e2e332cf IO-2935-Add-Enhanced-Websocket-Provider - fixes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-24 20:54:14 -04:00
Dave Richer
103d7c2bb2 IO-2935-Add-Enhanced-Websocket-Provider - Add additional try catch statements
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-24 20:10:15 -04:00
Dave Richer
f5f0b75617 IO-2935-Add-Enhanced-Websocket-Provider - PR notes, Admin UI, Expanded Join/Leave room functionality, moved test button to the client id (Imex / Rome) in footer, so it is still there, just invisible.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-24 19:59:29 -04:00
Dave Richer
c163554c3f IO-2935-Add-Enhanced-Websocket-Provider - Add another web socket provider, front end wiring, vite wiring (proxy and secure vite), bumped deps
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-24 16:28:44 -04:00
Dave Richer
6480f7f2aa Merge remote-tracking branch 'origin/release/2024-09-27' into feature/IO-2935-Deploy-New-WSS 2024-09-24 14:48:15 -04:00
Patrick Fic
ff46bbbb3f Merged in feature/IO-2597-task-reminders (pull request #1770)
IO-2957 Resolve task reminders not sending with incorrect references.
2024-09-24 01:24:18 +00:00
26 changed files with 2291 additions and 2247 deletions

1636
client/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,7 @@
"private": true,
"proxy": "http://localhost:4000",
"dependencies": {
"@ant-design/pro-layout": "^7.20.2",
"@ant-design/pro-layout": "^7.19.12",
"@apollo/client": "^3.11.8",
"@emotion/is-prop-valid": "^1.3.1",
"@fingerprintjs/fingerprintjs": "^4.5.0",
@@ -19,7 +19,7 @@
"@splitsoftware/splitio-react": "^1.13.0",
"@tanem/react-nprogress": "^5.0.51",
"@vitejs/plugin-react": "^4.3.1",
"antd": "^5.21.0",
"antd": "^5.20.1",
"apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^3.3.0",
"autosize": "^6.0.1",

View File

@@ -1,66 +1,62 @@
import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Form, Input, Table } from "antd";
import React, { useEffect, useState, useContext } from "react";
import React, { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { pageLimit } from "../../utils/config";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx"; // Import SocketContext
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
bodyshop: selectBodyshop
});
export default connect(mapStateToProps)(DmsAllocationsSummaryAp);
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export function DmsAllocationsSummaryAp({ bodyshop, billids, title }) {
export default connect(mapStateToProps, mapDispatchToProps)(DmsAllocationsSummaryAp);
export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
const { t } = useTranslation();
const [allocationsSummary, setAllocationsSummary] = useState([]);
const { socket } = useContext(SocketContext);
useEffect(() => {
if (!socket || !socket.connected) return;
const handleSuccess = async (billid) => {
socket.on("ap-export-success", (billid) => {
setAllocationsSummary((allocationsSummary) =>
allocationsSummary.map((a) => {
if (a.billid !== billid) return a;
return { ...a, status: "Successful" };
})
);
});
socket.on("ap-export-failure", ({ billid, error }) => {
allocationsSummary.map((a) => {
if (a.billid !== billid) return a;
return { ...a, status: error };
});
});
try {
await new Promise((resolve, reject) => {
socket.emit("clear-dms-session", (response) => {
if (response && response.status === "ok") {
resolve();
} else {
reject(new Error("Failed to clear DMS session"));
}
});
});
} catch (error) {
console.error("Failed to clear DMS session", error);
}
};
socket.on("ap-export-success", handleSuccess);
if (socket.disconnected) socket.connect();
return () => {
socket.off("ap-export-success", handleSuccess);
socket.removeListener("ap-export-success");
socket.removeListener("ap-export-failure");
//socket.disconnect();
};
}, [socket]);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
useEffect(() => {
if (socket && socket.connected) {
if (socket.connected) {
socket.emit("pbs-calculate-allocations-ap", billids, (ack) => {
setAllocationsSummary(ack);
socket.allocationsSummary = ack;
});
}
}, [socket, socket.connected, billids]);
console.log(allocationsSummary);
const columns = [
{
title: t("general.labels.status"),
@@ -72,40 +68,35 @@ export function DmsAllocationsSummaryAp({ bodyshop, billids, title }) {
dataIndex: ["Posting", "Reference"],
key: "reference"
},
{
title: t("jobs.fields.dms.lines"),
dataIndex: "Lines",
key: "Lines",
render: (text, record) => (
<table style={{ tableLayout: "auto", width: "100%" }}>
<thead>
<tr>
<th>{t("bills.fields.invoice_number")}</th>
<th>{t("bodyshop.fields.dms.dms_acctnumber")}</th>
<th>{t("jobs.fields.dms.amount")}</th>
<tr>
<th>{t("bills.fields.invoice_number")}</th>
<th>{t("bodyshop.fields.dms.dms_acctnumber")}</th>
<th>{t("jobs.fields.dms.amount")}</th>
</tr>
{record.Posting.Lines.map((l, idx) => (
<tr key={idx}>
<td>{l.InvoiceNumber}</td>
<td>{l.Account}</td>
<td>{l.Amount}</td>
</tr>
</thead>
<tbody>
{record.Posting.Lines.map((l, idx) => (
<tr key={idx}>
<td>{l.InvoiceNumber}</td>
<td>{l.Account}</td>
<td>{l.Amount}</td>
</tr>
))}
</tbody>
))}
</table>
)
}
];
const handleFinish = async (values) => {
if (socket) {
socket.emit("pbs-export-ap", {
billids,
txEnvelope: values
});
}
socket.emit(`pbs-export-ap`, {
billids,
txEnvelope: values
});
};
return (
@@ -114,9 +105,7 @@ export function DmsAllocationsSummaryAp({ bodyshop, billids, title }) {
extra={
<Button
onClick={() => {
if (socket) {
socket.emit("pbs-calculate-allocations-ap", billids, (ack) => setAllocationsSummary(ack));
}
socket.emit("pbs-calculate-allocations-ap", billids, (ack) => setAllocationsSummary(ack));
}}
>
<SyncOutlined />
@@ -135,7 +124,12 @@ export function DmsAllocationsSummaryAp({ bodyshop, billids, title }) {
name="journal"
label={t("jobs.fields.dms.journal")}
initialValue={bodyshop.cdk_configuration && bodyshop.cdk_configuration.default_journal}
rules={[{ required: true }]}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
>
<Input />
</Form.Item>

View File

@@ -24,7 +24,7 @@ export function DmsAllocationsSummary({ socket, bodyshop, jobId, title }) {
const [allocationsSummary, setAllocationsSummary] = useState([]);
useEffect(() => {
if (socket && socket.connected) {
if (socket.connected) {
socket.emit("cdk-calculate-allocations", jobId, (ack) => {
setAllocationsSummary(ack);
socket.allocationsSummary = ack;

View File

@@ -1,51 +1,37 @@
import { Button, Checkbox, Col, Table } from "antd";
import React, { useContext, useEffect, useState } from "react";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { socket } from "../../pages/dms/dms.container";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { alphaSort } from "../../utils/sorters";
import SocketContext from "../../contexts/SocketIO/socketContext"; // Import Socket Context
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsCustomerSelector);
export function DmsCustomerSelector({ bodyshop }) {
const { t } = useTranslation();
const { socket } = useContext(SocketContext); // Use Socket Context
const [customerList, setCustomerList] = useState([]);
const [customerList, setcustomerList] = useState([]);
const [open, setOpen] = useState(false);
const [selectedCustomer, setSelectedCustomer] = useState(null);
const [dmsType, setDmsType] = useState("cdk");
useEffect(() => {
if (socket) {
const handleCdkSelectCustomer = (customerList) => {
setOpen(true);
setDmsType("cdk");
setCustomerList(customerList);
};
const handlePbsSelectCustomer = (customerList) => {
setOpen(true);
setDmsType("pbs");
setCustomerList(customerList);
};
socket.on("cdk-select-customer", handleCdkSelectCustomer);
socket.on("pbs-select-customer", handlePbsSelectCustomer);
// Clean up listeners on unmount
return () => {
socket.off("cdk-select-customer", handleCdkSelectCustomer);
socket.off("pbs-select-customer", handlePbsSelectCustomer);
};
}
}, [socket]);
socket.on("cdk-select-customer", (customerList, callback) => {
setOpen(true);
setDmsType("cdk");
setcustomerList(customerList);
});
socket.on("pbs-select-customer", (customerList, callback) => {
setOpen(true);
setDmsType("pbs");
setcustomerList(customerList);
});
const onUseSelected = () => {
setOpen(false);
@@ -83,11 +69,17 @@ export function DmsCustomerSelector({ bodyshop }) {
key: "name1",
sorter: (a, b) => alphaSort(a.name1 && a.name1.fullName, b.name1 && b.name1.fullName)
},
{
title: t("jobs.fields.dms.address"),
//dataIndex: ["name2", "fullName"],
key: "address",
render: (record) =>
`${record.address?.addressLine?.[0]}, ${record.address?.city} ${record.address?.stateOrProvince} ${record.address?.postalCode}`
render: (record, value) =>
`${
record.address && record.address.addressLine && record.address.addressLine[0]
}, ${record.address && record.address.city} ${
record.address && record.address.stateOrProvince
} ${record.address && record.address.postalCode}`
}
];
@@ -103,15 +95,15 @@ export function DmsCustomerSelector({ bodyshop }) {
sorter: (a, b) => alphaSort(a.LastName, b.LastName),
render: (text, record) => `${record.FirstName || ""} ${record.LastName || ""}`
},
{
title: t("jobs.fields.dms.address"),
key: "address",
render: (record) => `${record.Address}, ${record.City} ${record.State} ${record.ZipCode}`
render: (record, value) => `${record.Address}, ${record.City} ${record.State} ${record.ZipCode}`
}
];
if (!open) return null;
return (
<Col span={24}>
<Table
@@ -133,6 +125,7 @@ export function DmsCustomerSelector({ bodyshop }) {
columns={dmsType === "cdk" ? cdkColumns : pbsColumns}
rowKey={(record) => (dmsType === "cdk" ? record.id.value : record.ContactId)}
dataSource={customerList}
//onChange={handleTableChange}
rowSelection={{
onSelect: (record) => {
setSelectedCustomer(dmsType === "cdk" ? record.id.value : record.ContactId);

View File

@@ -4,8 +4,11 @@ import React from "react";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
const mapStateToProps = createStructuredSelector({});
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -14,7 +17,7 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents);
export function DmsLogEvents({ logs }) {
export function DmsLogEvents({ socket, logs, bodyshop }) {
return (
<Timeline
pending

View File

@@ -46,7 +46,6 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp
onError: (error) => console.error(`Error fetching Kanban settings: ${error.message}`)
});
// This provides us the current version of the Lanes from the Redux store
// const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
useEffect(() => {

View File

@@ -4,50 +4,79 @@ import { auth } from "../../firebase/firebase.utils";
const useSocket = (bodyshop) => {
const [socket, setSocket] = useState(null);
const [clientId, setClientId] = useState(null); // State to store unique identifier
const [clientId, setClientId] = useState(null);
const [token, setToken] = useState(null);
useEffect(() => {
if (bodyshop && bodyshop.id) {
const endpoint = import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "https://localhost:3000";
// Listener for token changes
const unsubscribe = auth.onIdTokenChanged(async (user) => {
if (user) {
const newToken = await user.getIdToken();
setToken(newToken);
} else {
setToken(null);
}
});
// Clean up the listener on unmount
return () => unsubscribe();
}, []);
useEffect(() => {
if (bodyshop && bodyshop.id && token) {
const endpoint = import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "";
const socketInstance = SocketIO(endpoint, {
path: "/ws", // Ensure this matches the Vite proxy and backend path
path: "/wss",
withCredentials: true,
auth: async (callback) => {
const token = auth.currentUser && (await auth.currentUser.getIdToken());
callback({ token });
},
reconnectionAttempts: Infinity, // Try reconnecting forever
reconnectionDelay: 2000, // How long to wait between reconnection attempts
reconnectionDelayMax: 10000 // Maximum delay between attempts
auth: { token }, // Use the current token
reconnectionAttempts: Infinity,
reconnectionDelay: 2000,
reconnectionDelayMax: 10000
});
setSocket(socketInstance);
socketInstance.on("connect", () => {
const handleBodyshopMessage = (message) => {
console.log(`Received message for bodyshop ${bodyshop.id}:`, message);
};
const handleConnect = () => {
console.log("Socket connected:", socketInstance.id);
socketInstance.emit("join-bodyshop-room", bodyshop.id);
setClientId(socketInstance.id);
});
};
socketInstance.on("reconnect", (attempt) => {
const handleReconnect = (attempt) => {
console.log(`Socket reconnected after ${attempt} attempts`);
});
};
socketInstance.on("connect_error", (err) => {
const handleConnectionError = (err) => {
console.error("Socket connection error:", err);
});
};
socketInstance.on("disconnect", () => {
const handleDisconnect = () => {
console.log("Socket disconnected");
});
};
socketInstance.on("connect", handleConnect);
socketInstance.on("reconnect", handleReconnect);
socketInstance.on("connect_error", handleConnectionError);
socketInstance.on("disconnect", handleDisconnect);
socketInstance.on("bodyshop-message", handleBodyshopMessage);
return () => {
socketInstance.emit("leave-bodyshop-room", bodyshop.id);
socketInstance.off("connect", handleConnect);
socketInstance.off("reconnect", handleReconnect);
socketInstance.off("connect_error", handleConnectionError);
socketInstance.off("disconnect", handleDisconnect);
socketInstance.off("bodyshop-message", handleBodyshopMessage);
socketInstance.disconnect();
};
}
}, [bodyshop]);
}, [bodyshop, token]); // Include 'token' in dependencies to re-run effect when it changes
// Return both socket and clientId
return { socket, clientId };
};

View File

@@ -1,16 +1,20 @@
import { Button, Card, Col, notification, Row, Select, Space } from "antd";
import React, { useContext, useEffect, useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import SocketIO from "socket.io-client";
import DmsAllocationsSummaryApComponent from "../../components/dms-allocations-summary-ap/dms-allocations-summary-ap.component";
import DmsLogEvents from "../../components/dms-log-events/dms-log-events.component";
import { auth } from "../../firebase/firebase.utils";
import { setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import SocketContext from "../../contexts/SocketIO/socketContext";
const mapStateToProps = createStructuredSelector({});
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -19,9 +23,17 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
export function DmsContainer({ setBreadcrumbs, setSelectedHeader }) {
export const socket = SocketIO(import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "", {
path: "/ws",
withCredentials: true,
auth: async (callback) => {
const token = auth.currentUser && (await auth.currentUser.getIdToken());
callback({ token });
}
});
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const { socket } = useContext(SocketContext);
const [logLevel, setLogLevel] = useState("DEBUG");
const history = useNavigate();
const [logs, setLogs] = useState([]);
@@ -52,43 +64,40 @@ export function DmsContainer({ setBreadcrumbs, setSelectedHeader }) {
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
if (socket) {
const handleConnect = () => socket.emit("set-log-level", logLevel);
const handleReconnect = () => {
setLogs((logs) => [
socket.on("connect", () => socket.emit("set-log-level", logLevel));
socket.on("reconnect", () => {
setLogs((logs) => {
return [
...logs,
{
timestamp: new Date(),
level: "WARNING",
message: "Reconnected to DMS Export Service"
message: "Reconnected to CDK Export Service"
}
]);
};
const handleLogEvent = (payload) => {
setLogs((logs) => [...logs, payload]);
};
const handleExportComplete = () => {
notification.open({
type: "success",
message: t("jobs.labels.dms.apexported")
});
};
];
});
});
socket.on("connect", handleConnect);
socket.on("reconnect", handleReconnect);
socket.on("log-event", handleLogEvent);
socket.on("ap-export-complete", handleExportComplete);
socket.on("log-event", (payload) => {
setLogs((logs) => {
return [...logs, payload];
});
});
if (socket.disconnected) socket.connect();
socket.on("ap-export-complete", (payload) => {
notification.open({
type: "success",
message: t("jobs.labels.dms.apexported")
});
});
return () => {
socket.off("connect", handleConnect);
socket.off("reconnect", handleReconnect);
socket.off("log-event", handleLogEvent);
socket.off("ap-export-complete", handleExportComplete);
};
}
}, [socket, logLevel, t]);
if (socket.disconnected) socket.connect();
return () => {
socket.removeAllListeners();
socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (!state?.billids) {
history(`/manage/accounting/payables`);
@@ -124,20 +133,27 @@ export function DmsContainer({ setBreadcrumbs, setSelectedHeader }) {
<Button
onClick={() => {
setLogs([]);
if (socket) {
socket.emit("clear-dms-session");
}
socket.disconnect();
socket.connect();
}}
>
Clear Session
Reconnect
</Button>
</Space>
}
>
<DmsLogEvents logs={logs} />
<DmsLogEvents socket={socket} logs={logs} />
</Card>
</div>
</Col>
</Row>
);
}
export const determineDmsType = (bodyshop) => {
if (bodyshop.cdk_dealerid) return "cdk";
else {
return "pbs";
}
};

View File

@@ -1,11 +1,12 @@
import { useQuery } from "@apollo/client";
import { Button, Card, Col, notification, Result, Row, Select, Space } from "antd";
import queryString from "query-string";
import React, { useContext, useEffect, useRef, useState } from "react";
import React, { useEffect, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link, useLocation, useNavigate } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import SocketIO from "socket.io-client";
import AlertComponent from "../../components/alert/alert.component";
import DmsAllocationsSummary from "../../components/dms-allocations-summary/dms-allocations-summary.component";
import DmsCustomerSelector from "../../components/dms-customer-selector/dms-customer-selector.component";
@@ -13,12 +14,12 @@ import DmsLogEvents from "../../components/dms-log-events/dms-log-events.compone
import DmsPostForm from "../../components/dms-post-form/dms-post-form.component";
import LoadingSpinner from "../../components/loading-spinner/loading-spinner.component";
import { OwnerNameDisplayFunction } from "../../components/owner-name-display/owner-name-display.component";
import { auth } from "../../firebase/firebase.utils";
import { QUERY_JOB_EXPORT_DMS } from "../../graphql/jobs.queries";
import { insertAuditTrail, setBreadcrumbs, setSelectedHeader } from "../../redux/application/application.actions";
import { selectBodyshop } from "../../redux/user/user.selectors";
import InstanceRenderManager from "../../utils/instanceRenderMgr";
import AuditTrailMapping from "../../utils/AuditTrailMappings";
import SocketContext from "../../contexts/SocketIO/socketContext";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop
@@ -27,21 +28,25 @@ const mapStateToProps = createStructuredSelector({
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
setSelectedHeader: (key) => dispatch(setSelectedHeader(key)),
insertAuditTrail: ({ jobid, operation, type }) =>
dispatch(
insertAuditTrail({
jobid,
operation,
type
})
)
insertAuditTrail: ({ jobid, operation, type }) => dispatch(insertAuditTrail({ jobid, operation, type }))
});
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
export const socket = SocketIO(
import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "", // for dev testing,
{
path: "/ws",
withCredentials: true,
auth: async (callback) => {
const token = auth.currentUser && (await auth.currentUser.getIdToken());
callback({ token });
}
}
);
export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, insertAuditTrail }) {
const { t } = useTranslation();
const { socket } = useContext(SocketContext);
const [logLevel, setLogLevel] = useState("DEBUG");
const history = useNavigate();
const [logs, setLogs] = useState([]);
@@ -54,7 +59,6 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
fetchPolicy: "network-only",
nextFetchPolicy: "network-only"
});
const logsRef = useRef(null);
useEffect(() => {
@@ -79,73 +83,47 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
if (socket) {
const handleConnect = () => {
socket.emit("set-log-level", logLevel);
};
const handleReconnect = () => {
setLogs((logs) => [
socket.on("connect", () => socket.emit("set-log-level", logLevel));
socket.on("reconnect", () => {
setLogs((logs) => {
return [
...logs,
{
timestamp: new Date(),
level: "WARNING",
message: "Reconnected to DMS Export Service"
message: "Reconnected to CDK Export Service"
}
]);
};
];
});
});
socket.on("connect_error", (err) => {
console.log(`connect_error due to ${err}`, err);
notification.error({ message: err.message });
});
socket.on("log-event", (payload) => {
setLogs((logs) => {
return [...logs, payload];
});
});
socket.on("export-success", (payload) => {
notification.success({
message: t("jobs.successes.exported")
});
insertAuditTrail({
jobid: payload,
operation: AuditTrailMapping.jobexported(),
type: "jobexported"
});
history("/manage/accounting/receivables");
});
const handleConnectError = (err) => {
console.log(`connect_error due to ${err}`, err);
notification.error({ message: err.message });
};
const handleLogEvent = (payload) => {
setLogs((logs) => [...logs, payload]);
};
const handleExportSuccess = async (payload) => {
notification.success({
message: t("jobs.successes.exported")
});
insertAuditTrail({
jobid: payload,
operation: AuditTrailMapping.jobexported(),
type: "jobexported"
});
try {
await new Promise((resolve, reject) => {
socket.emit("clear-dms-session", (response) => {
if (response && response.status === "ok") {
resolve();
} else {
reject(new Error("Failed to clear DMS session"));
}
});
});
} catch (error) {
console.error("Failed to clear DMS session", error);
}
history("/manage/accounting/receivables");
};
socket.on("connect", handleConnect);
socket.on("reconnect", handleReconnect);
socket.on("connect_error", handleConnectError);
socket.on("log-event", handleLogEvent);
socket.on("export-success", handleExportSuccess);
return () => {
socket.off("connect", handleConnect);
socket.off("reconnect", handleReconnect);
socket.off("connect_error", handleConnectError);
socket.off("log-event", handleLogEvent);
socket.off("export-success", handleExportSuccess);
};
}
}, [socket, logLevel, t, insertAuditTrail, history]);
if (socket.disconnected) socket.connect();
return () => {
socket.removeAllListeners();
socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
if (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
@@ -205,17 +183,16 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
<Button
onClick={() => {
setLogs([]);
if (socket) {
socket.emit("clear-dms-session");
}
socket.disconnect();
socket.connect();
}}
>
Clear Session
Reconnect
</Button>
</Space>
}
>
<DmsLogEvents logs={logs} />
<DmsLogEvents socket={socket} logs={logs} />
</Card>
</div>
</Col>

View File

@@ -18,13 +18,13 @@ import LoadingSpinner from "../../components/loading-spinner/loading-spinner.com
import PartnerPingComponent from "../../components/partner-ping/partner-ping.component";
import PrintCenterModalContainer from "../../components/print-center-modal/print-center-modal.container";
import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-status.component";
import { requestForToken } from "../../firebase/firebase.utils";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
import { selectBodyshop, selectInstanceConflict } from "../../redux/user/user.selectors";
import UpdateAlert from "../../components/update-alert/update-alert.component";
import InstanceRenderManager from "../../utils/instanceRenderMgr.js";
import "./manage.page.styles.scss";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
import { requestForToken } from "../../firebase/firebase.utils.js";
const JobsPage = lazy(() => import("../jobs/jobs.page"));
@@ -132,27 +132,6 @@ export function Manage({ conflict, bodyshop }) {
});
}, [t]);
useEffect(() => {
if (socket && bodyshop && bodyshop.id) {
const handleConnect = () => {
socket.emit("join-bodyshop-room", bodyshop.id);
};
const handleBodyshopMessage = (message) => {
console.log(`Received message for bodyshop ${bodyshop.id}:`, message);
};
socket.on("connect", handleConnect);
socket.on("bodyshop-message", handleBodyshopMessage);
return () => {
socket.emit("leave-bodyshop-room", bodyshop.id);
socket.off("connect", handleConnect);
socket.off("bodyshop-message", handleBodyshopMessage);
};
}
}, [socket, bodyshop]);
const AppRouteTable = (
<Suspense
fallback={
@@ -625,7 +604,7 @@ export function Manage({ conflict, bodyshop }) {
}}
>
<div style={{ display: "flex" }}>
<div>
<div onClick={broadcastMessage}>
{`${InstanceRenderManager({
imex: t("titles.imexonline"),
rome: t("titles.romeonline"),
@@ -634,8 +613,6 @@ export function Manage({ conflict, bodyshop }) {
</div>
<div id="noticeable-widget" style={{ marginLeft: "1rem" }} />
</div>
<button onClick={broadcastMessage}>Broadcast Message</button>
<Link to="/disclaimer" target="_blank" style={{ color: "#ccc" }}>
Disclaimer & Notices
</Link>

View File

@@ -9,6 +9,7 @@ import eslint from "vite-plugin-eslint";
import { VitePWA } from "vite-plugin-pwa";
import InstanceRenderManager from "./src/utils/instanceRenderMgr";
import chalk from "chalk";
//import { visualizer } from "rollup-plugin-visualizer";
process.env.VITE_APP_GIT_SHA_DATE = new Date().toLocaleString("en-US", {
timeZone: "America/Los_Angeles"
@@ -46,6 +47,7 @@ export const logger = createLogger("info", {
export default defineConfig({
base: "/",
plugins: [
//visualizer(),
ViteEjsPlugin((viteConfig) => ({ env: viteConfig.env })),
VitePWA({
injectRegister: "auto",
@@ -124,6 +126,12 @@ export default defineConfig({
secure: false,
ws: true
},
"/wss": {
target: "ws://localhost:4000",
rewriteWsOrigin: true,
secure: false,
ws: true
},
"/api": {
target: "http://localhost:4000",
changeOrigin: true,
@@ -160,6 +168,12 @@ export default defineConfig({
secure: false,
ws: true
},
"/wss": {
target: "ws://localhost:4000",
rewriteWsOrigin: true,
secure: false,
ws: true
},
"/api": {
target: "http://localhost:4000",
changeOrigin: true,
@@ -174,7 +188,32 @@ export default defineConfig({
manualChunks: {
antd: ["antd"],
"react-redux": ["react-redux"],
redux: ["redux"]
redux: ["redux"],
lodash: ["lodash"],
"@sentry/react": ["@sentry/react"],
"@splitsoftware/splitio-react": ["@splitsoftware/splitio-react"],
logrocket: ["logrocket"],
"firebase/app": ["firebase/app"],
"firebase/firestore": ["firebase/firestore"],
"firebase/firestore/lite": ["firebase/firestore/lite"],
"firebase/auth": ["firebase/auth"],
"firebase/functions": ["firebase/functions"],
"firebase/storage": ["firebase/storage"],
"firebase/database": ["firebase/database"],
"firebase/remote-config": ["firebase/remote-config"],
"firebase/performance": ["firebase/performance"],
"@firebase/app": ["@firebase/app"],
"@firebase/firestore": ["@firebase/firestore"],
"@firebase/firestore/lite": ["@firebase/firestore/lite"],
"@firebase/auth": ["@firebase/auth"],
"@firebase/functions": ["@firebase/functions"],
"@firebase/storage": ["@firebase/storage"],
"@firebase/database": ["@firebase/database"],
"@firebase/remote-config": ["@firebase/remote-config"],
"@firebase/performance": ["@firebase/performance"],
markerjs2: ["markerjs2"],
"@apollo/client": ["@apollo/client"],
"libphonenumber-js": ["libphonenumber-js"]
}
}
}
@@ -184,6 +223,8 @@ export default defineConfig({
"react",
"react-dom",
"antd",
"lodash",
"@sentry/react",
"@apollo/client",
"@reduxjs/toolkit",
"axios",

129
package-lock.json generated
View File

@@ -13,6 +13,7 @@
"@aws-sdk/client-ses": "^3.654.0",
"@aws-sdk/credential-provider-node": "^3.654.0",
"@opensearch-project/opensearch": "^2.12.0",
"@socket.io/admin-ui": "^0.5.1",
"@socket.io/redis-adapter": "^8.3.0",
"aws4": "^1.13.2",
"axios": "^1.7.7",
@@ -1766,6 +1767,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@redis/bloom/-/bloom-1.2.0.tgz",
"integrity": "sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
@@ -1774,6 +1776,7 @@
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@redis/client/-/client-1.6.0.tgz",
"integrity": "sha512-aR0uffYI700OEEH4gYnitAnv3vzVGXCFvYfdpu/CJKvk4pHfLPEy/JSZyrpQ+15WhXe1yJRXLtfQ84s4mEXnPg==",
"license": "MIT",
"dependencies": {
"cluster-key-slot": "1.1.2",
"generic-pool": "3.9.0",
@@ -1787,6 +1790,7 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@redis/graph/-/graph-1.1.1.tgz",
"integrity": "sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
@@ -1795,6 +1799,7 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@redis/json/-/json-1.0.7.tgz",
"integrity": "sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
@@ -1803,6 +1808,7 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@redis/search/-/search-1.2.0.tgz",
"integrity": "sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
@@ -1811,6 +1817,7 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@redis/time-series/-/time-series-1.1.0.tgz",
"integrity": "sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==",
"license": "MIT",
"peerDependencies": {
"@redis/client": "^1.0.0"
}
@@ -2389,6 +2396,20 @@
"node": ">=16.0.0"
}
},
"node_modules/@socket.io/admin-ui": {
"version": "0.5.1",
"resolved": "https://registry.npmjs.org/@socket.io/admin-ui/-/admin-ui-0.5.1.tgz",
"integrity": "sha512-1dlGL2FGm6T+uL1e6iDvbo2eCINwvW7iVbjIblwh5kPPRM1SP8lmZrbFZf4QNJ/cqQ+JLcx49eXGM9WAB4TK7w==",
"license": "MIT",
"dependencies": {
"@types/bcryptjs": "^2.4.2",
"bcryptjs": "^2.4.3",
"debug": "~4.3.1"
},
"peerDependencies": {
"socket.io": ">=3.1.0"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
@@ -2398,6 +2419,7 @@
"version": "8.3.0",
"resolved": "https://registry.npmjs.org/@socket.io/redis-adapter/-/redis-adapter-8.3.0.tgz",
"integrity": "sha512-ly0cra+48hDmChxmIpnESKrc94LjRL80TEmZVscuQ/WWkRP81nNj8W8cCGMqbI4L6NCuAaPRSzZF1a9GlAxxnA==",
"license": "MIT",
"dependencies": {
"debug": "~4.3.1",
"notepack.io": "~3.0.1",
@@ -2441,6 +2463,12 @@
}
}
},
"node_modules/@types/bcryptjs": {
"version": "2.4.6",
"resolved": "https://registry.npmjs.org/@types/bcryptjs/-/bcryptjs-2.4.6.tgz",
"integrity": "sha512-9xlo6R2qDs5uixm0bcIqCeMCE6HiQsIyel9KQySStiyqNl2tnj2mP3DX1Nf56MD6KMenNNlBBsy3LJ7gUEQPXQ==",
"license": "MIT"
},
"node_modules/@types/body-parser": {
"version": "1.19.5",
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
@@ -2863,6 +2891,12 @@
"tweetnacl": "^0.14.3"
}
},
"node_modules/bcryptjs": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-2.4.3.tgz",
"integrity": "sha512-V/Hy/X9Vt7f3BbPJEi8BdVFMByHi+jNXrYkW3huaybV/kQ0KJg0Y6PkEMbn+zeT+i+SiKZ/HMqJGIIt4LZDqNQ==",
"license": "MIT"
},
"node_modules/better-queue": {
"version": "3.8.12",
"resolved": "https://registry.npmjs.org/better-queue/-/better-queue-3.8.12.tgz",
@@ -3157,6 +3191,7 @@
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/cluster-key-slot/-/cluster-key-slot-1.1.2.tgz",
"integrity": "sha512-RMr0FhtfXemyinomL4hrWcYJxmX6deFdCxpJzhDttxgO1+bcCnkk+9drydLVDmAMG7NE6aN/fl4F7ucU/90gAA==",
"license": "Apache-2.0",
"engines": {
"node": ">=0.10.0"
}
@@ -3574,11 +3609,12 @@
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
},
"node_modules/debug": {
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "2.1.2"
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
@@ -3589,11 +3625,6 @@
}
}
},
"node_modules/debug/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/decode-uri-component": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
@@ -3858,9 +3889,10 @@
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
@@ -4095,15 +4127,6 @@
"ms": "2.0.0"
}
},
"node_modules/express/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/express/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -4259,15 +4282,6 @@
"ms": "2.0.0"
}
},
"node_modules/finalhandler/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/finalhandler/node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@@ -4555,6 +4569,7 @@
"version": "3.9.0",
"resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-3.9.0.tgz",
"integrity": "sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==",
"license": "MIT",
"engines": {
"node": ">= 4"
}
@@ -5752,7 +5767,8 @@
"node_modules/notepack.io": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/notepack.io/-/notepack.io-3.0.1.tgz",
"integrity": "sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg=="
"integrity": "sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg==",
"license": "MIT"
},
"node_modules/npmlog": {
"version": "5.0.1",
@@ -5795,9 +5811,13 @@
}
},
"node_modules/object-inspect": {
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"version": "1.13.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
"integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
"license": "MIT",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -6251,6 +6271,10 @@
"version": "4.7.0",
"resolved": "https://registry.npmjs.org/redis/-/redis-4.7.0.tgz",
"integrity": "sha512-zvmkHEAdGMn+hMRXuMBtu4Vo5P6rHQjLoHftu+lBqq8ZTA3RCVC/WzD790bkKKiNFp7d5/9PcSD19fJyyRvOdQ==",
"license": "MIT",
"workspaces": [
"./packages/*"
],
"dependencies": {
"@redis/bloom": "1.2.0",
"@redis/client": "1.6.0",
@@ -6436,6 +6460,15 @@
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
"license": "MIT"
},
"node_modules/send/node_modules/encodeurl": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/send/node_modules/mime": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
@@ -6463,15 +6496,6 @@
"node": ">= 0.8.0"
}
},
"node_modules/serve-static/node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
@@ -6631,23 +6655,6 @@
"node": ">=14.0.0"
}
},
"node_modules/soap/node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/socket.io": {
"version": "4.8.0",
"resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.0.tgz",
@@ -6670,6 +6677,7 @@
"version": "2.5.5",
"resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz",
"integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==",
"license": "MIT",
"dependencies": {
"debug": "~4.3.4",
"ws": "~8.17.1"
@@ -7423,6 +7431,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/uid2/-/uid2-1.0.0.tgz",
"integrity": "sha512-+I6aJUv63YAcY9n4mQreLUt0d4lvwkkopDNmpomkAUz0fAkEMV9pRWxN0EjhW1YfRhcuyHg2v3mwddCDW1+LFQ==",
"license": "MIT",
"engines": {
"node": ">= 4.0.0"
}

View File

@@ -23,6 +23,7 @@
"@aws-sdk/client-ses": "^3.654.0",
"@aws-sdk/credential-provider-node": "^3.654.0",
"@opensearch-project/opensearch": "^2.12.0",
"@socket.io/admin-ui": "^0.5.1",
"@socket.io/redis-adapter": "^8.3.0",
"aws4": "^1.13.2",
"axios": "^1.7.7",

225
server.js
View File

@@ -9,6 +9,11 @@ const { Server } = require("socket.io");
const { createClient } = require("redis");
const { createAdapter } = require("@socket.io/redis-adapter");
const logger = require("./server/utils/logger");
const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents");
const { instrument, RedisStore } = require("@socket.io/admin-ui");
const { isString, isEmpty } = require("lodash");
const applyRedisHelpers = require("./server/utils/redisHelpers");
// Load environment variables
require("dotenv").config({
@@ -23,12 +28,13 @@ const SOCKETIO_CORS_ORIGIN = [
"https://test.imex.online",
"https://www.test.imex.online",
"http://localhost:3000",
"https://localhost:3000",
"https://imex.online",
"https://www.imex.online",
"https://romeonline.io",
"https://www.romeonline.io",
"https://beta.test.romeonline.io",
"https://www.beta.test.romeonline.io",
"https://test.romeonline.io",
"https://www.test.romeonline.io",
"https://beta.romeonline.io",
"https://www.beta.romeonline.io",
"https://beta.test.imex.online",
@@ -40,7 +46,11 @@ const SOCKETIO_CORS_ORIGIN = [
"https://www.promanager.web-est.com",
"https://www.promanager.web-est.com",
"https://old.imex.online",
"https://www.old.imex.online"
"https://www.old.imex.online",
"https://wsadmin.imex.online",
"https://www.wsadmin.imex.online",
"http://localhost:3333",
"https://localhost:3333"
];
/**
@@ -52,8 +62,13 @@ const applyMiddleware = (app) => {
app.use(cookieParser());
app.use(bodyParser.json({ limit: "50mb" }));
app.use(bodyParser.urlencoded({ limit: "50mb", extended: true }));
app.use(cors({ credentials: true, exposedHeaders: ["set-cookie"] }));
app.use(
cors({
origin: SOCKETIO_CORS_ORIGIN,
credentials: true,
exposedHeaders: ["set-cookie"]
})
);
// Helper middleware
app.use((req, res, next) => {
req.logger = logger;
@@ -117,8 +132,8 @@ const applySocketIO = async (server, app) => {
process.exit(0);
});
const io = new Server(server, {
path: "/ws",
const ioRedis = new Server(server, {
path: "/wss",
adapter: createAdapter(pubClient, subClient),
cors: {
origin: SOCKETIO_CORS_ORIGIN,
@@ -128,173 +143,41 @@ const applySocketIO = async (server, app) => {
}
});
app.use((req, res, next) => {
req.pubClient = pubClient;
req.io = io;
next();
if (isString(process.env.REDIS_ADMIN_PASS) && !isEmpty(process.env.REDIS_ADMIN_PASS)) {
logger.log(`[${process.env.NODE_ENV}] Initializing Redis Admin UI....`, "INFO", "redis", "api");
instrument(ioRedis, {
auth: {
type: "basic",
username: "admin",
password: process.env.REDIS_ADMIN_PASS
},
mode: process.env.REDIS_ADMIN_MODE || "development"
});
}
const io = new Server(server, {
path: "/ws",
cors: {
origin: SOCKETIO_CORS_ORIGIN,
methods: ["GET", "POST"],
credentials: true,
exposedHeaders: ["set-cookie"]
}
});
Object.assign(module.exports, { io, pubClient });
return { pubClient, io };
};
/**
* Apply Redis helper functions
* @param pubClient
* @param app
*/
const applyRedisHelpers = (pubClient, app) => {
// Store session data in Redis
const setSessionData = async (socketId, key, value) => {
await pubClient.hSet(`socket:${socketId}`, key, JSON.stringify(value)); // Use Redis pubClient
};
// Retrieve session data from Redis
const getSessionData = async (socketId, key) => {
const data = await pubClient.hGet(`socket:${socketId}`, key);
return data ? JSON.parse(data) : null;
};
// Clear session data from Redis
const clearSessionData = async (socketId) => {
await pubClient.del(`socket:${socketId}`);
};
// Store multiple session data in Redis
const setMultipleSessionData = async (socketId, keyValues) => {
// keyValues is expected to be an object { key1: value1, key2: value2, ... }
const entries = Object.entries(keyValues).map(([key, value]) => [key, JSON.stringify(value)]);
await pubClient.hSet(`socket:${socketId}`, ...entries.flat());
};
// Retrieve multiple session data from Redis
const getMultipleSessionData = async (socketId, keys) => {
const data = await pubClient.hmGet(`socket:${socketId}`, keys);
// Redis returns an object with null values for missing keys, so we parse the non-null ones
return Object.fromEntries(keys.map((key, index) => [key, data[index] ? JSON.parse(data[index]) : null]));
};
const setMultipleFromArraySessionData = async (socketId, keyValueArray) => {
// Use Redis multi/pipeline to batch the commands
const multi = pubClient.multi();
keyValueArray.forEach(([key, value]) => {
multi.hSet(`socket:${socketId}`, key, JSON.stringify(value));
app.use((req, res, next) => {
Object.assign(req, {
pubClient,
io,
ioRedis
});
await multi.exec(); // Execute all queued commands
};
// Helper function to add an item to the end of the Redis list
const addItemToEndOfList = async (socketId, key, newItem) => {
try {
await pubClient.rPush(`socket:${socketId}:${key}`, JSON.stringify(newItem));
} catch (error) {
console.error(`Error adding item to the end of the list for socket ${socketId}:`, error);
}
};
// Helper function to add an item to the beginning of the Redis list
const addItemToBeginningOfList = async (socketId, key, newItem) => {
try {
await pubClient.lPush(`socket:${socketId}:${key}`, JSON.stringify(newItem));
} catch (error) {
console.error(`Error adding item to the beginning of the list for socket ${socketId}:`, error);
}
};
// Helper function to clear a list in Redis
const clearList = async (socketId, key) => {
try {
await pubClient.del(`socket:${socketId}:${key}`);
} catch (error) {
console.error(`Error clearing list for socket ${socketId}:`, error);
}
};
const api = {
setSessionData,
getSessionData,
clearSessionData,
setMultipleSessionData,
getMultipleSessionData,
setMultipleFromArraySessionData,
addItemToEndOfList,
addItemToBeginningOfList,
clearList
};
Object.assign(module.exports, api);
app.use((req, res, next) => {
req.sessionUtils = api;
next();
});
// // Demo to show how all the helper functions work
// const demoSessionData = async () => {
// const socketId = "testSocketId";
//
// // Store session data using setSessionData
// await exports.setSessionData(socketId, "field1", "Hello, Redis!");
//
// // Retrieve session data using getSessionData
// const field1Value = await exports.getSessionData(socketId, "field1");
// console.log("Retrieved single field value:", field1Value);
//
// // Store multiple session data using setMultipleSessionData
// await exports.setMultipleSessionData(socketId, { field2: "Second Value", field3: "Third Value" });
//
// // Retrieve multiple session data using getMultipleSessionData
// const multipleFields = await exports.getMultipleSessionData(socketId, ["field2", "field3"]);
// console.log("Retrieved multiple field values:", multipleFields);
//
// // Store multiple session data using setMultipleFromArraySessionData
// await exports.setMultipleFromArraySessionData(socketId, [
// ["field4", "Fourth Value"],
// ["field5", "Fifth Value"]
// ]);
//
// // Retrieve and log all fields
// const allFields = await exports.getMultipleSessionData(socketId, [
// "field1",
// "field2",
// "field3",
// "field4",
// "field5"
// ]);
// console.log("Retrieved all field values:", allFields);
//
// // Add item to the end of a Redis list
// await exports.addItemToEndOfList(socketId, "logEvents", { event: "Log Event 1", timestamp: new Date() });
// await exports.addItemToEndOfList(socketId, "logEvents", { event: "Log Event 2", timestamp: new Date() });
//
// // Add item to the beginning of a Redis list
// await exports.addItemToBeginningOfList(socketId, "logEvents", { event: "First Log Event", timestamp: new Date() });
//
// // Retrieve the entire list (using lRange)
// const logEvents = await pubClient.lRange(`socket:${socketId}:logEvents`, 0, -1);
// console.log("Log Events List:", logEvents.map(JSON.parse));
//
// // **Add the new code below to test clearList**
//
// // Clear the list using clearList
// await exports.clearList(socketId, "logEvents");
// console.log("Log Events List cleared.");
//
// // Retrieve the list after clearing to confirm it's empty
// const logEventsAfterClear = await pubClient.lRange(`socket:${socketId}:logEvents`, 0, -1);
// console.log("Log Events List after clearing:", logEventsAfterClear); // Should be an empty array
//
// // Clear session data
// await exports.clearSessionData(socketId);
// console.log("Session data cleared.");
// };
//
// if (process.env.NODE_ENV === "development") {
// demoSessionData();
// }
Object.assign(module.exports, { io, pubClient, ioRedis });
return { pubClient, io, ioRedis };
};
/**
@@ -307,11 +190,15 @@ const main = async () => {
const server = http.createServer(app);
const { pubClient } = await applySocketIO(server, app);
applyRedisHelpers(pubClient, app);
const { pubClient, ioRedis } = await applySocketIO(server, app);
const api = applyRedisHelpers(pubClient, app);
// Legacy Socket Events
require("./server/web-sockets/web-socket");
applyMiddleware(app);
applyRoutes(app);
redisSocketEvents(ioRedis, api);
try {
await server.listen(port);

View File

@@ -12,92 +12,67 @@ const AxiosLib = require("axios").default;
const axios = AxiosLib.create();
const { PBS_ENDPOINTS, PBS_CREDENTIALS } = require("./pbs-constants");
const { CheckForErrors } = require("./pbs-job-export");
const { getSessionData, getMultipleSessionData, setMultipleSessionData } = require("../../../server");
const uuid = require("uuid").v4;
axios.interceptors.request.use((x) => {
const socket = x.socket;
axios.interceptors.request.use(
async (x) => {
const socket = x.socket;
const headers = {
...x.headers.common,
...x.headers[x.method],
...x.headers
};
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
console.log(printable);
const headers = {
...x.headers.common,
...x.headers[x.method],
...x.headers
};
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
return x;
});
console.log(printable);
axios.interceptors.response.use((x) => {
const socket = x.config.socket;
// Use await properly here for the async operation
await CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
console.log(printable);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
return x; // Return the modified request
},
(error) => {
return Promise.reject(error); // Proper error handling
}
);
axios.interceptors.response.use(
async (x) => {
const socket = x.config.socket;
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
console.log(printable);
// Use await properly here for the async operation
await CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
return x; // Return the modified response
},
(error) => {
return Promise.reject(error); // Proper error handling
}
);
return x;
});
async function PbsCalculateAllocationsAp(socket, billids) {
try {
await CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`);
CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`);
const { bills, bodyshops } = await QueryBillData(socket, billids);
const bodyshop = bodyshops[0];
await setMultipleSessionData(socket.id, {
bills,
bodyshop
});
const txEnvelope = await getSessionData(socket.id, "txEnvelope");
socket.bodyshop = bodyshop;
socket.bills = bills;
//Each bill will enter it's own top level transaction.
const transactionlist = [];
if (bills.length === 0) {
await CdkBase.createLogEvent(
CdkBase.createLogEvent(
socket,
"ERROR",
`No bills found for export. Ensure they have not already been exported and try again.`
);
}
bills.forEach((bill) => {
//Keep the allocations at the bill level.
const transactionObject = {
SerialNumber: bodyshop.pbs_serialnumber,
SerialNumber: socket.bodyshop.pbs_serialnumber,
billid: bill.id,
Posting: {
Reference: bill.invoice_number,
JournalCode: socket.txEnvelope ? socket.txEnvelope.journal : null,
TransactionDate: moment().tz(socket.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z",
//Description: "Bulk AP posting.",
//AdditionalInfo: "String",
Reference: bill.invoice_number,
JournalCode: txEnvelope?.journal,
TransactionDate: moment().tz(bodyshop.timezone).toISOString(),
Source: "ImEX Online", // TODO: Resolve this for Rome Online.
Lines: [] // Will be populated with allocation data,
Source: "ImEX Online", //TODO:AIO Resolve this for rome online.
Lines: [] //socket.apAllocations,
}
};
@@ -142,13 +117,13 @@ async function PbsCalculateAllocationsAp(socket, billids) {
};
}
// Add the line amount.
//Add the line amount.
billHash[cc.name] = {
...billHash[cc.name],
Amount: billHash[cc.name].Amount.add(lineDinero)
};
// Does the line have taxes?
//Does the line have taxes?
if (bl.applicable_taxes.federal) {
billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name] = {
...billHash[bodyshop.md_responsibility_centers.taxes.federal_itc.name],
@@ -170,7 +145,7 @@ async function PbsCalculateAllocationsAp(socket, billids) {
let APAmount = Dinero();
Object.keys(billHash).map((key) => {
if (billHash[key].Amount.getAmount() !== 0) {
if (billHash[key].Amount.getAmount() > 0 || billHash[key].Amount.getAmount() < 0) {
transactionObject.Posting.Lines.push({
...billHash[key],
Amount: billHash[key].Amount.toFormat("0.00")
@@ -194,19 +169,19 @@ async function PbsCalculateAllocationsAp(socket, billids) {
return transactionlist;
} catch (error) {
await CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
}
}
exports.PbsCalculateAllocationsAp = PbsCalculateAllocationsAp;
async function QueryBillData(socket, billids) {
await CdkBase.createLogEvent(socket, "DEBUG", `Querying bill data for id(s) ${billids}`);
CdkBase.createLogEvent(socket, "DEBUG", `Querying bill data for id(s) ${billids}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.GET_PBS_AP_ALLOCATIONS, { billids: billids });
await CdkBase.createLogEvent(socket, "TRACE", `Bill data query result ${JSON.stringify(result, null, 2)}`);
CdkBase.createLogEvent(socket, "TRACE", `Bill data query result ${JSON.stringify(result, null, 2)}`);
return result;
}
@@ -221,49 +196,40 @@ function getCostAccount(billline, respcenters) {
}
exports.PbsExportAp = async function (socket, { billids, txEnvelope }) {
await CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`);
CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`);
const apAllocations = await PbsCalculateAllocationsAp(socket, billids);
await setMultipleSessionData(socket.id, {
apAllocations,
txEnvelope
});
for (const allocation of apAllocations) {
//apAllocations has the same shap as the lines key for the accounting posting to PBS.
socket.apAllocations = await PbsCalculateAllocationsAp(socket, billids);
socket.txEnvelope = txEnvelope;
for (const allocation of socket.apAllocations) {
const { billid, ...restAllocation } = allocation;
const { data: AccountPostingChange } = await axios.post(PBS_ENDPOINTS.AccountingPostingChange, restAllocation, {
auth: PBS_CREDENTIALS,
socket
});
CheckForErrors(socket, AccountPostingChange).catch((err) =>
console.error(`Error running CheckingForErrors in pbs-ap-allocations`)
);
CheckForErrors(socket, AccountPostingChange);
if (AccountPostingChange.WasSuccessful) {
await CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
await MarkApExported(socket, [billid]);
socket.emit("ap-export-success", billid);
} else {
await CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`);
CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`);
socket.emit("ap-export-failure", {
billid,
error: AccountPostingChange.Message
});
}
}
socket.emit("ap-export-complete");
};
async function MarkApExported(socket, billids) {
const { bills, bodyshop } = await getMultipleSessionData(socket.id, ["bills", "bodyshop"]);
await CdkBase.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`);
CdkBase.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
return await client
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.MARK_BILLS_EXPORTED, {
billids,
@@ -271,11 +237,13 @@ async function MarkApExported(socket, billids) {
exported: true,
exported_at: new Date()
},
logs: bills.map((bill) => ({
bodyshopid: bodyshop.id,
logs: socket.bills.map((bill) => ({
bodyshopid: socket.bodyshop.id,
billid: bill.id,
successful: true,
useremail: socket.user.email
}))
});
return result;
}

View File

@@ -12,169 +12,136 @@ const CalculateAllocations = require("../../cdk/cdk-calculate-allocations").defa
const CdkBase = require("../../web-sockets/web-socket");
const moment = require("moment-timezone");
const Dinero = require("dinero.js");
const { setSessionData, getSessionData, getMultipleSessionData, setMultipleSessionData } = require("../../../server");
const InstanceManager = require("../../utils/instanceMgr").default;
const axios = AxiosLib.create();
axios.interceptors.request.use(
async (x) => {
const socket = x.socket;
axios.interceptors.request.use((x) => {
const socket = x.socket;
const headers = {
...x.headers.common,
...x.headers[x.method],
...x.headers
};
const headers = {
...x.headers.common,
...x.headers[x.method],
...x.headers
};
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
console.log(printable);
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
console.log(printable);
return x;
});
await CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
axios.interceptors.response.use((x) => {
const socket = x.config.socket;
return x; // Make sure to return the request object
},
(error) => {
return Promise.reject(error);
}
);
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
console.log(printable);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
axios.interceptors.response.use(
async (x) => {
const socket = x.config.socket;
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
console.log(printable);
await CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
return x; // Make sure to return the response object
},
(error) => {
return Promise.reject(error);
}
);
return x;
});
exports.default = async function (socket, { txEnvelope, jobid }) {
socket.logEvents = [];
socket.recordid = jobid;
socket.txEnvelope = txEnvelope;
try {
await setMultipleSessionData(socket.id, {
recordid: jobid,
txEnvelope
});
await CdkBase.createLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`);
CdkBase.createLogEvent(socket, "DEBUG", `Received Job export request for id ${jobid}`);
const JobData = await QueryJobData(socket, jobid);
await setSessionData(socket.id, "JobData", JobData);
await CdkBase.createLogEvent(socket, "DEBUG", `Querying the DMS for the Vehicle Record.`);
// Query for the Vehicle record to get the associated customer
const DmsVeh = await QueryVehicleFromDms(socket);
await setSessionData(socket.id, "DmsVeh", DmsVeh);
let DMSVehCustomer;
socket.JobData = JobData;
CdkBase.createLogEvent(socket, "DEBUG", `Querying the DMS for the Vehicle Record.`);
//Query for the Vehicle record to get the associated customer.
socket.DmsVeh = await QueryVehicleFromDms(socket);
//Todo: Need to validate the lines and methods below.
if (DmsVeh?.CustomerRef) {
// Get the associated customer from the Vehicle Record
DMSVehCustomer = await QueryCustomerBycodeFromDms(socket, DmsVeh.CustomerRef);
await setSessionData(socket.id, "DMSVehCustomer", DMSVehCustomer);
if (socket.DmsVeh && socket.DmsVeh.CustomerRef) {
//Get the associated customer from the Vehicle Record.
socket.DMSVehCustomer = await QueryCustomerBycodeFromDms(socket, socket.DmsVeh.CustomerRef);
}
const DMSCustList = await QueryCustomersFromDms(socket);
await setSessionData(socket.id, "DMSCustList", DMSCustList);
socket.DMSCustList = await QueryCustomersFromDms(socket);
socket.emit("pbs-select-customer", [
...(DMSVehCustomer ? [{ ...DMSVehCustomer, vinOwner: true }] : []),
...DMSCustList
...(socket.DMSVehCustomer ? [{ ...socket.DMSVehCustomer, vinOwner: true }] : []),
...socket.DMSCustList
]);
} catch (error) {
await CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsJobExport. ${error}`);
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsJobExport. ${error}`);
}
};
exports.PbsSelectedCustomer = async function PbsSelectedCustomer(socket, selectedCustomerId) {
try {
const JobData = await getSessionData(socket.id, "JobData");
if (socket.JobData.bodyshop.pbs_configuration.disablecontactvehicle === false) {
CdkBase.createLogEvent(socket, "DEBUG", `User selected customer ${selectedCustomerId || "NEW"}`);
if (JobData.bodyshop.pbs_configuration.disablecontactvehicle === false) {
await CdkBase.createLogEvent(socket, "DEBUG", `User selected customer ${selectedCustomerId || "NEW"}`);
// Upsert the contact information as per Wafaa's Email
await CdkBase.createLogEvent(
//Upsert the contact information as per Wafaa's Email.
CdkBase.createLogEvent(
socket,
"DEBUG",
`Upserting contact information to DMS for ${
JobData.ownr_fn || ""
} ${JobData.ownr_ln || ""} ${JobData.ownr_co_nm || ""}`
socket.JobData.ownr_fn || ""
} ${socket.JobData.ownr_ln || ""} ${socket.JobData.ownr_co_nm || ""}`
);
const ownerRef = await UpsertContactData(socket, selectedCustomerId);
await CdkBase.createLogEvent(socket, "DEBUG", `Upserting vehicle information to DMS for ${JobData.v_vin}`);
CdkBase.createLogEvent(socket, "DEBUG", `Upserting vehicle information to DMS for ${socket.JobData.v_vin}`);
await UpsertVehicleData(socket, ownerRef.ReferenceId);
} else {
await CdkBase.createLogEvent(
CdkBase.createLogEvent(
socket,
"DEBUG",
`Contact and Vehicle updates disabled. Skipping to accounting data insert.`
);
}
await CdkBase.createLogEvent(socket, "DEBUG", `Inserting account data.`);
await CdkBase.createLogEvent(socket, "DEBUG", `Inserting accounting posting data..`);
CdkBase.createLogEvent(socket, "DEBUG", `Inserting account data.`);
CdkBase.createLogEvent(socket, "DEBUG", `Inserting accounting posting data..`);
const insertResponse = await InsertAccountPostingData(socket);
// TODO: Insert Clear session
if (insertResponse.WasSuccessful) {
await CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`);
await MarkJobExported(socket, JobData.id);
CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported.`);
await MarkJobExported(socket, socket.JobData.id);
socket.emit("export-success", JobData.id);
socket.emit("export-success", socket.JobData.id);
} else {
await CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`);
CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`);
}
} catch (error) {
await CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsSelectedCustomer. ${error}`);
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkSelectedCustomer. ${error}`);
await InsertFailedExportLog(socket, error);
}
};
async function CheckForErrors(socket, response) {
if (response.WasSuccessful === undefined || response.WasSuccessful === true) {
await CdkBase.createLogEvent(socket, "DEBUG", `Successful response from DMS. ${response.Message || ""}`);
CdkBase.createLogEvent(socket, "DEBUG", `Successful response from DMS. ${response.Message || ""}`);
} else {
await CdkBase.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`);
await CdkBase.createLogEvent(socket, "TRACE", `Error received from DMS: ${JSON.stringify(response)}`);
CdkBase.createLogEvent(socket, "ERROR", `Error received from DMS: ${response.Message}`);
CdkBase.createLogEvent(socket, "TRACE", `Error received from DMS: ${JSON.stringify(response)}`);
}
}
exports.CheckForErrors = CheckForErrors;
async function QueryJobData(socket, jobid) {
await CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`);
CdkBase.createLogEvent(socket, "DEBUG", `Querying job data for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.QUERY_JOBS_FOR_PBS_EXPORT, { id: jobid });
await CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
return result.jobs_by_pk;
}
async function QueryVehicleFromDms(socket) {
try {
const JobData = await getSessionData(socket.id, "JobData");
if (!JobData.v_vin) return null;
if (!socket.JobData.v_vin) return null;
const { data: VehicleGetResponse, request } = await axios.post(
PBS_ENDPOINTS.VehicleGet,
{
SerialNumber: JobData.bodyshop.pbs_serialnumber,
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
// VehicleId: "00000000000000000000000000000000",
// Year: "String",
// Make: "String",
@@ -182,7 +149,7 @@ async function QueryVehicleFromDms(socket) {
// Trim: "String",
// ModelNumber: "String",
// StockNumber: "String",
VIN: JobData.v_vin
VIN: socket.JobData.v_vin
// LicenseNumber: "String",
// Lot: "String",
// Status: "String",
@@ -199,31 +166,26 @@ async function QueryVehicleFromDms(socket) {
{ auth: PBS_CREDENTIALS, socket }
);
await CheckForErrors(socket, VehicleGetResponse);
CheckForErrors(socket, VehicleGetResponse);
return VehicleGetResponse;
} catch (error) {
await CdkBase.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`);
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryVehicleFromDms - ${error}`);
throw new Error(error);
}
}
async function QueryCustomersFromDms(socket) {
try {
// Retrieve JobData from session storage
const JobData = await getSessionData(socket.id, "JobData");
// Make an API call to PBS to query customer details
const { data: CustomerGetResponse } = await axios.post(
PBS_ENDPOINTS.ContactGet,
{
SerialNumber: JobData.bodyshop.pbs_serialnumber,
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
//ContactId: "00000000000000000000000000000000",
// ContactCode: JobData.owner.accountingid,
FirstName: JobData.ownr_fn,
LastName: JobData.ownr_co_nm ? JobData.ownr_co_nm : JobData.ownr_ln,
PhoneNumber: JobData.ownr_ph1,
EmailAddress: JobData.ownr_ea
// ContactCode: socket.JobData.owner.accountingid,
FirstName: socket.JobData.ownr_fn,
LastName: socket.JobData.ownr_co_nm ? socket.JobData.ownr_co_nm : socket.JobData.ownr_ln,
PhoneNumber: socket.JobData.ownr_ph1,
EmailAddress: socket.JobData.ownr_ea
// ModifiedSince: "0001-01-01T00:00:00.0000000Z",
// ModifiedUntil: "0001-01-01T00:00:00.0000000Z",
// ContactIdList: ["00000000000000000000000000000000"],
@@ -235,36 +197,27 @@ async function QueryCustomersFromDms(socket) {
},
{ auth: PBS_CREDENTIALS, socket }
);
// Check for errors in the PBS response
await CheckForErrors(socket, CustomerGetResponse);
// Return the list of contacts from the PBS response
CheckForErrors(socket, CustomerGetResponse);
return CustomerGetResponse && CustomerGetResponse.Contacts;
} catch (error) {
// Log any errors encountered during the API call
await CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
throw new Error(error);
}
}
async function QueryCustomerBycodeFromDms(socket, CustomerRef) {
try {
// Retrieve JobData from session storage
const JobData = await getSessionData(socket.id, "JobData");
// Make an API call to PBS to query customer by ContactId
const { data: CustomerGetResponse } = await axios.post(
PBS_ENDPOINTS.ContactGet,
{
SerialNumber: JobData.bodyshop.pbs_serialnumber,
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
ContactId: CustomerRef
//ContactCode: JobData.owner.accountingid,
//FirstName: JobData.ownr_co_nm
// ? JobData.ownr_co_nm
// : JobData.ownr_fn,
//LastName: JobData.ownr_ln,
//PhoneNumber: JobData.ownr_ph1,
//ContactCode: socket.JobData.owner.accountingid,
//FirstName: socket.JobData.ownr_co_nm
// ? socket.JobData.ownr_co_nm
// : socket.JobData.ownr_fn,
//LastName: socket.JobData.ownr_ln,
//PhoneNumber: socket.JobData.ownr_ph1,
// EmailAddress: "String",
// ModifiedSince: "0001-01-01T00:00:00.0000000Z",
// ModifiedUntil: "0001-01-01T00:00:00.0000000Z",
@@ -277,42 +230,33 @@ async function QueryCustomerBycodeFromDms(socket, CustomerRef) {
},
{ auth: PBS_CREDENTIALS, socket }
);
// Check for errors in the PBS response
await CheckForErrors(socket, CustomerGetResponse);
// Return the list of contacts from the PBS response
CheckForErrors(socket, CustomerGetResponse);
return CustomerGetResponse && CustomerGetResponse.Contacts;
} catch (error) {
// Log any errors encountered during the API call
await CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomerBycodeFromDms - ${error}`);
CdkBase.createLogEvent(socket, "ERROR", `Error in QueryCustomersFromDms - ${error}`);
throw new Error(error);
}
}
async function UpsertContactData(socket, selectedCustomerId) {
try {
// Retrieve JobData from session storage
const JobData = await getSessionData(socket.id, "JobData");
// Make an API call to PBS to upsert contact data
const { data: ContactChangeResponse } = await axios.post(
PBS_ENDPOINTS.ContactChange,
{
ContactInfo: {
// Id: JobData.owner.id,
// Id: socket.JobData.owner.id,
...(selectedCustomerId ? { ContactId: selectedCustomerId } : {}),
SerialNumber: JobData.bodyshop.pbs_serialnumber,
Code: JobData.owner.accountingid,
...(JobData.ownr_co_nm
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
Code: socket.JobData.owner.accountingid,
...(socket.JobData.ownr_co_nm
? {
//LastName: JobData.ownr_ln,
FirstName: JobData.ownr_co_nm,
//LastName: socket.JobData.ownr_ln,
FirstName: socket.JobData.ownr_co_nm,
IsBusiness: true
}
: {
LastName: JobData.ownr_ln,
FirstName: JobData.ownr_fn,
LastName: socket.JobData.ownr_ln,
FirstName: socket.JobData.ownr_fn,
IsBusiness: false
}),
@@ -322,20 +266,20 @@ async function UpsertContactData(socket, selectedCustomerId) {
IsInactive: false,
//ApartmentNumber: "String",
Address: JobData.ownr_addr1,
City: JobData.ownr_city,
//County: JobData.ownr_addr1,
State: JobData.ownr_st,
ZipCode: JobData.ownr_zip,
Address: socket.JobData.ownr_addr1,
City: socket.JobData.ownr_city,
//County: socket.JobData.ownr_addr1,
State: socket.JobData.ownr_st,
ZipCode: socket.JobData.ownr_zip,
//BusinessPhone: "String",
//BusinessPhoneExt: "String",
HomePhone: JobData.ownr_ph2,
CellPhone: JobData.ownr_ph1,
HomePhone: socket.JobData.ownr_ph2,
CellPhone: socket.JobData.ownr_ph1,
//BusinessPhoneRawReverse: "String",
//HomePhoneRawReverse: "String",
//CellPhoneRawReverse: "String",
//FaxNumber: "String",
EmailAddress: JobData.ownr_ea
EmailAddress: socket.JobData.ownr_ea
//Notes: "String",
//CriticalMemo: "String",
//BirthDate: "0001-01-01T00:00:00.0000000Z",
@@ -368,43 +312,39 @@ async function UpsertContactData(socket, selectedCustomerId) {
},
{ auth: PBS_CREDENTIALS, socket }
);
await CheckForErrors(socket, ContactChangeResponse);
CheckForErrors(socket, ContactChangeResponse);
return ContactChangeResponse;
} catch (error) {
await CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`);
CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertContactData - ${error}`);
throw new Error(error);
}
}
async function UpsertVehicleData(socket, ownerRef) {
try {
const JobData = await getSessionData(socket.id, "JobData");
const { data: VehicleChangeResponse } = await axios.post(
PBS_ENDPOINTS.VehicleChange,
{
VehicleInfo: {
//Id: "string/00000000-0000-0000-0000-000000000000",
//VehicleId: "00000000000000000000000000000000",
SerialNumber: JobData.bodyshop.pbs_serialnumber,
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
//StockNumber: "String",
VIN: JobData.v_vin,
LicenseNumber: JobData.plate_no,
VIN: socket.JobData.v_vin,
LicenseNumber: socket.JobData.plate_no,
//FleetNumber: "String",
//Status: "String",
OwnerRef: ownerRef, // "00000000000000000000000000000000",
ModelNumber: JobData.vehicle && JobData.vehicle.v_makecode,
Make: JobData.v_make_desc,
Model: JobData.v_model_desc,
Trim: JobData.vehicle && JobData.vehicle.v_trimcode,
ModelNumber: socket.JobData.vehicle && socket.JobData.vehicle.v_makecode,
Make: socket.JobData.v_make_desc,
Model: socket.JobData.v_model_desc,
Trim: socket.JobData.vehicle && socket.JobData.vehicle.v_trimcode,
//VehicleType: "String",
Year: JobData.v_model_yr,
Odometer: JobData.kmout,
Year: socket.JobData.v_model_yr,
Odometer: socket.JobData.kmout,
ExteriorColor: {
Code: JobData.v_color,
Description: JobData.v_color
Code: socket.JobData.v_color,
Description: socket.JobData.v_color
}
// InteriorColor: { Code: "String", Description: "String" },
//Engine: "String",
@@ -525,112 +465,100 @@ async function UpsertVehicleData(socket, ownerRef) {
},
{ auth: PBS_CREDENTIALS, socket }
);
await CheckForErrors(socket, VehicleChangeResponse);
CheckForErrors(socket, VehicleChangeResponse);
return VehicleChangeResponse;
} catch (error) {
await CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
CdkBase.createLogEvent(socket, "ERROR", `Error in UpsertVehicleData - ${error}`);
throw new Error(error);
}
}
async function InsertAccountPostingData(socket) {
try {
const { JobData, txEnvelope } = await getMultipleSessionData(socket.id, ["JobData", "txEnvelope"]);
const allocations = await CalculateAllocations(socket, JobData.id);
const allocations = await CalculateAllocations(socket, socket.JobData.id);
const wips = [];
allocations.forEach((alloc) => {
// Add the sale item from each allocation if the amount is greater than 0 and not a tax
//Add the sale item from each allocation.
if (alloc.sale.getAmount() > 0 && !alloc.tax) {
const item = {
Account: alloc.profitCenter.dms_acctnumber,
ControlNumber: JobData.ro_number,
ControlNumber: socket.JobData.ro_number,
Amount: alloc.sale.multiply(-1).toFormat("0.00"),
// Comment: "String",
// AdditionalInfo: "String",
InvoiceNumber: JobData.ro_number,
InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
};
wips.push(item);
}
// Add the cost item if the cost amount is greater than 0 and not a tax
//Add the cost Item.
if (alloc.cost.getAmount() > 0 && !alloc.tax) {
const item = {
Account: alloc.costCenter.dms_acctnumber,
ControlNumber: JobData.ro_number,
ControlNumber: socket.JobData.ro_number,
Amount: alloc.cost.toFormat("0.00"),
// Comment: "String",
// AdditionalInfo: "String",
InvoiceNumber: JobData.ro_number,
InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
};
wips.push(item);
const itemWip = {
Account: alloc.costCenter.dms_wip_acctnumber,
ControlNumber: JobData.ro_number,
ControlNumber: socket.JobData.ro_number,
Amount: alloc.cost.multiply(-1).toFormat("0.00"),
// Comment: "String",
// AdditionalInfo: "String",
InvoiceNumber: JobData.ro_number,
InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
};
wips.push(itemWip);
// Add to the WIP account.
//Add to the WIP account.
}
// Add tax-related entries if applicable
if (alloc.tax) {
if (alloc.sale.getAmount() > 0) {
const item2 = {
Account: alloc.profitCenter.dms_acctnumber,
ControlNumber: JobData.ro_number,
ControlNumber: socket.JobData.ro_number,
Amount: alloc.sale.multiply(-1).toFormat("0.00"),
// Comment: "String",
// AdditionalInfo: "String",
InvoiceNumber: JobData.ro_number,
InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
};
wips.push(item2);
}
}
});
// Add payer information
txEnvelope.payers.forEach((payer) => {
socket.txEnvelope.payers.forEach((payer) => {
const item = {
Account: payer.dms_acctnumber,
ControlNumber: payer.controlnumber,
Amount: Dinero({ amount: Math.round(payer.amount * 100) }).toFormat("0.0"),
// Comment: "String",
// AdditionalInfo: "String",
InvoiceNumber: JobData.ro_number,
InvoiceDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString()
//Comment: "String",
//AdditionalInfo: "String",
InvoiceNumber: socket.JobData.ro_number,
InvoiceDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString()
};
wips.push(item);
});
await setSessionData(socket.id, "transWips", wips);
socket.transWips = wips;
const { data: AccountPostingChange } = await axios.post(
PBS_ENDPOINTS.AccountingPostingChange,
{
SerialNumber: JobData.bodyshop.pbs_serialnumber,
SerialNumber: socket.JobData.bodyshop.pbs_serialnumber,
Posting: {
Reference: JobData.ro_number,
JournalCode: txEnvelope.journal,
TransactionDate: moment(JobData.date_invoiced).tz(JobData.bodyshop.timezone).toISOString(), // "0001-01-01T00:00:00.0000000Z",
Description: txEnvelope.story,
// AdditionalInfo: "String",
Reference: socket.JobData.ro_number,
JournalCode: socket.txEnvelope.journal,
TransactionDate: moment(socket.JobData.date_invoiced).tz(socket.JobData.bodyshop.timezone).toISOString(), //"0001-01-01T00:00:00.0000000Z",
Description: socket.txEnvelope.story,
//AdditionalInfo: "String",
Source: InstanceManager({ imex: "ImEX Online", rome: "Rome Online" }),
Lines: wips
}
@@ -638,56 +566,54 @@ async function InsertAccountPostingData(socket) {
{ auth: PBS_CREDENTIALS, socket }
);
await CheckForErrors(socket, AccountPostingChange);
CheckForErrors(socket, AccountPostingChange);
return AccountPostingChange;
} catch (error) {
await CdkBase.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`);
CdkBase.createLogEvent(socket, "ERROR", `Error in InsertAccountPostingData - ${error}`);
throw new Error(error);
}
}
async function MarkJobExported(socket, jobid) {
await CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`);
CdkBase.createLogEvent(socket, "DEBUG", `Marking job as exported for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const { JobData, transWips } = await getMultipleSessionData(socket.id, ["JobData", "transWips"]);
return await client
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.MARK_JOB_EXPORTED, {
jobId: jobid,
job: {
status: JobData.bodyshop.md_ro_statuses.default_exported || "Exported*",
status: socket.JobData.bodyshop.md_ro_statuses.default_exported || "Exported*",
date_exported: new Date()
},
log: {
bodyshopid: JobData.bodyshop.id,
bodyshopid: socket.JobData.bodyshop.id,
jobid: jobid,
successful: true,
useremail: socket.user.email,
metadata: transWips
metadata: socket.transWips
},
bill: {
exported: true,
exported_at: new Date()
}
});
return result;
}
async function InsertFailedExportLog(socket, error) {
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const JobData = await getSessionData(socket.id, "JobData");
return await client
const result = await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.INSERT_EXPORT_LOG, {
log: {
bodyshopid: JobData.bodyshop.id,
jobid: JobData.id,
bodyshopid: socket.JobData.bodyshop.id,
jobid: socket.JobData.id,
successful: false,
message: [error],
useremail: socket.user.email
}
});
return result;
}

View File

@@ -18,12 +18,12 @@ const { DiscountNotAlreadyCounted } = InstanceManager({
exports.defaultRoute = async function (req, res) {
try {
await CdkBase.createLogEvent(req, "DEBUG", `Received request to calculate allocations for ${req.body.jobid}`);
CdkBase.createLogEvent(req, "DEBUG", `Received request to calculate allocations for ${req.body.jobid}`);
const jobData = await QueryJobData(req, req.BearerToken, req.body.jobid);
return res.status(200).json({ data: await calculateAllocations(req, jobData) });
return res.status(200).json({ data: calculateAllocations(req, jobData) });
} catch (error) {
console.log(error);
await CdkBase.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
CdkBase.createLogEvent(req, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
res.status(500).json({ error: `Error encountered in CdkCalculateAllocations. ${error}` });
}
};
@@ -31,22 +31,22 @@ exports.defaultRoute = async function (req, res) {
exports.default = async function (socket, jobid) {
try {
const jobData = await QueryJobData(socket, "Bearer " + socket.handshake.auth.token, jobid);
return await calculateAllocations(socket, jobData);
return calculateAllocations(socket, jobData);
} catch (error) {
console.log(error);
await CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in CdkCalculateAllocations. ${error}`);
}
};
async function QueryJobData(connectionData, token, jobid) {
await CdkBase.createLogEvent(connectionData, "DEBUG", `Querying job data for id ${jobid}`);
CdkBase.createLogEvent(connectionData, "DEBUG", `Querying job data for id ${jobid}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client.setHeaders({ Authorization: token }).request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
await CdkBase.createLogEvent(connectionData, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
CdkBase.createLogEvent(connectionData, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
return result.jobs_by_pk;
}
async function calculateAllocations(connectionData, job) {
function calculateAllocations(connectionData, job) {
const { bodyshop } = job;
const taxAllocations = InstanceManager({
@@ -164,7 +164,7 @@ async function calculateAllocations(connectionData, job) {
const selectedDmsAllocationConfig = bodyshop.md_responsibility_centers.dms_defaults.find(
(d) => d.name === job.dms_allocation
);
await CdkBase.createLogEvent(
CdkBase.createLogEvent(
connectionData,
"DEBUG",
`Using DMS Allocation ${selectedDmsAllocationConfig && selectedDmsAllocationConfig.name} for cost export.`
@@ -354,10 +354,9 @@ async function calculateAllocations(connectionData, job) {
}
if (InstanceManager({ rome: true })) {
//profile level adjustments for parts
for (const key of Object.keys(job.job_totals.parts.adjustments)) {
Object.keys(job.job_totals.parts.adjustments).forEach((key) => {
const accountName = selectedDmsAllocationConfig.profits[key];
const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName);
if (otherAccount) {
if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero();
@@ -365,41 +364,31 @@ async function calculateAllocations(connectionData, job) {
Dinero(job.job_totals.parts.adjustments[key])
);
} else {
// Use await correctly here
await CdkBase.createLogEvent(
CdkBase.createLogEvent(
connectionData,
"ERROR",
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account for key ${key}.`
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}`
);
}
}
});
//profile level adjustments for labor and materials
for (const key of Object.keys(job.job_totals.rates)) {
if (
job.job_totals.rates[key] &&
job.job_totals.rates[key].adjustment &&
Dinero(job.job_totals.rates[key].adjustment).isZero() === false
) {
Object.keys(job.job_totals.rates).forEach((key) => {
if (job.job_totals.rates[key] && job.job_totals.rates[key].adjustment && Dinero(job.job_totals.rates[key].adjustment).isZero() === false) {
const accountName = selectedDmsAllocationConfig.profits[key.toUpperCase()];
const otherAccount = bodyshop.md_responsibility_centers.profits.find((c) => c.name === accountName);
if (otherAccount) {
if (!profitCenterHash[accountName]) profitCenterHash[accountName] = Dinero();
profitCenterHash[accountName] = profitCenterHash[accountName].add(
Dinero(job.job_totals.rates[key].adjustments)
);
profitCenterHash[accountName] = profitCenterHash[accountName].add(Dinero(job.job_totals.rates[key].adjustments));
} else {
// Await the log event creation here
await CdkBase.createLogEvent(
CdkBase.createLogEvent(
connectionData,
"ERROR",
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account for key ${key}.`
`Error encountered in CdkCalculateAllocations. Unable to find adjustment account. ${error}`
);
}
}
}
});
}
const jobAllocations = _.union(Object.keys(profitCenterHash), Object.keys(costCenterHash)).map((key) => {

View File

@@ -86,7 +86,7 @@ async function GetCdkMakes(req, cdk_dealerid) {
{}
);
await CheckCdkResponseForError(null, soapResponseVehicleSearch);
CheckCdkResponseForError(null, soapResponseVehicleSearch);
const [result, rawResponse, , rawRequest] = soapResponseVehicleSearch;
logger.log("cdk-replace-makes-models-request", "ERROR", req.user.email, null, {
cdk_dealerid,

File diff suppressed because it is too large Load Diff

View File

@@ -15,10 +15,10 @@ exports.CDK_CREDENTIALS = CDK_CREDENTIALS;
const cdkDomain =
process.env.NODE_ENV === "production" ? "https://3pa.dmotorworks.com" : "https://uat-3pa.dmotorworks.com";
async function CheckCdkResponseForError(socket, soapResponse) {
function CheckCdkResponseForError(socket, soapResponse) {
if (!soapResponse[0]) {
//The response was null, this might be ok, it might not.
await CdkBase.createLogEvent(
CdkBase.createLogEvent(
socket,
"WARNING",
`Warning detected in CDK Response - it appears to be null. Stack: ${new Error().stack}`
@@ -31,22 +31,23 @@ async function CheckCdkResponseForError(socket, soapResponse) {
if (Array.isArray(ResultToCheck)) {
ResultToCheck.forEach((result) => checkIndividualResult(socket, result));
} else {
await checkIndividualResult(socket, ResultToCheck);
checkIndividualResult(socket, ResultToCheck);
}
}
exports.CheckCdkResponseForError = CheckCdkResponseForError;
async function checkIndividualResult(socket, ResultToCheck) {
function checkIndividualResult(socket, ResultToCheck) {
if (
ResultToCheck.errorLevel === 0 ||
ResultToCheck.errorLevel === "0" ||
ResultToCheck.code === "success" ||
(!ResultToCheck.code && !ResultToCheck.errorLevel)
) {
)
//TODO: Verify that this is the best way to detect errors.
} else {
await CdkBase.createLogEvent(
return;
else {
CdkBase.createLogEvent(
socket,
"ERROR",
`Error detected in CDK Response - ${JSON.stringify(ResultToCheck, null, 2)}`

View File

@@ -41,11 +41,9 @@ const tasksEmailQueueCleanup = async () => {
}
};
// TODO: Consolidate into a group that can be run (multiple events with the same name)
if (process.env.NODE_ENV !== "development") {
// Handling SIGINT (e.g., Ctrl+C)
process.on("SIGINT", async () => {
logger.log("Clearing Tasks Email Queue...", "INFO", "redis", "api");
await tasksEmailQueueCleanup();
process.exit(0);
});

View File

@@ -7,9 +7,11 @@ require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
// Emit this to bodyshop room
exports.default = async (req, res) => {
const { useremail, bodyshopid, operationName, variables, env, time, dbevent, user } = req.body;
const { ioRedis } = req;
try {
await client.request(queries.INSERT_IOEVENT, {
event: {
@@ -22,6 +24,12 @@ exports.default = async (req, res) => {
useremail
}
});
ioRedis.to(bodyshopid).emit("bodyshop-message", {
operationName,
useremail
});
res.sendStatus(200);
} catch (error) {
logger.log("ioevent-error", "trace", user, null, {

View File

@@ -0,0 +1,189 @@
/**
* Apply Redis helper functions
* @param pubClient
* @param app
*/
const applyRedisHelpers = (pubClient, app) => {
// Store session data in Redis
const setSessionData = async (socketId, key, value) => {
await pubClient.hSet(`socket:${socketId}`, key, JSON.stringify(value)); // Use Redis pubClient
};
// Retrieve session data from Redis
const getSessionData = async (socketId, key) => {
const data = await pubClient.hGet(`socket:${socketId}`, key);
return data ? JSON.parse(data) : null;
};
// Clear session data from Redis
const clearSessionData = async (socketId) => {
await pubClient.del(`socket:${socketId}`);
};
// Store multiple session data in Redis
const setMultipleSessionData = async (socketId, keyValues) => {
// keyValues is expected to be an object { key1: value1, key2: value2, ... }
const entries = Object.entries(keyValues).map(([key, value]) => [key, JSON.stringify(value)]);
await pubClient.hSet(`socket:${socketId}`, ...entries.flat());
};
// Retrieve multiple session data from Redis
const getMultipleSessionData = async (socketId, keys) => {
const data = await pubClient.hmGet(`socket:${socketId}`, keys);
// Redis returns an object with null values for missing keys, so we parse the non-null ones
return Object.fromEntries(keys.map((key, index) => [key, data[index] ? JSON.parse(data[index]) : null]));
};
const setMultipleFromArraySessionData = async (socketId, keyValueArray) => {
// Use Redis multi/pipeline to batch the commands
const multi = pubClient.multi();
keyValueArray.forEach(([key, value]) => {
multi.hSet(`socket:${socketId}`, key, JSON.stringify(value));
});
await multi.exec(); // Execute all queued commands
};
// Helper function to add an item to the end of the Redis list
const addItemToEndOfList = async (socketId, key, newItem) => {
try {
await pubClient.rPush(`socket:${socketId}:${key}`, JSON.stringify(newItem));
} catch (error) {
console.error(`Error adding item to the end of the list for socket ${socketId}:`, error);
}
};
// Helper function to add an item to the beginning of the Redis list
const addItemToBeginningOfList = async (socketId, key, newItem) => {
try {
await pubClient.lPush(`socket:${socketId}:${key}`, JSON.stringify(newItem));
} catch (error) {
console.error(`Error adding item to the beginning of the list for socket ${socketId}:`, error);
}
};
// Helper function to clear a list in Redis
const clearList = async (socketId, key) => {
try {
await pubClient.del(`socket:${socketId}:${key}`);
} catch (error) {
console.error(`Error clearing list for socket ${socketId}:`, error);
}
};
// Add methods to manage room users
const addUserToRoom = async (bodyshopUUID, user) => {
try {
await pubClient.sAdd(`bodyshopRoom:${bodyshopUUID}`, JSON.stringify(user));
} catch (err) {
console.error(`Error adding user to room: ${bodyshopUUID}`);
}
};
const removeUserFromRoom = async (bodyshopUUID, user) => {
try {
await pubClient.sRem(`bodyshopRoom:${bodyshopUUID}`, JSON.stringify(user));
} catch (err) {
console.error(`Error remove user from room: ${bodyshopUUID}`);
}
};
const getUsersInRoom = async (bodyshopUUID) => {
try {
const users = await pubClient.sMembers(`bodyshopRoom:${bodyshopUUID}`);
return users.map((user) => JSON.parse(user));
} catch (err) {
console.error(`Error getUsersInRoom: ${bodyshopUUID}`);
}
};
const api = {
setSessionData,
getSessionData,
clearSessionData,
setMultipleSessionData,
getMultipleSessionData,
setMultipleFromArraySessionData,
addItemToEndOfList,
addItemToBeginningOfList,
clearList,
addUserToRoom,
removeUserFromRoom,
getUsersInRoom
};
Object.assign(module.exports, api);
app.use((req, res, next) => {
req.sessionUtils = api;
next();
});
// // Demo to show how all the helper functions work
// const demoSessionData = async () => {
// const socketId = "testSocketId";
//
// // Store session data using setSessionData
// await exports.setSessionData(socketId, "field1", "Hello, Redis!");
//
// // Retrieve session data using getSessionData
// const field1Value = await exports.getSessionData(socketId, "field1");
// console.log("Retrieved single field value:", field1Value);
//
// // Store multiple session data using setMultipleSessionData
// await exports.setMultipleSessionData(socketId, { field2: "Second Value", field3: "Third Value" });
//
// // Retrieve multiple session data using getMultipleSessionData
// const multipleFields = await exports.getMultipleSessionData(socketId, ["field2", "field3"]);
// console.log("Retrieved multiple field values:", multipleFields);
//
// // Store multiple session data using setMultipleFromArraySessionData
// await exports.setMultipleFromArraySessionData(socketId, [
// ["field4", "Fourth Value"],
// ["field5", "Fifth Value"]
// ]);
//
// // Retrieve and log all fields
// const allFields = await exports.getMultipleSessionData(socketId, [
// "field1",
// "field2",
// "field3",
// "field4",
// "field5"
// ]);
// console.log("Retrieved all field values:", allFields);
//
// // Add item to the end of a Redis list
// await exports.addItemToEndOfList(socketId, "logEvents", { event: "Log Event 1", timestamp: new Date() });
// await exports.addItemToEndOfList(socketId, "logEvents", { event: "Log Event 2", timestamp: new Date() });
//
// // Add item to the beginning of a Redis list
// await exports.addItemToBeginningOfList(socketId, "logEvents", { event: "First Log Event", timestamp: new Date() });
//
// // Retrieve the entire list (using lRange)
// const logEvents = await pubClient.lRange(`socket:${socketId}:logEvents`, 0, -1);
// console.log("Log Events List:", logEvents.map(JSON.parse));
//
// // **Add the new code below to test clearList**
//
// // Clear the list using clearList
// await exports.clearList(socketId, "logEvents");
// console.log("Log Events List cleared.");
//
// // Retrieve the list after clearing to confirm it's empty
// const logEventsAfterClear = await pubClient.lRange(`socket:${socketId}:logEvents`, 0, -1);
// console.log("Log Events List after clearing:", logEventsAfterClear); // Should be an empty array
//
// // Clear session data
// await exports.clearSessionData(socketId);
// console.log("Session data cleared.");
// };
//
// if (process.env.NODE_ENV === "development") {
// demoSessionData();
// }
// "th1s1sr3d1s" (BCrypt)
return api;
};
module.exports = applyRedisHelpers;

View File

@@ -0,0 +1,104 @@
const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const logger = require("../utils/logger");
const { admin } = require("../firebase/firebase-handler");
// Logging helper functions
function createLogEvent(socket, level, message) {
console.log(`[IOREDIS LOG EVENT] - ${socket.user.email} - ${socket.id} - ${message}`);
logger.log("ioredis-log-event", level, socket.user.email, null, { wsmessage: message });
}
const redisSocketEvents = (io, { addUserToRoom, getUsersInRoom, removeUserFromRoom }) => {
// Room management and broadcasting events
function registerRoomAndBroadcastEvents(socket) {
socket.on("join-bodyshop-room", async (bodyshopUUID) => {
socket.join(bodyshopUUID);
await addUserToRoom(bodyshopUUID, { uid: socket.user.uid, email: socket.user.email });
createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${bodyshopUUID}`);
// Notify all users in the room about the updated user list
const usersInRoom = await getUsersInRoom(bodyshopUUID);
io.to(bodyshopUUID).emit("room-users-updated", usersInRoom);
});
socket.on("leave-bodyshop-room", async (bodyshopUUID) => {
socket.leave(bodyshopUUID);
createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${bodyshopUUID}`);
});
socket.on("get-room-users", async (bodyshopUUID, callback) => {
const usersInRoom = await getUsersInRoom(bodyshopUUID);
callback(usersInRoom);
});
socket.on("broadcast-to-bodyshop", async (bodyshopUUID, message) => {
io.to(bodyshopUUID).emit("bodyshop-message", message);
createLogEvent(socket, "INFO", `Broadcast message to bodyshop ${bodyshopUUID}`);
});
socket.on("disconnect", async () => {
createLogEvent(socket, "DEBUG", `User disconnected.`);
// Get all rooms the socket is part of
const rooms = Array.from(socket.rooms).filter((room) => room !== socket.id);
for (const bodyshopUUID of rooms) {
await removeUserFromRoom(bodyshopUUID, { uid: socket.user.uid, email: socket.user.email });
// Notify all users in the room about the updated user list
const usersInRoom = await getUsersInRoom(bodyshopUUID);
io.to(bodyshopUUID).emit("room-users-updated", usersInRoom);
}
});
}
// Register all socket events for a given socket connection
function registerSocketEvents(socket) {
createLogEvent(socket, "DEBUG", `Connected and Authenticated.`);
// Register room and broadcasting events
registerRoomAndBroadcastEvents(socket);
// Handle socket disconnection
socket.on("disconnect", async () => {
createLogEvent(socket, "DEBUG", `User disconnected.`);
});
}
const authMiddleware = (socket, next) => {
try {
if (socket.handshake.auth.token) {
admin
.auth()
.verifyIdToken(socket.handshake.auth.token)
.then((user) => {
socket.user = user;
next();
})
.catch((error) => {
next(new Error("Authentication error", JSON.stringify(error)));
});
} else {
next(new Error("Authentication error - no authorization token."));
}
} catch (error) {
console.log("Uncaught connection error:::", error);
logger.log("websocket-connection-error", "error", null, null, {
token: socket.handshake.auth.token,
...error
});
next(new Error(`Authentication error ${error}`));
}
};
// Socket.IO Middleware and Connection
io.use(authMiddleware);
io.on("connection", registerSocketEvents);
};
module.exports = {
redisSocketEvents
};

View File

@@ -3,68 +3,64 @@ require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const { io, setSessionData, clearSessionData, getMultipleSessionData, addItemToEndOfList } = require("../../server");
const { io } = require("../../server");
const { admin } = require("../firebase/firebase-handler");
const logger = require("../utils/logger");
const { default: CdkJobExport, CdkSelectedCustomer } = require("../cdk/cdk-job-export");
const CdkGetMakes = require("../cdk/cdk-get-makes").default;
const CdkCalculateAllocations = require("../cdk/cdk-calculate-allocations").default;
const { isArray } = require("lodash");
const logger = require("../utils/logger");
const { default: PbsExportJob, PbsSelectedCustomer } = require("../accounting/pbs/pbs-job-export");
const { PbsCalculateAllocationsAp, PbsExportAp } = require("../accounting/pbs/pbs-ap-allocations");
// Middleware for verifying tokens via Firebase authentication
function socketAuthMiddleware(socket, next) {
const token = socket.handshake.auth.token;
if (!token) return next(new Error("Authentication error - no authorization token."));
admin
.auth()
.verifyIdToken(token)
.then((user) => {
socket.user = user;
next();
})
.catch((error) => {
next(new Error("Authentication error", JSON.stringify(error)));
io.use(function (socket, next) {
try {
if (socket.handshake.auth.token) {
admin
.auth()
.verifyIdToken(socket.handshake.auth.token)
.then((user) => {
socket.user = user;
next();
})
.catch((error) => {
next(new Error("Authentication error", JSON.stringify(error)));
});
} else {
next(new Error("Authentication error - no authorization token."));
}
} catch (error) {
console.log("Uncaught connection error:::", error);
logger.log("websocket-connection-error", "error", null, null, {
token: socket.handshake.auth.token,
...error
});
}
next(new Error(`Authentication error ${error}`));
}
});
// Register all socket events for a given socket connection
async function registerSocketEvents(socket) {
await setSessionData(socket.id, "log_level", "TRACE");
await createLogEvent(socket, "DEBUG", `Connected and Authenticated.`);
io.on("connection", (socket) => {
socket.log_level = "TRACE";
createLogEvent(socket, "DEBUG", `Connected and Authenticated.`);
// Register CDK-related socket events
registerCdkEvents(socket);
// Register PBS AR-related socket events
registerPbsArEvents(socket);
// Register PBS AP-related socket events
registerPbsApEvents(socket);
// Register room and broadcasting events
registerRoomAndBroadcastEvents(socket);
// Register event to clear DMS session
registerDmsClearSessionEvent(socket);
// Handle socket disconnection
socket.on("disconnect", async () => {
await createLogEvent(socket, "DEBUG", `User disconnected.`);
socket.on("set-log-level", (level) => {
socket.log_level = level;
socket.emit("log-event", {
timestamp: new Date(),
level: "INFO",
message: `Updated log level to ${level}`
});
});
}
// CDK-specific socket events
function registerCdkEvents(socket) {
socket.on("cdk-export-job", (jobid) => CdkJobExport(socket, jobid));
socket.on("cdk-selected-customer", async (selectedCustomerId) => {
await createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`);
///CDK
socket.on("cdk-export-job", (jobid) => {
CdkJobExport(socket, jobid);
});
socket.on("cdk-selected-customer", (selectedCustomerId) => {
createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`);
socket.selectedCustomerId = selectedCustomerId;
CdkSelectedCustomer(socket, selectedCustomerId);
await setSessionData(socket.id, "selectedCustomerId ", selectedCustomerId);
});
socket.on("cdk-get-makes", async (cdk_dealerid, callback) => {
@@ -72,160 +68,159 @@ function registerCdkEvents(socket) {
const makes = await CdkGetMakes(socket, cdk_dealerid);
callback(makes);
} catch (error) {
await createLogEvent(socket, "ERROR", `Error in cdk-get-makes WS call. ${JSON.stringify(error, null, 2)}`);
createLogEvent(socket, "ERROR", `Error in cdk-get-makes WS call. ${JSON.stringify(error, null, 2)}`);
}
});
socket.on("cdk-calculate-allocations", async (jobid, callback) => {
const allocations = await CdkCalculateAllocations(socket, jobid);
await createLogEvent(socket, "DEBUG", `Allocations calculated.`);
await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations, null, 2)}`);
createLogEvent(socket, "DEBUG", `Allocations calculated.`);
createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
callback(allocations);
});
}
//END CDK
// PBS AR-specific socket events
function registerPbsArEvents(socket) {
//PBS AR
socket.on("pbs-calculate-allocations", async (jobid, callback) => {
const allocations = await CdkCalculateAllocations(socket, jobid);
await createLogEvent(socket, "DEBUG", `PBS AR allocations calculated.`);
await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations, null, 2)}`);
createLogEvent(socket, "DEBUG", `Allocations calculated.`);
createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
callback(allocations);
});
socket.on("pbs-export-job", (jobid) => PbsExportJob(socket, jobid));
socket.on("pbs-selected-customer", async (selectedCustomerId) => {
await createLogEvent(socket, "DEBUG", `PBS AR selected customer ID ${selectedCustomerId}`);
PbsSelectedCustomer(socket, selectedCustomerId).catch((err) =>
console.error(`Error in pbs-selected-customer: ${err}`)
);
await setSessionData(socket.id, "selectedCustomerId", selectedCustomerId);
socket.on("pbs-export-job", (jobid) => {
PbsExportJob(socket, jobid);
});
}
socket.on("pbs-selected-customer", (selectedCustomerId) => {
createLogEvent(socket, "DEBUG", `User selected customer ID ${selectedCustomerId}`);
socket.selectedCustomerId = selectedCustomerId;
PbsSelectedCustomer(socket, selectedCustomerId);
});
//End PBS AR
// PBS AP-specific socket events
function registerPbsApEvents(socket) {
//PBS AP
socket.on("pbs-calculate-allocations-ap", async (billids, callback) => {
const allocations = await PbsCalculateAllocationsAp(socket, billids);
await createLogEvent(socket, "DEBUG", `PBS AP allocations calculated.`);
await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations, null, 2)}`);
createLogEvent(socket, "DEBUG", `AP Allocations calculated.`);
createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
socket.apAllocations = allocations;
callback(allocations);
await setSessionData(socket.id, "pbs_ap_allocations", allocations);
});
socket.on("pbs-export-ap", async ({ billids, txEnvelope }) => {
await setSessionData(socket.id, "txEnvelope", txEnvelope);
PbsExportAp(socket, {
billids,
txEnvelope
}).catch((err) => console.error(`Error in pbs-export-ap: ${err}`));
});
}
// Room management and broadcasting events
function registerRoomAndBroadcastEvents(socket) {
socket.on("join-bodyshop-room", async (bodyshopUUID) => {
socket.join(bodyshopUUID);
await createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${bodyshopUUID}`);
socket.on("pbs-export-ap", ({ billids, txEnvelope }) => {
socket.txEnvelope = txEnvelope;
PbsExportAp(socket, { billids, txEnvelope });
});
socket.on("leave-bodyshop-room", async (bodyshopUUID) => {
socket.leave(bodyshopUUID);
await createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${bodyshopUUID}`);
//END PBS AP
socket.on("disconnect", () => {
createLogEvent(socket, "DEBUG", `User disconnected.`);
});
});
socket.on("broadcast-to-bodyshop", async (bodyshopUUID, message) => {
io.to(bodyshopUUID).emit("bodyshop-message", message);
await createLogEvent(socket, "INFO", `Broadcast message to bodyshop ${bodyshopUUID}`);
});
}
// DMS session clearing event
function registerDmsClearSessionEvent(socket) {
socket.on("clear-dms-session", async () => {
await clearSessionData(socket.id); // Clear all session data in Redis
await createLogEvent(socket, "INFO", `DMS session data cleared for socket ${socket.id}`);
});
}
// Logging helper functions
async function createLogEvent(socket, level, message) {
const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]);
if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy(level)) {
const logMessage = { timestamp: new Date(), level, message };
// Log the message to the console and emit it via the socket
console.log(`[WS LOG EVENT] ${level} - ${logMessage.timestamp} - ${socket.user.email} - ${socket.id} - ${message}`);
socket.emit("log-event", logMessage);
// Log the event via the logger
logger.log("ws-log-event", level, socket.user.email, recordid, { wsmessage: message });
// Add the log message to the Redis list using the helper function
await addItemToEndOfList(socket.id, "logEvents", logMessage);
}
}
async function createJsonEvent(socket, level, message, json) {
const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]);
if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy(level)) {
const logMessage = { timestamp: new Date(), level, message };
// Emit the log message via the socket
socket.emit("log-event", logMessage);
// Log the JSON event via the logger
logger.log("ws-log-event-json", level, socket.user.email, recordid, {
wsmessage: message,
json
function createLogEvent(socket, level, message) {
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) {
console.log(`[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${socket.id} - ${message}`);
socket.emit("log-event", {
timestamp: new Date(),
level,
message
});
// Use the helper function to append the log event to the Redis list
await addItemToEndOfList(socket.id, "logEvents", logMessage);
logger.log("ws-log-event", level, socket.user.email, socket.recordid, {
wsmessage: message
});
if (socket.logEvents && isArray(socket.logEvents)) {
socket.logEvents.push({
timestamp: new Date(),
level,
message
});
}
// if (level === "ERROR") {
// throw new Error(message);
// }
}
}
async function createXmlEvent(socket, xml, message, isError = false) {
const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]);
function createJsonEvent(socket, level, message, json) {
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy(level)) {
console.log(`[WS LOG EVENT] ${level} - ${new Date()} - ${socket.user.email} - ${socket.id} - ${message}`);
socket.emit("log-event", {
timestamp: new Date(),
level,
message
});
}
logger.log("ws-log-event-json", level, socket.user.email, socket.recordid, {
wsmessage: message,
json
});
if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy("TRACE")) {
const logMessage = {
if (socket.logEvents && isArray(socket.logEvents)) {
socket.logEvents.push({
timestamp: new Date(),
level,
message
});
}
// if (level === "ERROR") {
// throw new Error(message);
// }
}
function createXmlEvent(socket, xml, message, isError = false) {
if (LogLevelHierarchy(socket.log_level) >= LogLevelHierarchy("TRACE")) {
socket.emit("log-event", {
timestamp: new Date(),
level: isError ? "ERROR" : "TRACE",
message: `${message}: ${xml}`
};
});
}
// Emit the log message via the socket
socket.emit("log-event", logMessage);
logger.log(
isError ? "ws-log-event-xml-error" : "ws-log-event-xml",
isError ? "ERROR" : "TRACE",
socket.user.email,
socket.recordid,
{
wsmessage: message,
xml
}
);
// Log the XML event via the logger
logger.log(
isError ? "ws-log-event-xml-error" : "ws-log-event-xml",
isError ? "ERROR" : "TRACE",
socket.user.email,
recordid,
{ wsmessage: message, xml }
);
// Use the helper function to append the log event to the Redis list
await addItemToEndOfList(socket.id, "logEvents", { ...logMessage, xml });
if (socket.logEvents && isArray(socket.logEvents)) {
socket.logEvents.push({
timestamp: new Date(),
level: isError ? "ERROR" : "TRACE",
message,
xml
});
}
}
// Log level hierarchy
function LogLevelHierarchy(level) {
const levels = { XML: 5, TRACE: 5, DEBUG: 4, INFO: 3, WARNING: 2, ERROR: 1 };
return levels[level] || 3;
switch (level) {
case "XML":
return 5;
case "TRACE":
return 5;
case "DEBUG":
return 4;
case "INFO":
return 3;
case "WARNING":
return 2;
case "ERROR":
return 1;
default:
return 3;
}
}
// Socket.IO Middleware and Connection
io.use(socketAuthMiddleware);
io.on("connection", registerSocketEvents);
// Export logging helpers
exports.createLogEvent = createLogEvent;
exports.createXmlEvent = createXmlEvent;
exports.createJsonEvent = createJsonEvent;