Compare commits

..

23 Commits

Author SHA1 Message Date
Dave Richer
b13c42b02f IO-2742-redis - Merge Release / Up deps (prior to parking)
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-24 14:44:22 -04:00
Dave Richer
93f6a80fda Merge remote-tracking branch 'origin/release/2024-09-27' into feature/IO-2742-redis 2024-09-24 14:21:51 -04:00
Dave Richer
5e14258839 IO-2742-redis - PR Changes
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-24 14:20:09 -04:00
Dave Richer
5c54cf6c44 feature/IO-2924-Refactor-Production-Board-For-Sockets - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-16 11:48:17 -04:00
Dave Richer
a2d95dbce3 feature/IO-2742-redis - Checkpoint - clear stage before moving to sub task
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-12 11:53:29 -04:00
Dave Richer
51c181dab7 feature/IO-2742-redis - Checkpoint, Final cleanup of server.js
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-11 20:52:49 -04:00
Dave Richer
4486858a86 feature/IO-2742-redis - Checkpoint, All optimizations to prevent multiple requests to redis have been applied. Things like using lists for the Log Events, to getting and setting multiple values at the same time when given the chance.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-11 20:27:40 -04:00
Dave Richer
f606228792 feature/IO-2742-redis - Checkpoint, Redis fully implemented.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-11 19:08:24 -04:00
Dave Richer
bca0a35cdd Remove express line
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-11 11:12:14 -04:00
Dave Richer
0ee51ed4c1 - Update
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-11 11:06:36 -04:00
Dave Richer
11b903f24b - Fix DMS Socket (missing reference)
- Add preview mode

Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-11 10:30:55 -04:00
Dave Richer
f83deb0c26 - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-10 17:15:08 -04:00
Dave Richer
9a0d973b26 Normalize SocketIO App wide.
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-10 16:23:38 -04:00
Dave Richer
ea0a717895 feature/IO-2742-redis - Normalize Network
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-10 15:17:13 -04:00
Dave Richer
40f1a2f6ed feature/IO-2742-redis - Dep Bump
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-10 14:34:50 -04:00
Dave Richer
3433b1a600 Merge remote-tracking branch 'origin/master-AIO' into feature/IO-2742-redis 2024-09-10 14:28:44 -04:00
Dave Richer
49a7313abb - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-09 23:27:28 -04:00
Dave Richer
319b24ce8a Merge remote-tracking branch 'origin/master-AIO' into feature/IO-2742-redis 2024-09-09 11:39:48 -04:00
Dave Richer
08d8a4f7dc - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-09 11:39:39 -04:00
Dave Richer
03e8b62e4f - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-06 13:22:17 -04:00
Dave Richer
44db8f20e9 - Checkpoint
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-06 11:53:08 -04:00
Dave Richer
f28068d0e7 Merge remote-tracking branch 'origin/release/2024-09-06' into feature/IO-2742-redis 2024-09-05 19:38:00 -04:00
Patrick Fic
5f082b9619 Basic connection to elasticache server. 2024-08-22 14:48:00 -07:00
41 changed files with 2331 additions and 2770 deletions

1634
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.19.12",
"@ant-design/pro-layout": "^7.20.2",
"@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.20.1",
"antd": "^5.21.0",
"apollo-link-logger": "^2.0.1",
"apollo-link-sentry": "^3.3.0",
"autosize": "^6.0.1",

View File

@@ -1,62 +1,66 @@
import { SyncOutlined } from "@ant-design/icons";
import { Button, Card, Form, Input, Table } from "antd";
import React, { useEffect, useState } from "react";
import React, { useEffect, useState, useContext } 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
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps)(DmsAllocationsSummaryAp);
export default connect(mapStateToProps, mapDispatchToProps)(DmsAllocationsSummaryAp);
export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
export function DmsAllocationsSummaryAp({ bodyshop, billids, title }) {
const { t } = useTranslation();
const [allocationsSummary, setAllocationsSummary] = useState([]);
const { socket } = useContext(SocketContext);
useEffect(() => {
socket.on("ap-export-success", (billid) => {
if (!socket || !socket.connected) return;
const handleSuccess = async (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 };
});
});
if (socket.disconnected) socket.connect();
return () => {
socket.removeListener("ap-export-success");
socket.removeListener("ap-export-failure");
//socket.disconnect();
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);
}
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
socket.on("ap-export-success", handleSuccess);
return () => {
socket.off("ap-export-success", handleSuccess);
};
}, [socket]);
useEffect(() => {
if (socket.connected) {
if (socket && 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"),
@@ -68,35 +72,40 @@ export function DmsAllocationsSummaryAp({ socket, 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%" }}>
<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>
<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>
))}
</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) => {
socket.emit(`pbs-export-ap`, {
billids,
txEnvelope: values
});
if (socket) {
socket.emit("pbs-export-ap", {
billids,
txEnvelope: values
});
}
};
return (
@@ -105,7 +114,9 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
extra={
<Button
onClick={() => {
socket.emit("pbs-calculate-allocations-ap", billids, (ack) => setAllocationsSummary(ack));
if (socket) {
socket.emit("pbs-calculate-allocations-ap", billids, (ack) => setAllocationsSummary(ack));
}
}}
>
<SyncOutlined />
@@ -124,12 +135,7 @@ export function DmsAllocationsSummaryAp({ socket, bodyshop, billids, title }) {
name="journal"
label={t("jobs.fields.dms.journal")}
initialValue={bodyshop.cdk_configuration && bodyshop.cdk_configuration.default_journal}
rules={[
{
required: true
//message: t("general.validation.required"),
}
]}
rules={[{ required: true }]}
>
<Input />
</Form.Item>

View File

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

View File

@@ -1,37 +1,51 @@
import { Button, Checkbox, Col, Table } from "antd";
import React, { useState } from "react";
import React, { useContext, useEffect, 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) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
const mapDispatchToProps = (dispatch) => ({});
export default connect(mapStateToProps, mapDispatchToProps)(DmsCustomerSelector);
export function DmsCustomerSelector({ bodyshop }) {
const { t } = useTranslation();
const [customerList, setcustomerList] = useState([]);
const { socket } = useContext(SocketContext); // Use Socket Context
const [customerList, setCustomerList] = useState([]);
const [open, setOpen] = useState(false);
const [selectedCustomer, setSelectedCustomer] = useState(null);
const [dmsType, setDmsType] = useState("cdk");
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);
});
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]);
const onUseSelected = () => {
setOpen(false);
@@ -69,17 +83,11 @@ 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, 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}`
render: (record) =>
`${record.address?.addressLine?.[0]}, ${record.address?.city} ${record.address?.stateOrProvince} ${record.address?.postalCode}`
}
];
@@ -95,15 +103,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, value) => `${record.Address}, ${record.City} ${record.State} ${record.ZipCode}`
render: (record) => `${record.Address}, ${record.City} ${record.State} ${record.ZipCode}`
}
];
if (!open) return null;
return (
<Col span={24}>
<Table
@@ -125,7 +133,6 @@ 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,11 +4,8 @@ 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({
bodyshop: selectBodyshop
});
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -17,7 +14,7 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(DmsLogEvents);
export function DmsLogEvents({ socket, logs, bodyshop }) {
export function DmsLogEvents({ logs }) {
return (
<Timeline
pending

View File

@@ -219,7 +219,7 @@ export function JobsExportAllButton({
};
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled || jobIds?.length > 10}>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.exportselected")}
</Button>
);

View File

@@ -200,7 +200,7 @@ export function PayableExportAll({
);
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled || billids?.length > 10}>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.exportselected")}
</Button>
);

View File

@@ -180,7 +180,7 @@ export function PaymentsExportAllButton({
};
return (
<Button onClick={handleQbxml} loading={loading} disabled={disabled || paymentIds?.length > 10}>
<Button onClick={handleQbxml} loading={loading} disabled={disabled}>
{t("jobs.actions.exportselected")}
</Button>
);

View File

@@ -1,9 +1,8 @@
import React, { useContext, useEffect, useMemo, useRef } from "react";
import { useApolloClient, useQuery, useSubscription } from "@apollo/client";
import React, { useEffect, useMemo, useRef } from "react";
import { useQuery, useSubscription } from "@apollo/client";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import {
QUERY_EXACT_JOB_IN_PRODUCTION,
QUERY_JOBS_IN_PRODUCTION,
SUBSCRIPTION_JOBS_IN_PRODUCTION,
SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW
@@ -11,8 +10,6 @@ import {
import { QUERY_KANBAN_SETTINGS } from "../../graphql/user.queries";
import { selectBodyshop, selectCurrentUser } from "../../redux/user/user.selectors";
import ProductionBoardKanbanComponent from "./production-board-kanban.component";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
const mapStateToProps = createStructuredSelector({
bodyshop: selectBodyshop,
@@ -20,20 +17,7 @@ const mapStateToProps = createStructuredSelector({
});
function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionType = "direct" }) {
const fired = useRef(false);
const client = useApolloClient();
const { socket } = useContext(SocketContext); // Get the socket from context
const reconnectTimeout = useRef(null); // To store the reconnect timeout
const disconnectTime = useRef(null); // To track disconnection time
const acceptableReconnectTime = 2000; // 2 seconds threshold
const {
treatments: { Websocket_Production }
} = useSplitTreatments({
attributes: {},
names: ["Websocket_Production"],
splitKey: bodyshop && bodyshop.imexshopid
});
const fired = useRef(false); // useRef to keep track of whether the subscription fired
const combinedStatuses = useMemo(
() => [
@@ -50,12 +34,9 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp
onError: (error) => console.error(`Error fetching jobs in production: ${error.message}`)
});
const subscriptionEnabled = Websocket_Production?.treatment === "off";
const { data: updatedJobs } = useSubscription(
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION,
{
skip: !subscriptionEnabled,
onError: (error) => console.error(`Error subscribing to jobs in production: ${error.message}`)
}
);
@@ -65,112 +46,23 @@ function ProductionBoardKanbanContainer({ bodyshop, currentUser, subscriptionTyp
onError: (error) => console.error(`Error fetching Kanban settings: ${error.message}`)
});
useEffect(() => {
if (subscriptionEnabled) {
if (!updatedJobs) {
return;
}
if (!fired.current) {
fired.current = true;
return;
}
refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
}
}, [updatedJobs, refetch, subscriptionEnabled]);
// This provides us the current version of the Lanes from the Redux store
// const currentReducerData = useSelector((state) => (state.trello.lanes ? state.trello : {}));
// Socket.IO implementation for users with Split treatment "off"
useEffect(() => {
if (subscriptionEnabled || !socket || !bodyshop || !bodyshop.id) {
if (!updatedJobs) {
return;
}
const handleJobUpdates = async (jobChangedData) => {
const jobId = jobChangedData.id;
// Access the existing cache for QUERY_JOBS_IN_PRODUCTION
const existingJobsCache = client.readQuery({
query: QUERY_JOBS_IN_PRODUCTION
});
const existingJobs = existingJobsCache?.jobs || [];
// Check if the job already exists in the cached jobs
const existingJob = existingJobs.find((job) => job.id === jobId);
if (existingJob) {
// If the job exists, we update the cache without making any additional queries
client.writeQuery({
query: QUERY_JOBS_IN_PRODUCTION,
data: {
jobs: existingJobs.map((job) =>
job.id === jobId ? { ...existingJob, ...jobChangedData, __typename: "jobs" } : job
)
}
});
} else {
// If the job doesn't exist, fetch it from the server and then add it to the cache
try {
const { data: jobData } = await client.query({
query: QUERY_EXACT_JOB_IN_PRODUCTION,
variables: { id: jobId },
fetchPolicy: "network-only"
});
// Add the job to the existing cached jobs
client.writeQuery({
query: QUERY_JOBS_IN_PRODUCTION,
data: {
jobs: [...existingJobs, { ...jobData.job, __typename: "jobs" }]
}
});
} catch (error) {
console.error(`Error fetching job ${jobId}: ${error.message}`);
}
}
};
const handleDisconnect = () => {
// Capture the disconnection time
disconnectTime.current = Date.now();
};
const handleReconnect = () => {
const reconnectTime = Date.now();
const disconnectionDuration = reconnectTime - disconnectTime.current;
// Only refetch if disconnection was longer than the acceptable reconnect time
if (disconnectionDuration >= acceptableReconnectTime) {
if (!reconnectTimeout.current) {
reconnectTimeout.current = setTimeout(() => {
const randomDelay = Math.floor(Math.random() * (30000 - 10000 + 1)) + 10000; // Random delay between 10 and 30 seconds
setTimeout(() => {
if (refetch) refetch().catch((err) => console.error(`Issue `));
reconnectTimeout.current = null; // Clear the timeout reference after refetch
}, randomDelay);
}, acceptableReconnectTime);
}
}
};
// Listen for 'job-changed', 'disconnect', and 'connect' events
socket.on("production-job-updated", handleJobUpdates);
socket.on("disconnect", handleDisconnect);
socket.on("connect", handleReconnect);
// Clean up on unmount or when dependencies change
return () => {
socket.off("production-job-updated", handleJobUpdates);
socket.off("disconnect", handleDisconnect);
socket.off("connect", handleReconnect);
if (reconnectTimeout.current) {
clearTimeout(reconnectTimeout.current);
}
};
}, [subscriptionEnabled, socket, bodyshop, client, refetch]);
if (!fired.current) {
fired.current = true;
return;
}
refetch().catch((err) => console.error(`Error re-fetching jobs in production: ${err.message}`));
}, [updatedJobs, refetch]);
const filteredAssociationSettings = useMemo(() => {
return associationSettings?.associations[0] || null;
}, [associationSettings?.associations]);
}, [associationSettings]);
return (
<ProductionBoardKanbanComponent

View File

@@ -1,5 +1,5 @@
import { useApolloClient, useQuery, useSubscription } from "@apollo/client";
import React, { useContext, useEffect, useState, useRef } from "react";
import React, { useEffect, useState } from "react";
import {
QUERY_EXACT_JOB_IN_PRODUCTION,
QUERY_EXACT_JOBS_IN_PRODUCTION,
@@ -9,46 +9,19 @@ import {
} from "../../graphql/jobs.queries";
import ProductionListTable from "./production-list-table.component";
import _ from "lodash";
import { useSplitTreatments } from "@splitsoftware/splitio-react";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
export default function ProductionListTableContainer({ bodyshop, subscriptionType = "direct" }) {
const client = useApolloClient();
const { socket } = useContext(SocketContext);
const [joblist, setJoblist] = useState([]);
const reconnectTimeout = useRef(null); // To store the reconnect timeout
const disconnectTime = useRef(null); // To store the time of disconnection
const acceptableReconnectTime = 2000; // 2 seconds threshold
// Get Split treatment
const {
treatments: { Websocket_Production }
} = useSplitTreatments({
attributes: {},
names: ["Websocket_Production"],
splitKey: bodyshop && bodyshop.imexshopid
});
// Determine if subscription is enabled
const subscriptionEnabled = Websocket_Production?.treatment === "off";
// Use GraphQL query
export default function ProductionListTableContainer({ subscriptionType = "direct" }) {
const { refetch, loading, data } = useQuery(QUERY_JOBS_IN_PRODUCTION, {
pollInterval: 3600000,
fetchPolicy: "network-only",
nextFetchPolicy: "network-only"
});
// Use GraphQL subscription when subscription is enabled
const client = useApolloClient();
const [joblist, setJoblist] = useState([]);
const { data: updatedJobs } = useSubscription(
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION,
{
skip: !subscriptionEnabled
}
subscriptionType === "view" ? SUBSCRIPTION_JOBS_IN_PRODUCTION_VIEW : SUBSCRIPTION_JOBS_IN_PRODUCTION
);
// Update joblist when data changes
useEffect(() => {
if (!(data && data.jobs)) return;
setJoblist(
@@ -58,134 +31,34 @@ export default function ProductionListTableContainer({ bodyshop, subscriptionTyp
);
}, [data]);
// Handle updates from GraphQL subscription
useEffect(() => {
if (subscriptionEnabled) {
if (!updatedJobs || joblist.length === 0) return;
if (!updatedJobs || joblist.length === 0) return;
const jobDiff = _.differenceWith(
joblist,
updatedJobs.jobs,
(a, b) => a.id === b.id && a.updated_at === b.updated_at
);
const jobDiff = _.differenceWith(
joblist,
updatedJobs.jobs,
(a, b) => a.id === b.id && a.updated_at === b.updated_at
);
if (jobDiff.length > 1) {
getUpdatedJobsData(jobDiff.map((j) => j.id));
} else if (jobDiff.length === 1) {
jobDiff.forEach((job) => {
getUpdatedJobData(job.id);
});
}
setJoblist(updatedJobs.jobs);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [updatedJobs, subscriptionEnabled]);
// Handle updates from Socket.IO when subscription is disabled
useEffect(() => {
if (subscriptionEnabled || !socket || !bodyshop || !bodyshop.id) {
return;
}
const handleJobUpdates = async (jobChangedData) => {
const jobId = jobChangedData.id;
// Access the existing cache for QUERY_JOBS_IN_PRODUCTION
const existingJobsCache = client.readQuery({
query: QUERY_JOBS_IN_PRODUCTION
if (jobDiff.length > 1) {
getUpdatedJobsData(jobDiff.map((j) => j.id));
} else if (jobDiff.length === 1) {
jobDiff.forEach((job) => {
getUpdatedJobData(job.id);
});
}
const existingJobs = existingJobsCache?.jobs || [];
setJoblist(updatedJobs.jobs);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [updatedJobs]);
// Check if the job already exists in the cached jobs
const existingJob = existingJobs.find((job) => job.id === jobId);
if (existingJob) {
// If the job exists, we update the cache without making any additional queries
client.writeQuery({
query: QUERY_JOBS_IN_PRODUCTION,
data: {
jobs: existingJobs.map((job) =>
job.id === jobId ? { ...existingJob, ...jobChangedData, __typename: "jobs" } : job
)
}
});
} else {
// If the job doesn't exist, fetch it from the server and then add it to the cache
try {
const { data: jobData } = await client.query({
query: QUERY_EXACT_JOB_IN_PRODUCTION,
variables: { id: jobId },
fetchPolicy: "network-only"
});
// Add the job to the existing cached jobs
client.writeQuery({
query: QUERY_JOBS_IN_PRODUCTION,
data: {
jobs: [...existingJobs, { ...jobData.job, __typename: "jobs" }]
}
});
} catch (error) {
console.error(`Error fetching job ${jobId}: ${error.message}`);
}
}
};
const handleDisconnect = () => {
// Capture the time when the disconnection happens
disconnectTime.current = Date.now();
};
const handleReconnect = () => {
// Calculate how long the disconnection lasted
const reconnectTime = Date.now();
const disconnectionDuration = reconnectTime - disconnectTime.current;
// If disconnection lasted less than acceptable reconnect time, do nothing
if (disconnectionDuration < acceptableReconnectTime) {
return;
}
// Schedule a refetch with a random delay between 10 and 30 seconds
if (!reconnectTimeout.current) {
reconnectTimeout.current = setTimeout(() => {
const randomDelay = Math.floor(Math.random() * (30000 - 10000 + 1)) + 10000; // Random delay between 10 and 30 seconds
setTimeout(() => {
if (refetch) refetch();
reconnectTimeout.current = null; // Clear the timeout reference after refetch
}, randomDelay);
}, acceptableReconnectTime);
}
};
// Listen for 'production-job-updated', 'disconnect', and 'connect' events
socket.on("production-job-updated", handleJobUpdates);
socket.on("disconnect", handleDisconnect);
socket.on("connect", handleReconnect);
// Clean up on unmount or when dependencies change
return () => {
socket.off("production-job-updated", handleJobUpdates);
socket.off("disconnect", handleDisconnect);
socket.off("connect", handleReconnect);
if (reconnectTimeout.current) {
clearTimeout(reconnectTimeout.current);
}
};
}, [subscriptionEnabled, socket, bodyshop, client, refetch]);
// Functions to fetch updated job data
const getUpdatedJobData = async (jobId) => {
await client.query({
client.query({
query: QUERY_EXACT_JOB_IN_PRODUCTION,
variables: { id: jobId },
fetchPolicy: "network-only"
variables: { id: jobId }
});
};
const getUpdatedJobsData = (jobIds) => {
const getUpdatedJobsData = async (jobIds) => {
client.query({
query: QUERY_EXACT_JOBS_IN_PRODUCTION,
variables: { ids: jobIds }

View File

@@ -1,18 +0,0 @@
import { connect } from "react-redux";
import { GlobalOutlined } from "@ant-design/icons";
import { createStructuredSelector } from "reselect";
import React from "react";
import { selectWssStatus } from "../../redux/application/application.selectors";
const mapStateToProps = createStructuredSelector({
//currentUser: selectCurrentUser
wssStatus: selectWssStatus
});
const mapDispatchToProps = (dispatch) => ({
//setUserLanguage: language => dispatch(setUserLanguage(language))
});
export default connect(mapStateToProps, mapDispatchToProps)(WssStatusDisplay);
export function WssStatusDisplay({ wssStatus }) {
console.log("🚀 ~ WssStatusDisplay ~ wssStatus:", wssStatus);
return <GlobalOutlined style={{ color: wssStatus === "connected" ? "green" : "red", marginRight: ".5rem" }} />;
}

View File

@@ -1,88 +1,54 @@
import { useEffect, useState, useRef } from "react";
import { useEffect, useState } from "react";
import SocketIO from "socket.io-client";
import { auth } from "../../firebase/firebase.utils";
import { store } from "../../redux/store";
import { setWssStatus } from "../../redux/application/application.actions";
const useSocket = (bodyshop) => {
const socketRef = useRef(null);
const [clientId, setClientId] = useState(null);
const [socket, setSocket] = useState(null);
const [clientId, setClientId] = useState(null); // State to store unique identifier
useEffect(() => {
const unsubscribe = auth.onIdTokenChanged(async (user) => {
if (user) {
const newToken = await user.getIdToken();
if (bodyshop && bodyshop.id) {
const endpoint = import.meta.env.PROD ? import.meta.env.VITE_APP_AXIOS_BASE_API_URL : "https://localhost:3000";
if (socketRef.current) {
// Send new token to server
socketRef.current.emit("update-token", newToken);
} else if (bodyshop && bodyshop.id) {
// Initialize the socket
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
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
});
const socketInstance = SocketIO(endpoint, {
path: "/wss",
withCredentials: true,
auth: { token: newToken },
reconnectionAttempts: Infinity,
reconnectionDelay: 2000,
reconnectionDelayMax: 10000
});
setSocket(socketInstance);
socketRef.current = socketInstance;
socketInstance.on("connect", () => {
console.log("Socket connected:", socketInstance.id);
setClientId(socketInstance.id);
});
const handleBodyshopMessage = (message) => {
if (!import.meta.env.DEV) return;
console.log(`Received message for bodyshop ${bodyshop.id}:`, message);
};
socketInstance.on("reconnect", (attempt) => {
console.log(`Socket reconnected after ${attempt} attempts`);
});
const handleConnect = () => {
console.log("Socket connected:", socketInstance.id);
socketInstance.emit("join-bodyshop-room", bodyshop.id);
setClientId(socketInstance.id);
store.dispatch(setWssStatus("connected"))
};
socketInstance.on("connect_error", (err) => {
console.error("Socket connection error:", err);
});
const handleReconnect = (attempt) => {
console.log(`Socket reconnected after ${attempt} attempts`);
store.dispatch(setWssStatus("connected"))
};
socketInstance.on("disconnect", () => {
console.log("Socket disconnected");
});
const handleConnectionError = (err) => {
console.error("Socket connection error:", err);
store.dispatch(setWssStatus("error"))
};
const handleDisconnect = () => {
console.log("Socket disconnected");
store.dispatch(setWssStatus("disconnected"))
};
socketInstance.on("connect", handleConnect);
socketInstance.on("reconnect", handleReconnect);
socketInstance.on("connect_error", handleConnectionError);
socketInstance.on("disconnect", handleDisconnect);
socketInstance.on("bodyshop-message", handleBodyshopMessage);
}
} else {
// User is not authenticated
if (socketRef.current) {
socketRef.current.disconnect();
socketRef.current = null;
}
}
});
// Clean up the listener on unmount
return () => {
unsubscribe();
if (socketRef.current) {
socketRef.current.disconnect();
socketRef.current = null;
}
};
return () => {
socketInstance.disconnect();
};
}
}, [bodyshop]);
return { socket: socketRef.current, clientId };
// Return both socket and clientId
return { socket, clientId };
};
export default useSocket;

View File

@@ -1,20 +1,16 @@
import { Button, Card, Col, notification, Row, Select, Space } from "antd";
import React, { useEffect, useRef, useState } from "react";
import React, { useContext, 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({
bodyshop: selectBodyshop
});
const mapStateToProps = createStructuredSelector({});
const mapDispatchToProps = (dispatch) => ({
setBreadcrumbs: (breadcrumbs) => dispatch(setBreadcrumbs(breadcrumbs)),
@@ -23,17 +19,9 @@ const mapDispatchToProps = (dispatch) => ({
export default connect(mapStateToProps, mapDispatchToProps)(DmsContainer);
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 }) {
export function DmsContainer({ setBreadcrumbs, setSelectedHeader }) {
const { t } = useTranslation();
const { socket } = useContext(SocketContext);
const [logLevel, setLogLevel] = useState("DEBUG");
const history = useNavigate();
const [logs, setLogs] = useState([]);
@@ -64,40 +52,43 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
socket.on("connect", () => socket.emit("set-log-level", logLevel));
socket.on("reconnect", () => {
setLogs((logs) => {
return [
if (socket) {
const handleConnect = () => socket.emit("set-log-level", logLevel);
const handleReconnect = () => {
setLogs((logs) => [
...logs,
{
timestamp: new Date(),
level: "WARNING",
message: "Reconnected to CDK Export Service"
message: "Reconnected to DMS Export Service"
}
];
});
});
]);
};
const handleLogEvent = (payload) => {
setLogs((logs) => [...logs, payload]);
};
const handleExportComplete = () => {
notification.open({
type: "success",
message: t("jobs.labels.dms.apexported")
});
};
socket.on("log-event", (payload) => {
setLogs((logs) => {
return [...logs, payload];
});
});
socket.on("connect", handleConnect);
socket.on("reconnect", handleReconnect);
socket.on("log-event", handleLogEvent);
socket.on("ap-export-complete", handleExportComplete);
socket.on("ap-export-complete", (payload) => {
notification.open({
type: "success",
message: t("jobs.labels.dms.apexported")
});
});
if (socket.disconnected) socket.connect();
if (socket.disconnected) socket.connect();
return () => {
socket.removeAllListeners();
socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return () => {
socket.off("connect", handleConnect);
socket.off("reconnect", handleReconnect);
socket.off("log-event", handleLogEvent);
socket.off("ap-export-complete", handleExportComplete);
};
}
}, [socket, logLevel, t]);
if (!state?.billids) {
history(`/manage/accounting/payables`);
@@ -133,27 +124,20 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
<Button
onClick={() => {
setLogs([]);
socket.disconnect();
socket.connect();
if (socket) {
socket.emit("clear-dms-session");
}
}}
>
Reconnect
Clear Session
</Button>
</Space>
}
>
<DmsLogEvents socket={socket} logs={logs} />
<DmsLogEvents logs={logs} />
</Card>
</div>
</Col>
</Row>
);
}
export const determineDmsType = (bodyshop) => {
if (bodyshop.cdk_dealerid) return "cdk";
else {
return "pbs";
}
};

View File

@@ -1,12 +1,11 @@
import { useQuery } from "@apollo/client";
import { Button, Card, Col, notification, Result, Row, Select, Space } from "antd";
import queryString from "query-string";
import React, { useEffect, useRef, useState } from "react";
import React, { useContext, 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";
@@ -14,12 +13,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
@@ -28,25 +27,21 @@ 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([]);
@@ -59,6 +54,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
fetchPolicy: "network-only",
nextFetchPolicy: "network-only"
});
const logsRef = useRef(null);
useEffect(() => {
@@ -83,47 +79,73 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
}, [t, setBreadcrumbs, setSelectedHeader]);
useEffect(() => {
socket.on("connect", () => socket.emit("set-log-level", logLevel));
socket.on("reconnect", () => {
setLogs((logs) => {
return [
if (socket) {
const handleConnect = () => {
socket.emit("set-log-level", logLevel);
};
const handleReconnect = () => {
setLogs((logs) => [
...logs,
{
timestamp: new Date(),
level: "WARNING",
message: "Reconnected to CDK Export Service"
message: "Reconnected to DMS 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");
});
]);
};
if (socket.disconnected) socket.connect();
return () => {
socket.removeAllListeners();
socket.disconnect();
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
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 (loading) return <LoadingSpinner />;
if (error) return <AlertComponent message={error.message} type="error" />;
@@ -183,16 +205,17 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
<Button
onClick={() => {
setLogs([]);
socket.disconnect();
socket.connect();
if (socket) {
socket.emit("clear-dms-session");
}
}}
>
Reconnect
Clear Session
</Button>
</Space>
}
>
<DmsLogEvents socket={socket} logs={logs} />
<DmsLogEvents logs={logs} />
</Card>
</div>
</Col>

View File

@@ -18,14 +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 WssStatusDisplayComponent from "../../components/wss-status-display/wss-status-display.component.jsx";
import SocketContext from "../../contexts/SocketIO/socketContext.jsx";
import { requestForToken } from "../../firebase/firebase.utils.js";
const JobsPage = lazy(() => import("../jobs/jobs.page"));
@@ -133,6 +132,27 @@ 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={
@@ -605,8 +625,7 @@ export function Manage({ conflict, bodyshop }) {
}}
>
<div style={{ display: "flex" }}>
<WssStatusDisplayComponent />
<div onClick={broadcastMessage}>
<div>
{`${InstanceRenderManager({
imex: t("titles.imexonline"),
rome: t("titles.romeonline"),
@@ -615,6 +634,8 @@ 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

@@ -26,7 +26,7 @@ export function ProductionListComponent({ bodyshop }) {
return (
<>
<NoteUpsertModal />
<ProductionListTable bodyshop={bodyshop} subscriptionType={Production_Use_View.treatment} />
<ProductionListTable subscriptionType={Production_Use_View.treatment} />
</>
);
}

View File

@@ -67,7 +67,3 @@ export const setUpdateAvailable = (isUpdateAvailable) => ({
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
payload: isUpdateAvailable
});
export const setWssStatus = (status) => ({
type: ApplicationActionTypes.SET_WSS_STATUS,
payload: status
});

View File

@@ -3,7 +3,6 @@ import ApplicationActionTypes from "./application.types";
const INITIAL_STATE = {
loading: false,
online: true,
wssStatus: "disconnected",
updateAvailable: false,
breadcrumbs: [],
recentItems: [],
@@ -88,9 +87,6 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
case ApplicationActionTypes.SET_PROBLEM_JOBS: {
return { ...state, problemJobs: action.payload };
}
case ApplicationActionTypes.SET_WSS_STATUS: {
return { ...state, wssStatus: action.payload };
}
default:
return state;
}

View File

@@ -22,4 +22,3 @@ export const selectJobReadOnly = createSelector([selectApplication], (applicatio
export const selectOnline = createSelector([selectApplication], (application) => application.online);
export const selectProblemJobs = createSelector([selectApplication], (application) => application.problemJobs);
export const selectUpdateAvailable = createSelector([selectApplication], (application) => application.updateAvailable);
export const selectWssStatus = createSelector([selectApplication], (application) => application.wssStatus);

View File

@@ -12,7 +12,6 @@ const ApplicationActionTypes = {
SET_ONLINE_STATUS: "SET_ONLINE_STATUS",
INSERT_AUDIT_TRAIL: "INSERT_AUDIT_TRAIL",
SET_PROBLEM_JOBS: "SET_PROBLEM_JOBS",
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE",
SET_WSS_STATUS: "SET_WSS_STATUS"
SET_UPDATE_AVAILABLE: "SET_UPDATE_AVAILABLE"
};
export default ApplicationActionTypes;

View File

@@ -242,10 +242,6 @@ export function* signInSuccessSaga({ payload }) {
window.$crisp.push(["set", "user:nickname", [payload.displayName || payload.email]]);
window.$crisp.push(["set", "session:segments", [["user"]]]);
},
rome: () => {
window.$zoho.salesiq.visitor.name(payload.displayName || payload.email);
window.$zoho.salesiq.visitor.email(payload.email);
},
promanager: () => {
Userpilot.identify(payload.email, {
email: payload.email
@@ -375,9 +371,6 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
if (authRecord[0] && authRecord[0].user.validemail) {
window.$crisp.push(["set", "user:email", [authRecord[0].user.email]]);
}
},
rome: () => {
window.$zoho.salesiq.visitor.info({ "Shop Name": payload.shopname });
}
});
} catch (error) {

View File

@@ -9,7 +9,6 @@ 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"
@@ -47,7 +46,6 @@ export const logger = createLogger("info", {
export default defineConfig({
base: "/",
plugins: [
//visualizer(),
ViteEjsPlugin((viteConfig) => ({ env: viteConfig.env })),
VitePWA({
injectRegister: "auto",
@@ -126,12 +124,6 @@ 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,
@@ -168,12 +160,6 @@ 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,
@@ -188,32 +174,7 @@ export default defineConfig({
manualChunks: {
antd: ["antd"],
"react-redux": ["react-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"]
redux: ["redux"]
}
}
}
@@ -223,8 +184,6 @@ export default defineConfig({
"react",
"react-dom",
"antd",
"lodash",
"@sentry/react",
"@apollo/client",
"@reduxjs/toolkit",
"axios",

129
package-lock.json generated
View File

@@ -13,7 +13,6 @@
"@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",
@@ -1767,7 +1766,6 @@
"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"
}
@@ -1776,7 +1774,6 @@
"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",
@@ -1790,7 +1787,6 @@
"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"
}
@@ -1799,7 +1795,6 @@
"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"
}
@@ -1808,7 +1803,6 @@
"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"
}
@@ -1817,7 +1811,6 @@
"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"
}
@@ -2396,20 +2389,6 @@
"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",
@@ -2419,7 +2398,6 @@
"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",
@@ -2463,12 +2441,6 @@
}
}
},
"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",
@@ -2891,12 +2863,6 @@
"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",
@@ -3191,7 +3157,6 @@
"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"
}
@@ -3609,12 +3574,11 @@
"integrity": "sha512-vjAczensTgRcqDERK0SR2XMwsF/tSvnvlv6VcF2GIhg6Sx4yOIt/irsr1RDJsKiIyBzJDpCoXiWWq28MqH2cnQ=="
},
"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",
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"dependencies": {
"ms": "^2.1.3"
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
@@ -3625,6 +3589,11 @@
}
}
},
"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",
@@ -3889,10 +3858,9 @@
"integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ=="
},
"node_modules/encodeurl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz",
"integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==",
"license": "MIT",
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
"integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==",
"engines": {
"node": ">= 0.8"
}
@@ -4127,6 +4095,15 @@
"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",
@@ -4282,6 +4259,15 @@
"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",
@@ -4569,7 +4555,6 @@
"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"
}
@@ -5767,8 +5752,7 @@
"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==",
"license": "MIT"
"integrity": "sha512-TKC/8zH5pXIAMVQio2TvVDTtPRX+DJPHDqjRbxogtFiByHyzKmy96RA0JtCQJ+WouyyL4A10xomQzgbUT+1jCg=="
},
"node_modules/npmlog": {
"version": "5.0.1",
@@ -5811,13 +5795,9 @@
}
},
"node_modules/object-inspect": {
"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"
},
"version": "1.13.1",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
"integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
@@ -6271,10 +6251,6 @@
"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",
@@ -6460,15 +6436,6 @@
"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",
@@ -6496,6 +6463,15 @@
"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",
@@ -6655,6 +6631,23 @@
"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",
@@ -6677,7 +6670,6 @@
"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"
@@ -7431,7 +7423,6 @@
"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,7 +23,6 @@
"@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",

233
server.js
View File

@@ -9,12 +9,6 @@ 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 } = require("@socket.io/admin-ui");
const { isString, isEmpty } = require("lodash");
const applyRedisHelpers = require("./server/utils/redisHelpers");
const applyIOHelpers = require("./server/utils/ioHelpers");
// Load environment variables
require("dotenv").config({
@@ -29,13 +23,12 @@ 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://test.romeonline.io",
"https://www.test.romeonline.io",
"https://beta.test.romeonline.io",
"https://www.beta.test.romeonline.io",
"https://beta.romeonline.io",
"https://www.beta.romeonline.io",
"https://beta.test.imex.online",
@@ -45,13 +38,9 @@ const SOCKETIO_CORS_ORIGIN = [
"https://www.test.promanager.web-est.com",
"https://test.promanager.web-est.com",
"https://www.promanager.web-est.com",
"https://promanager.web-est.com",
"https://www.promanager.web-est.com",
"https://old.imex.online",
"https://www.old.imex.online",
"https://wsadmin.imex.online",
"https://www.wsadmin.imex.online",
"http://localhost:3333",
"https://localhost:3333"
"https://www.old.imex.online"
];
/**
@@ -128,8 +117,8 @@ const applySocketIO = async (server, app) => {
process.exit(0);
});
const ioRedis = new Server(server, {
path: "/wss",
const io = new Server(server, {
path: "/ws",
adapter: createAdapter(pubClient, subClient),
cors: {
origin: SOCKETIO_CORS_ORIGIN,
@@ -139,41 +128,173 @@ const applySocketIO = async (server, app) => {
}
});
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"]
}
});
app.use((req, res, next) => {
Object.assign(req, {
pubClient,
io,
ioRedis
});
req.pubClient = pubClient;
req.io = io;
next();
});
Object.assign(module.exports, { io, pubClient, ioRedis });
Object.assign(module.exports, { io, pubClient });
return { pubClient, io, ioRedis };
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));
});
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();
// }
};
/**
@@ -186,16 +307,11 @@ const main = async () => {
const server = http.createServer(app);
const { pubClient, ioRedis } = await applySocketIO(server, app);
const api = applyRedisHelpers(pubClient, app, logger);
const ioHelpers = applyIOHelpers(app, api, ioRedis, logger);
// Legacy Socket Events
const { pubClient } = await applySocketIO(server, app);
applyRedisHelpers(pubClient, app);
require("./server/web-sockets/web-socket");
applyMiddleware(app);
applyRoutes(app);
redisSocketEvents(ioRedis, api, ioHelpers);
try {
await server.listen(port);
@@ -206,11 +322,4 @@ const main = async () => {
};
// Start server
main().catch((error) => {
logger.log(`Main-API-Error: Something was not caught in the application.`, "error", "api", null, {
error: error.message,
errorjson: JSON.stringify(error)
});
// Note: If we want the app to crash on all uncaught async operations, we would
// need to put a `process.exit(1);` here
});
main();

View File

@@ -12,67 +12,92 @@ 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;
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);
axios.interceptors.request.use(
async (x) => {
const socket = x.socket;
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
const headers = {
...x.headers.common,
...x.headers[x.method],
...x.headers
};
return x;
});
const printable = `${new Date()} | Request: ${x.method.toUpperCase()} | ${
x.url
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
axios.interceptors.response.use((x) => {
const socket = x.config.socket;
console.log(printable);
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
console.log(printable);
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
// Use await properly here for the async operation
await CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
return x;
});
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
}
);
async function PbsCalculateAllocationsAp(socket, billids) {
try {
CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`);
await CdkBase.createLogEvent(socket, "DEBUG", `Received request to calculate allocations for ${billids}`);
const { bills, bodyshops } = await QueryBillData(socket, billids);
const bodyshop = bodyshops[0];
socket.bodyshop = bodyshop;
socket.bills = bills;
await setMultipleSessionData(socket.id, {
bills,
bodyshop
});
const txEnvelope = await getSessionData(socket.id, "txEnvelope");
//Each bill will enter it's own top level transaction.
const transactionlist = [];
if (bills.length === 0) {
CdkBase.createLogEvent(
await 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: socket.bodyshop.pbs_serialnumber,
SerialNumber: 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",
Source: "ImEX Online", //TODO:AIO Resolve this for rome online.
Lines: [] //socket.apAllocations,
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,
}
};
@@ -117,13 +142,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],
@@ -145,7 +170,7 @@ async function PbsCalculateAllocationsAp(socket, billids) {
let APAmount = Dinero();
Object.keys(billHash).map((key) => {
if (billHash[key].Amount.getAmount() > 0 || billHash[key].Amount.getAmount() < 0) {
if (billHash[key].Amount.getAmount() !== 0) {
transactionObject.Posting.Lines.push({
...billHash[key],
Amount: billHash[key].Amount.toFormat("0.00")
@@ -169,19 +194,19 @@ async function PbsCalculateAllocationsAp(socket, billids) {
return transactionlist;
} catch (error) {
CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
await CdkBase.createLogEvent(socket, "ERROR", `Error encountered in PbsCalculateAllocationsAp. ${error}`);
}
}
exports.PbsCalculateAllocationsAp = PbsCalculateAllocationsAp;
async function QueryBillData(socket, billids) {
CdkBase.createLogEvent(socket, "DEBUG", `Querying bill data for id(s) ${billids}`);
await 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 });
CdkBase.createLogEvent(socket, "TRACE", `Bill data query result ${JSON.stringify(result, null, 2)}`);
await CdkBase.createLogEvent(socket, "TRACE", `Bill data query result ${JSON.stringify(result, null, 2)}`);
return result;
}
@@ -196,40 +221,49 @@ function getCostAccount(billline, respcenters) {
}
exports.PbsExportAp = async function (socket, { billids, txEnvelope }) {
CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`);
await CdkBase.createLogEvent(socket, "DEBUG", `Exporting selected AP.`);
//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 apAllocations = await PbsCalculateAllocationsAp(socket, billids);
await setMultipleSessionData(socket.id, {
apAllocations,
txEnvelope
});
for (const allocation of apAllocations) {
const { billid, ...restAllocation } = allocation;
const { data: AccountPostingChange } = await axios.post(PBS_ENDPOINTS.AccountingPostingChange, restAllocation, {
auth: PBS_CREDENTIALS,
socket
});
CheckForErrors(socket, AccountPostingChange);
CheckForErrors(socket, AccountPostingChange).catch((err) =>
console.error(`Error running CheckingForErrors in pbs-ap-allocations`)
);
if (AccountPostingChange.WasSuccessful) {
CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
await CdkBase.createLogEvent(socket, "DEBUG", `Marking bill as exported.`);
await MarkApExported(socket, [billid]);
socket.emit("ap-export-success", billid);
} else {
CdkBase.createLogEvent(socket, "ERROR", `Export was not succesful.`);
await CdkBase.createLogEvent(socket, "ERROR", `Export was not successful.`);
socket.emit("ap-export-failure", {
billid,
error: AccountPostingChange.Message
});
}
}
socket.emit("ap-export-complete");
};
async function MarkApExported(socket, billids) {
CdkBase.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`);
const { bills, bodyshop } = await getMultipleSessionData(socket.id, ["bills", "bodyshop"]);
await CdkBase.createLogEvent(socket, "DEBUG", `Marking bills as exported for id ${billids}`);
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
const result = await client
return await client
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
.request(queries.MARK_BILLS_EXPORTED, {
billids,
@@ -237,13 +271,11 @@ async function MarkApExported(socket, billids) {
exported: true,
exported_at: new Date()
},
logs: socket.bills.map((bill) => ({
bodyshopid: socket.bodyshop.id,
logs: bills.map((bill) => ({
bodyshopid: bodyshop.id,
billid: bill.id,
successful: true,
useremail: socket.user.email
}))
});
return result;
}

View File

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

View File

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

View File

@@ -86,7 +86,7 @@ async function GetCdkMakes(req, cdk_dealerid) {
{}
);
CheckCdkResponseForError(null, soapResponseVehicleSearch);
await 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";
function CheckCdkResponseForError(socket, soapResponse) {
async function CheckCdkResponseForError(socket, soapResponse) {
if (!soapResponse[0]) {
//The response was null, this might be ok, it might not.
CdkBase.createLogEvent(
await CdkBase.createLogEvent(
socket,
"WARNING",
`Warning detected in CDK Response - it appears to be null. Stack: ${new Error().stack}`
@@ -31,23 +31,22 @@ function CheckCdkResponseForError(socket, soapResponse) {
if (Array.isArray(ResultToCheck)) {
ResultToCheck.forEach((result) => checkIndividualResult(socket, result));
} else {
checkIndividualResult(socket, ResultToCheck);
await checkIndividualResult(socket, ResultToCheck);
}
}
exports.CheckCdkResponseForError = CheckCdkResponseForError;
function checkIndividualResult(socket, ResultToCheck) {
async 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.
return;
else {
CdkBase.createLogEvent(
} else {
await CdkBase.createLogEvent(
socket,
"ERROR",
`Error detected in CDK Response - ${JSON.stringify(ResultToCheck, null, 2)}`

View File

@@ -41,9 +41,11 @@ 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,32 +7,21 @@ 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,
ioHelpers: { getBodyshopRoom }
} = req;
try {
// await client.request(queries.INSERT_IOEVENT, {
// event: {
// operationname: operationName,
// time,
// dbevent,
// env,
// variables,
// bodyshopid,
// useremail
// }
// });
ioRedis.to(getBodyshopRoom(bodyshopid)).emit("bodyshop-message", {
operationName,
useremail
await client.request(queries.INSERT_IOEVENT, {
event: {
operationname: operationName,
time,
dbevent,
env,
variables,
bodyshopid,
useremail
}
});
res.sendStatus(200);
} catch (error) {
logger.log("ioevent-error", "trace", user, null, {

View File

@@ -1,32 +0,0 @@
const { isObject } = require("lodash");
const jobUpdated = async (req, res) => {
const { ioRedis, logger, ioHelpers } = req;
if (!req?.body?.event?.data?.new || !isObject(req?.body?.event?.data?.new)) {
logger.log("job-update-error", "ERROR", req.user?.email, null, {
message: `Malformed Job Update request sent from Hasura`,
body: req?.body
});
return res.json({
status: "error",
message: `Malformed Job Update request sent from Hasura`
});
}
logger.log("job-update", "INFO", req.user?.email, null, {
message: `Job updated event received from Hasura`,
jobid: req?.body?.event?.data?.new?.id
});
const updatedJob = req.body.event.data.new;
const bodyshopID = updatedJob.shopid;
// Emit the job-updated event only to the room corresponding to the bodyshop
ioRedis.to(ioHelpers.getBodyshopRoom(bodyshopID)).emit("production-job-updated", updatedJob);
return res.json({ message: "Job updated and event emitted" });
};
module.exports = jobUpdated;

View File

@@ -14,4 +14,3 @@ exports.costing = require("./job-costing").JobCosting;
exports.costingmulti = require("./job-costing").JobCostingMulti;
exports.statustransition = require("./job-status-transition").statustransition;
exports.lifecycle = require("./job-lifecycle");
exports.jobUpdated = require("./job-updated");

View File

@@ -5,7 +5,7 @@ const ppc = require("../ccc/partspricechange");
const { partsScan } = require("../parts-scan/parts-scan");
const eventAuthorizationMiddleware = require("../middleware/eventAuthorizationMIddleware");
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
const { totals, statustransition, totalsSsu, costing, lifecycle, costingmulti, jobUpdated } = require("../job/job");
const { totals, statustransition, totalsSsu, costing, lifecycle, costingmulti } = require("../job/job");
const withUserGraphQLClientMiddleware = require("../middleware/withUserGraphQLClientMiddleware");
router.post("/totals", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, totals);
@@ -16,6 +16,5 @@ router.post("/lifecycle", validateFirebaseIdTokenMiddleware, withUserGraphQLClie
router.post("/costingmulti", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, costingmulti);
router.post("/partsscan", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, partsScan);
router.post("/ppc", validateFirebaseIdTokenMiddleware, withUserGraphQLClientMiddleware, ppc.generatePpc);
router.post("/job-updated", eventAuthorizationMiddleware, jobUpdated);
module.exports = router;

View File

@@ -1,17 +0,0 @@
const applyIOHelpers = (app, api, io, logger) => {
const getBodyshopRoom = (bodyshopID) => `bodyshop-broadcast-room:${bodyshopID}`;
const ioHelpersAPI = {
getBodyshopRoom
};
// Helper middleware
app.use((req, res, next) => {
req.ioHelpers = ioHelpersAPI;
next();
});
return ioHelpersAPI;
};
module.exports = applyIOHelpers;

View File

@@ -1,212 +0,0 @@
const logger = require("./logger");
/**
* Apply Redis helper functions
* @param pubClient
* @param app
*/
const applyRedisHelpers = (pubClient, app, logger) => {
// Store session data in Redis
const setSessionData = async (socketId, key, value) => {
try {
await pubClient.hSet(`socket:${socketId}`, key, JSON.stringify(value)); // Use Redis pubClient
} catch (error) {
logger.log(`Error Setting Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
}
};
// Retrieve session data from Redis
const getSessionData = async (socketId, key) => {
try {
const data = await pubClient.hGet(`socket:${socketId}`, key);
return data ? JSON.parse(data) : null;
} catch (error) {
logger.log(`Error Getting Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
}
};
// Clear session data from Redis
const clearSessionData = async (socketId) => {
try {
await pubClient.del(`socket:${socketId}`);
} catch (error) {
logger.log(`Error Clearing Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
}
};
// Store multiple session data in Redis
const setMultipleSessionData = async (socketId, keyValues) => {
try {
// 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());
} catch (error) {
logger.log(`Error Setting Multiple Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
}
};
// Retrieve multiple session data from Redis
const getMultipleSessionData = async (socketId, keys) => {
try {
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]));
} catch (error) {
logger.log(`Error Getting Multiple Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
}
};
const setMultipleFromArraySessionData = async (socketId, keyValueArray) => {
try {
// 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
} catch (error) {
logger.log(`Error Setting Multiple Session Data for socket ${socketId}: ${error}`, "ERROR", "redis");
}
};
// 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) {
logger.log(`Error adding item to the end of the list for socket ${socketId}: ${error}`, "ERROR", "redis");
}
};
// 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) {
logger.log(`Error adding item to the beginning of the list for socket ${socketId}: ${error}`, "ERROR", "redis");
}
};
// Helper function to clear a list in Redis
const clearList = async (socketId, key) => {
try {
await pubClient.del(`socket:${socketId}:${key}`);
} catch (error) {
logger.log(`Error clearing list for socket ${socketId}: ${error}`, "ERROR", "redis");
}
};
// Add methods to manage room users
const addUserToRoom = async (room, user) => {
try {
await pubClient.sAdd(room, JSON.stringify(user));
} catch (error) {
logger.log(`Error adding user to room ${room}: ${error}`, "ERROR", "redis");
}
};
const removeUserFromRoom = async (room, user) => {
try {
await pubClient.sRem(room, JSON.stringify(user));
} catch (error) {
logger.log(`Error removing user to room ${room}: ${error}`, "ERROR", "redis");
}
};
const getUsersInRoom = async (room) => {
try {
const users = await pubClient.sMembers(room);
return users.map((user) => JSON.parse(user));
} catch (error) {
logger.log(`Error getting users in room ${room}: ${error}`, "ERROR", "redis");
}
};
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

@@ -1,132 +0,0 @@
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 registerUpdateEvents = (socket) => {
socket.on("update-token", async (newToken) => {
try {
socket.user = await admin.auth().verifyIdToken(newToken);
createLogEvent(socket, "INFO", "Token updated successfully");
socket.emit("token-updated", { success: true });
} catch (error) {
createLogEvent(socket, "ERROR", `Token update failed: ${error.message}`);
socket.emit("token-updated", { success: false, error: error.message });
// Optionally disconnect the socket if token verification fails
socket.disconnect();
}
});
};
const redisSocketEvents = (io, { addUserToRoom, getUsersInRoom, removeUserFromRoom }, { getBodyshopRoom }) => {
// Room management and broadcasting events
function registerRoomAndBroadcastEvents(socket) {
socket.on("join-bodyshop-room", async (bodyshopUUID) => {
try {
const room = getBodyshopRoom(bodyshopUUID);
socket.join(room);
await addUserToRoom(room, { uid: socket.user.uid, email: socket.user.email, socket: socket.id });
createLogEvent(socket, "DEBUG", `Client joined bodyshop room: ${room}`);
// Notify all users in the room about the updated user list
const usersInRoom = await getUsersInRoom(room);
io.to(room).emit("room-users-updated", usersInRoom);
} catch (error) {
createLogEvent(socket, "ERROR", `Error joining room: ${error}`);
}
});
socket.on("leave-bodyshop-room", async (bodyshopUUID) => {
try {
const room = getBodyshopRoom(bodyshopUUID);
socket.leave(room);
createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${room}`);
} catch (error) {
createLogEvent(socket, "ERROR", `Error joining room: ${error}`);
}
});
socket.on("get-room-users", async (bodyshopUUID, callback) => {
try {
const usersInRoom = await getUsersInRoom(getBodyshopRoom(bodyshopUUID));
callback(usersInRoom);
} catch (error) {
createLogEvent(socket, "ERROR", `Error getting room: ${error}`);
}
});
socket.on("broadcast-to-bodyshop", async (bodyshopUUID, message) => {
try {
const room = getBodyshopRoom(bodyshopUUID);
io.to(room).emit("bodyshop-message", message);
createLogEvent(socket, "INFO", `Broadcast message to bodyshop ${room}`);
} catch (error) {
createLogEvent(socket, "ERROR", `Error getting room: ${error}`);
}
});
socket.on("disconnect", async () => {
try {
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 room of rooms) {
await removeUserFromRoom(room, { uid: socket.user.uid, email: socket.user.email, socket: socket.id });
}
} catch (error) {
createLogEvent(socket, "ERROR", `Error getting room: ${error}`);
}
});
}
// Register all socket events for a given socket connection
function registerSocketEvents(socket) {
createLogEvent(socket, "DEBUG", `Registering RedisIO Socket Events.`);
// Register room and broadcasting events
registerRoomAndBroadcastEvents(socket);
registerUpdateEvents(socket);
}
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: ${error.message}`));
});
} 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, {
...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,64 +3,68 @@ require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const { io } = require("../../server");
const { io, setSessionData, clearSessionData, getMultipleSessionData, addItemToEndOfList } = 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");
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
// 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)));
});
next(new Error(`Authentication error ${error}`));
}
});
}
io.on("connection", (socket) => {
socket.log_level = "TRACE";
createLogEvent(socket, "DEBUG", `Connected and Authenticated.`);
// 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.`);
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}`
});
// 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.`);
});
}
///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;
// 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}`);
CdkSelectedCustomer(socket, selectedCustomerId);
await setSessionData(socket.id, "selectedCustomerId ", selectedCustomerId);
});
socket.on("cdk-get-makes", async (cdk_dealerid, callback) => {
@@ -68,159 +72,160 @@ io.on("connection", (socket) => {
const makes = await CdkGetMakes(socket, cdk_dealerid);
callback(makes);
} catch (error) {
createLogEvent(socket, "ERROR", `Error in cdk-get-makes WS call. ${JSON.stringify(error, null, 2)}`);
await 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);
createLogEvent(socket, "DEBUG", `Allocations calculated.`);
createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
await createLogEvent(socket, "DEBUG", `Allocations calculated.`);
await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations, null, 2)}`);
callback(allocations);
});
//END CDK
}
//PBS AR
// PBS AR-specific socket events
function registerPbsArEvents(socket) {
socket.on("pbs-calculate-allocations", async (jobid, callback) => {
const allocations = await CdkCalculateAllocations(socket, jobid);
createLogEvent(socket, "DEBUG", `Allocations calculated.`);
createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
await createLogEvent(socket, "DEBUG", `PBS AR allocations calculated.`);
await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations, null, 2)}`);
callback(allocations);
});
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
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);
});
}
// PBS AP-specific socket events
function registerPbsApEvents(socket) {
socket.on("pbs-calculate-allocations-ap", async (billids, callback) => {
const allocations = await PbsCalculateAllocationsAp(socket, billids);
createLogEvent(socket, "DEBUG", `AP Allocations calculated.`);
createLogEvent(socket, "TRACE", `Allocations calculated. ${JSON.stringify(allocations, null, 2)}`);
socket.apAllocations = allocations;
await createLogEvent(socket, "DEBUG", `PBS AP allocations calculated.`);
await createLogEvent(socket, "TRACE", `Allocations details: ${JSON.stringify(allocations, null, 2)}`);
callback(allocations);
await setSessionData(socket.id, "pbs_ap_allocations", allocations);
});
socket.on("pbs-export-ap", ({ billids, txEnvelope }) => {
socket.txEnvelope = txEnvelope;
PbsExportAp(socket, { billids, txEnvelope });
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}`);
});
//END PBS AP
socket.on("disconnect", () => {
createLogEvent(socket, "DEBUG", `User disconnected.`);
socket.on("leave-bodyshop-room", async (bodyshopUUID) => {
socket.leave(bodyshopUUID);
await createLogEvent(socket, "DEBUG", `Client left bodyshop room: ${bodyshopUUID}`);
});
});
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
});
socket.on("broadcast-to-bodyshop", async (bodyshopUUID, message) => {
io.to(bodyshopUUID).emit("bodyshop-message", message);
await createLogEvent(socket, "INFO", `Broadcast message to bodyshop ${bodyshopUUID}`);
});
}
logger.log("ws-log-event", level, socket.user.email, socket.recordid, {
wsmessage: message
});
// 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}`);
});
}
if (socket.logEvents && isArray(socket.logEvents)) {
socket.logEvents.push({
timestamp: new Date(),
level,
message
});
}
// if (level === "ERROR") {
// throw new Error(message);
// }
// 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);
}
}
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
});
async function createJsonEvent(socket, level, message, json) {
const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]);
if (socket.logEvents && isArray(socket.logEvents)) {
socket.logEvents.push({
timestamp: new Date(),
level,
message
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
});
// Use the helper function to append the log event to the Redis list
await addItemToEndOfList(socket.id, "logEvents", logMessage);
}
// 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", {
async function createXmlEvent(socket, xml, message, isError = false) {
const { logLevel, recordid } = await getMultipleSessionData(socket.id, ["log_level", "recordid"]);
if (LogLevelHierarchy(logLevel) >= LogLevelHierarchy("TRACE")) {
const logMessage = {
timestamp: new Date(),
level: isError ? "ERROR" : "TRACE",
message: `${message}: ${xml}`
});
}
};
logger.log(
isError ? "ws-log-event-xml-error" : "ws-log-event-xml",
isError ? "ERROR" : "TRACE",
socket.user.email,
socket.recordid,
{
wsmessage: message,
xml
}
);
// Emit the log message via the socket
socket.emit("log-event", logMessage);
if (socket.logEvents && isArray(socket.logEvents)) {
socket.logEvents.push({
timestamp: new Date(),
level: isError ? "ERROR" : "TRACE",
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 });
}
}
// Log level hierarchy
function LogLevelHierarchy(level) {
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;
}
const levels = { XML: 5, TRACE: 5, DEBUG: 4, INFO: 3, WARNING: 2, ERROR: 1 };
return levels[level] || 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;