Compare commits
112 Commits
feature/IO
...
feature/IO
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89fabf85e1 | ||
|
|
2439755f9e | ||
|
|
763384f05f | ||
|
|
cba2da8da7 | ||
|
|
f3d8aa3438 | ||
|
|
357d916e0a | ||
|
|
6ed12ebe7d | ||
|
|
6703bc025d | ||
|
|
387dac6779 | ||
|
|
6f454dd4cb | ||
|
|
1440a60228 | ||
|
|
f2aa3960aa | ||
|
|
06508f3ad8 | ||
|
|
8d4195b596 | ||
|
|
9e190e7fb7 | ||
|
|
5cbf00b0c8 | ||
|
|
655aeb86fc | ||
|
|
225549275d | ||
|
|
f0717b8b36 | ||
|
|
78771ae750 | ||
|
|
0389908398 | ||
|
|
54bee763df | ||
|
|
1117a94930 | ||
|
|
5fbfb992c7 | ||
|
|
87b3b65f3e | ||
|
|
9970190909 | ||
|
|
8eee371a90 | ||
|
|
ba97b1efef | ||
|
|
8d8887c28e | ||
|
|
3b19432974 | ||
|
|
a14b2340b0 | ||
|
|
624f8e77cb | ||
|
|
fb624c817d | ||
|
|
c2b4b66ed1 | ||
|
|
ffec03ab6c | ||
|
|
552163d7b9 | ||
|
|
db1f59578c | ||
|
|
8ec5831ec5 | ||
|
|
0146ac5b7b | ||
|
|
a603e5c0b8 | ||
|
|
338906e288 | ||
|
|
542997b1a7 | ||
|
|
5fce548666 | ||
|
|
80322caad0 | ||
|
|
56472d24d9 | ||
|
|
db5dcc271d | ||
|
|
73ab02225e | ||
|
|
83a1b7690d | ||
|
|
c9e28b1ed2 | ||
|
|
c25c66d00f | ||
|
|
d319ab49d4 | ||
|
|
a069989ea7 | ||
|
|
8e3aa186cb | ||
|
|
01c55d6277 | ||
|
|
3438907d8d | ||
|
|
ae020b651e | ||
|
|
d22988df15 | ||
|
|
8136a56ad2 | ||
|
|
830f6c0eea | ||
|
|
4c1849289a | ||
|
|
c45a4780e3 | ||
|
|
d4adc4c1aa | ||
|
|
d9e71423f5 | ||
|
|
6cac0f9594 | ||
|
|
2ab4615642 | ||
|
|
dd5961d419 | ||
|
|
8190958ba3 | ||
|
|
77e009f316 | ||
|
|
2b2738a8d1 | ||
|
|
3d10c9da7f | ||
|
|
e82c77d119 | ||
|
|
855a78be05 | ||
|
|
a29e840797 | ||
|
|
1b30c1ab58 | ||
|
|
80f235f12e | ||
|
|
9b67148522 | ||
|
|
6b501e4619 | ||
|
|
42f1d6fa13 | ||
|
|
3f247a9227 | ||
|
|
63b914731b | ||
|
|
23f8f69bbe | ||
|
|
fc3ea2bdf8 | ||
|
|
96e970faf7 | ||
|
|
c133195607 | ||
|
|
d75ea2b1a6 | ||
|
|
ec0fd840e4 | ||
|
|
971a81fc27 | ||
|
|
19050d31f7 | ||
|
|
e605433379 | ||
|
|
b9ebb70b7a | ||
|
|
79ed6f2388 | ||
|
|
785449a986 | ||
|
|
0b7d469e0e | ||
|
|
a57156756e | ||
|
|
c4c30d98d4 | ||
|
|
d4e8803b13 | ||
|
|
1f2786ddec | ||
|
|
e90cda07e4 | ||
|
|
9793daa04c | ||
|
|
117ced8fe7 | ||
|
|
e7909205d1 | ||
|
|
18028a70ab | ||
|
|
eeb8d8d26f | ||
|
|
23659fc412 | ||
|
|
ba65057782 | ||
|
|
60a859cac8 | ||
|
|
d085a9c7c9 | ||
|
|
ec518a0593 | ||
|
|
26836f662a | ||
|
|
6e88faa9d8 | ||
|
|
5b00ded5f6 | ||
|
|
c5b19d8f22 |
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@@ -0,0 +1 @@
|
||||
* text eol=lf
|
||||
24
.platform/hooks/predeploy/00-install-fonts.sh
Normal file
24
.platform/hooks/predeploy/00-install-fonts.sh
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Install required packages
|
||||
dnf install -y fontconfig freetype
|
||||
|
||||
# Move to the /tmp directory for temporary download and extraction
|
||||
cd /tmp
|
||||
|
||||
# Download the Montserrat font zip file
|
||||
wget https://images.imex.online/fonts/montserrat.zip -O montserrat.zip
|
||||
|
||||
# Unzip the downloaded font file
|
||||
unzip montserrat.zip -d montserrat
|
||||
|
||||
# Move the font files to the system fonts directory
|
||||
mv montserrat/montserrat/*.ttf /usr/share/fonts
|
||||
|
||||
# Rebuild the font cache
|
||||
fc-cache -fv
|
||||
|
||||
# Clean up
|
||||
rm -rf /tmp/montserrat /tmp/montserrat.zip
|
||||
|
||||
echo "Montserrat fonts installed and cached successfully."
|
||||
@@ -1 +1,2 @@
|
||||
client_max_body_size 50M;
|
||||
client_max_body_size 50M;
|
||||
client_body_buffer_size 5M;
|
||||
|
||||
1
_reference/localEmailViewer/.gitignore
vendored
Normal file
1
_reference/localEmailViewer/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
node_modules
|
||||
7
_reference/localEmailViewer/README.md
Normal file
7
_reference/localEmailViewer/README.md
Normal file
@@ -0,0 +1,7 @@
|
||||
This will connect to your dockers local stack session and render the email in HTML.
|
||||
|
||||
```shell
|
||||
node index.js
|
||||
```
|
||||
|
||||
http://localhost:3334
|
||||
116
_reference/localEmailViewer/index.js
Normal file
116
_reference/localEmailViewer/index.js
Normal file
@@ -0,0 +1,116 @@
|
||||
// index.js
|
||||
|
||||
import express from 'express';
|
||||
import fetch from 'node-fetch';
|
||||
import {simpleParser} from 'mailparser';
|
||||
|
||||
const app = express();
|
||||
const PORT = 3334;
|
||||
|
||||
app.get('/', async (req, res) => {
|
||||
try {
|
||||
const response = await fetch('http://localhost:4566/_aws/ses');
|
||||
if (!response.ok) {
|
||||
throw new Error('Network response was not ok');
|
||||
}
|
||||
const data = await response.json();
|
||||
const messagesHtml = await parseMessages(data.messages);
|
||||
res.send(renderHtml(messagesHtml));
|
||||
} catch (error) {
|
||||
console.error('Error fetching messages:', error);
|
||||
res.status(500).send('Error fetching messages');
|
||||
}
|
||||
});
|
||||
|
||||
async function parseMessages(messages) {
|
||||
const parsedMessages = await Promise.all(
|
||||
messages.map(async (message, index) => {
|
||||
try {
|
||||
const parsed = await simpleParser(message.RawData);
|
||||
return `
|
||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: lightgray">
|
||||
<div class="shadow-md rounded-lg p-4 mb-6" style="background-color: white">
|
||||
<div class="mb-2">
|
||||
<span class="font-bold text-lg">Message ${index + 1}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">From:</span> ${message.Source}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Region:</span> ${message.Region}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
||||
</div>
|
||||
</div>
|
||||
<div class="prose">
|
||||
${parsed.html || parsed.textAsHtml || 'No HTML content available'}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} catch (error) {
|
||||
console.error('Error parsing email:', error);
|
||||
return `
|
||||
<div class="bg-white shadow-md rounded-lg p-4 mb-6">
|
||||
<div class="mb-2">
|
||||
<span class="font-bold text-lg">Message ${index + 1}</span>
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">From:</span> ${message.Source}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Region:</span> ${message.Region}
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<span class="font-semibold">Timestamp:</span> ${message.Timestamp}
|
||||
</div>
|
||||
<div class="text-red-500">
|
||||
Error parsing email content
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
})
|
||||
);
|
||||
return parsedMessages.join('');
|
||||
}
|
||||
|
||||
function renderHtml(messagesHtml) {
|
||||
return `
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Email Messages Viewer</title>
|
||||
<script src="https://cdn.tailwindcss.com"></script>
|
||||
<style>
|
||||
body {
|
||||
background-color: #f3f4f6;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
.container {
|
||||
max-width: 800px;
|
||||
margin: 50px auto;
|
||||
padding: 20px;
|
||||
}
|
||||
.prose {
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container bg-white shadow-lg rounded-lg p-6">
|
||||
<h1 class="text-2xl font-bold text-center mb-6">Email Messages Viewer</h1>
|
||||
<div id="messages-container">
|
||||
${messagesHtml}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`;
|
||||
}
|
||||
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server is running on http://localhost:${PORT}`);
|
||||
});
|
||||
1214
_reference/localEmailViewer/package-lock.json
generated
Normal file
1214
_reference/localEmailViewer/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
_reference/localEmailViewer/package.json
Normal file
18
_reference/localEmailViewer/package.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"name": "localemailviewer",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"description": "",
|
||||
"dependencies": {
|
||||
"express": "^4.21.1",
|
||||
"mailparser": "^3.7.1",
|
||||
"node-fetch": "^3.3.2"
|
||||
}
|
||||
}
|
||||
@@ -2,8 +2,6 @@ import { ApolloProvider } from "@apollo/client";
|
||||
import { SplitFactoryProvider, SplitSdk } from "@splitsoftware/splitio-react";
|
||||
import { ConfigProvider } from "antd";
|
||||
import enLocale from "antd/es/locale/en_US";
|
||||
import dayjs from "../utils/day";
|
||||
import "dayjs/locale/en";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import GlobalLoadingBar from "../components/global-loading-bar/global-loading-bar.component";
|
||||
@@ -19,8 +17,6 @@ if (import.meta.env.DEV) {
|
||||
Userpilot.initialize("NX-69145f08");
|
||||
}
|
||||
|
||||
dayjs.locale("en");
|
||||
|
||||
const config = {
|
||||
core: {
|
||||
authorizationKey: import.meta.env.VITE_APP_SPLIT_API,
|
||||
|
||||
@@ -45,7 +45,7 @@ export function ChatPopupComponent({ chatVisible, selectedConversation, toggleCh
|
||||
if (fcmToken) {
|
||||
setpollInterval(0);
|
||||
} else {
|
||||
setpollInterval(60000);
|
||||
setpollInterval(90000);
|
||||
}
|
||||
}, [fcmToken]);
|
||||
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { Card, Table, Tag } from "antd";
|
||||
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import dayjs from "../../../utils/day";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
import axios from "axios";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import dayjs from "../../../utils/day";
|
||||
import LoadingSkeleton from "../../loading-skeleton/loading-skeleton.component";
|
||||
import DashboardRefreshRequired from "../refresh-required.component";
|
||||
|
||||
const fortyFiveDaysAgo = () => dayjs().subtract(45, "day").toLocaleString();
|
||||
|
||||
@@ -46,6 +46,11 @@ export default function JobLifecycleDashboardComponent({ data, bodyshop, ...card
|
||||
dataIndex: "humanReadable",
|
||||
key: "humanReadable"
|
||||
},
|
||||
{
|
||||
title: t("job_lifecycle.columns.average_human_readable"),
|
||||
dataIndex: "averageHumanReadable",
|
||||
key: "averageHumanReadable"
|
||||
},
|
||||
{
|
||||
title: t("job_lifecycle.columns.status_count"),
|
||||
key: "statusCount",
|
||||
|
||||
@@ -40,13 +40,11 @@ export function DmsLogEvents({ socket, logs, bodyshop }) {
|
||||
|
||||
function LogLevelHierarchy(level) {
|
||||
switch (level) {
|
||||
case "TRACE":
|
||||
return "pink";
|
||||
case "DEBUG":
|
||||
return "orange";
|
||||
case "INFO":
|
||||
return "blue";
|
||||
case "WARNING":
|
||||
case "WARN":
|
||||
return "yellow";
|
||||
case "ERROR":
|
||||
return "red";
|
||||
|
||||
@@ -8,7 +8,7 @@ import { INSERT_EULA_ACCEPTANCE } from "../../graphql/user.queries";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { acceptEula } from "../../redux/user/user.actions";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
import "./eula.styles.scss";
|
||||
import DateTimePicker from "../form-date-time-picker/form-date-time-picker.component.jsx";
|
||||
@@ -208,7 +208,7 @@ const EulaFormComponent = ({ form, handleChange, onFinish, t }) => (
|
||||
{
|
||||
required: true,
|
||||
validator: (_, value) => {
|
||||
if (day(value).isSame(day(), "day")) {
|
||||
if (dayjs(value).isSame(dayjs(), "day")) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
return Promise.reject(new Error(t("eula.messages.date_accepted")));
|
||||
|
||||
@@ -2,21 +2,38 @@ import { DatePicker } from "antd";
|
||||
import PropTypes from "prop-types";
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors.js";
|
||||
import dayjs from "../../utils/day";
|
||||
import { fuzzyMatchDate } from "./formats.js";
|
||||
|
||||
const DateTimePicker = ({ value, onChange, onBlur, id, onlyFuture, onlyToday, isDateOnly = false, ...restProps }) => {
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
});
|
||||
|
||||
const DateTimePicker = ({
|
||||
value,
|
||||
onChange,
|
||||
onBlur,
|
||||
id,
|
||||
onlyFuture,
|
||||
onlyToday,
|
||||
isDateOnly = false,
|
||||
bodyshop,
|
||||
...restProps
|
||||
}) => {
|
||||
const [isManualInput, setIsManualInput] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleChange = useCallback(
|
||||
(newDate) => {
|
||||
if (onChange) {
|
||||
onChange(newDate || null);
|
||||
onChange(bodyshop?.timezone && newDate ? dayjs(newDate).tz(bodyshop.timezone, true) : newDate);
|
||||
}
|
||||
setIsManualInput(false);
|
||||
},
|
||||
[onChange]
|
||||
[onChange, bodyshop?.timezone]
|
||||
);
|
||||
|
||||
const handleBlur = useCallback(
|
||||
@@ -102,4 +119,4 @@ DateTimePicker.propTypes = {
|
||||
isDateOnly: PropTypes.bool
|
||||
};
|
||||
|
||||
export default React.memo(DateTimePicker);
|
||||
export default connect(mapStateToProps, null)(DateTimePicker);
|
||||
|
||||
@@ -116,18 +116,15 @@ function Header({
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const deleteBetaCookie = () => {
|
||||
const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
|
||||
if (cookieExists) {
|
||||
const domain = window.location.hostname.split(".").slice(-2).join(".");
|
||||
document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
|
||||
console.log(`betaSwitchImex cookie deleted`);
|
||||
} else {
|
||||
console.log(`betaSwitchImex cookie does not exist`);
|
||||
}
|
||||
};
|
||||
|
||||
deleteBetaCookie();
|
||||
// const deleteBetaCookie = () => {
|
||||
// const cookieExists = document.cookie.split("; ").some((row) => row.startsWith(`betaSwitchImex=`));
|
||||
// if (cookieExists) {
|
||||
// const domain = window.location.hostname.split(".").slice(-2).join(".");
|
||||
// document.cookie = `betaSwitchImex=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/; domain=.${domain}`;
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// deleteBetaCookie();
|
||||
|
||||
const accountingChildren = [];
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import ScheduleEventColor from "./schedule-event.color.component";
|
||||
import ScheduleEventNote from "./schedule-event.note.component";
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { UPDATE_APPOINTMENT } from "../../graphql/appointments.queries";
|
||||
import ProductionListColumnComment from "../production-list-columns/production-list-columns.comment.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop
|
||||
@@ -127,6 +128,9 @@ export function ScheduleEventComponent({
|
||||
{(event.job && event.job.alt_transport) || ""}
|
||||
<ScheduleAtChange job={event && event.job} />
|
||||
</DataLabel>
|
||||
<DataLabel label={t("jobs.fields.comment")} valueStyle={{ overflow: "hidden", textOverflow: "ellipsis" }}>
|
||||
<ProductionListColumnComment record={event && event.job} />
|
||||
</DataLabel>
|
||||
<ScheduleEventNote event={event} />
|
||||
</div>
|
||||
) : (
|
||||
@@ -316,6 +320,7 @@ export function ScheduleEventComponent({
|
||||
})`}
|
||||
|
||||
{event.job && event.job.alt_transport && <div style={{ margin: ".1rem" }}>{event.job.alt_transport}</div>}
|
||||
{event?.job?.comment && `C: ${event.job.comment}`}
|
||||
</Space>
|
||||
) : (
|
||||
<div
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useState } from "react";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import axios from "axios";
|
||||
import { Badge, Card, Space, Table, Tag } from "antd";
|
||||
import { gql, useQuery } from "@apollo/client";
|
||||
@@ -72,7 +72,7 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
|
||||
dataIndex: "start",
|
||||
key: "start",
|
||||
render: (text) => DateTimeFormatterFunction(text),
|
||||
sorter: (a, b) => day(a.start).unix() - day(b.start).unix()
|
||||
sorter: (a, b) => dayjs(a.start).unix() - dayjs(b.start).unix()
|
||||
},
|
||||
{
|
||||
title: t("job_lifecycle.columns.relative_start"),
|
||||
@@ -90,7 +90,7 @@ export function JobLifecycleComponent({ job, statuses, ...rest }) {
|
||||
}
|
||||
return isEmpty(a.end) ? 1 : -1;
|
||||
}
|
||||
return day(a.end).unix() - day(b.end).unix();
|
||||
return dayjs(a.end).unix() - dayjs(b.end).unix();
|
||||
},
|
||||
render: (text) => (isEmpty(text) ? t("job_lifecycle.content.not_available") : DateTimeFormatterFunction(text))
|
||||
},
|
||||
|
||||
@@ -2,7 +2,7 @@ import React, { useState } from "react";
|
||||
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Form, notification, Popover, Select, Space } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
@@ -48,7 +48,7 @@ export function JobLineDispatchButton({
|
||||
const result = await dispatchLines({
|
||||
variables: {
|
||||
partsDispatch: {
|
||||
dispatched_at: day(),
|
||||
dispatched_at: dayjs(),
|
||||
employeeid: values.employeeid,
|
||||
jobid: job.id,
|
||||
dispatched_by: currentUser.email,
|
||||
@@ -138,7 +138,11 @@ export function JobLineDispatchButton({
|
||||
|
||||
return (
|
||||
<Popover open={visible} content={popMenu}>
|
||||
<Button disabled={selectedLines.length === 0 || jobRO || disabled} loading={loading} onClick={() => setVisible(true)}>
|
||||
<Button
|
||||
disabled={selectedLines.length === 0 || jobRO || disabled}
|
||||
loading={loading}
|
||||
onClick={() => setVisible(true)}
|
||||
>
|
||||
{t("joblines.actions.dispatchparts", { count: selectedLines.length })}
|
||||
</Button>
|
||||
</Popover>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, Card, Col, notification, Row, Table } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { UPDATE_PARTS_DISPATCH_LINE } from "../../graphql/parts-dispatch.queries";
|
||||
@@ -11,7 +11,7 @@ export default function PartsDispatchExpander({ dispatch, job }) {
|
||||
const [updateDispatchLine] = useMutation(UPDATE_PARTS_DISPATCH_LINE);
|
||||
|
||||
const handleAccept = async ({ partsDispatchLineId }) => {
|
||||
const accepted_at = day();
|
||||
const accepted_at = dayjs();
|
||||
const result = await updateDispatchLine({
|
||||
variables: { id: partsDispatchLineId, line: { accepted_at } },
|
||||
optimisticResponse: {
|
||||
|
||||
@@ -120,14 +120,6 @@ var formats = {
|
||||
};
|
||||
|
||||
const localizer = (dayjsLib) => {
|
||||
// load dayjs plugins
|
||||
dayjsLib.extend(isBetween);
|
||||
dayjsLib.extend(isSameOrAfter);
|
||||
dayjsLib.extend(isSameOrBefore);
|
||||
dayjsLib.extend(localeData);
|
||||
dayjsLib.extend(localizedFormat);
|
||||
dayjsLib.extend(minMax);
|
||||
dayjsLib.extend(utc);
|
||||
var locale = function locale(dj, c) {
|
||||
return c ? dj.locale(c) : dj;
|
||||
};
|
||||
@@ -136,7 +128,8 @@ const localizer = (dayjsLib) => {
|
||||
// then use the timezone aware version
|
||||
|
||||
//TODO This was the issue entirely...
|
||||
// var dayjs = dayjsLib.tz ? dayjsLib.tz : dayjsLib;
|
||||
// var dayjs = dayjsLib.tz ? dayjsLib.tz : dayjsLib;
|
||||
|
||||
var dayjs = dayjsLib;
|
||||
|
||||
function getTimezoneOffset(date) {
|
||||
|
||||
@@ -20,6 +20,7 @@ const mapStateToProps = createStructuredSelector({
|
||||
bodyshop: selectBodyshop,
|
||||
problemJobs: selectProblemJobs
|
||||
});
|
||||
|
||||
const localizer = local(dayjs);
|
||||
|
||||
export function ScheduleCalendarWrapperComponent({
|
||||
|
||||
@@ -4,12 +4,12 @@ import ScoreboardChart from "../scoreboard-chart/scoreboard-chart.component";
|
||||
import ScoreboardLastDays from "../scoreboard-last-days/scoreboard-last-days.component";
|
||||
import ScoreboardTargetsTable from "../scoreboard-targets-table/scoreboard-targets-table.component";
|
||||
|
||||
import { useApolloClient, useQuery } from "@apollo/client";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { GET_BLOCKED_DAYS, QUERY_SCOREBOARD } from "../../graphql/scoreboard.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import dayjs from "../../utils/day";
|
||||
import { useApolloClient, useQuery } from "@apollo/client";
|
||||
import { GET_BLOCKED_DAYS, QUERY_SCOREBOARD } from "../../graphql/scoreboard.queries";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
//currentUser: selectCurrentUser
|
||||
@@ -26,7 +26,7 @@ export function ScoreboardDisplayComponent({ bodyshop }) {
|
||||
start: dayjs().startOf("month"),
|
||||
end: dayjs().endOf("month")
|
||||
},
|
||||
pollInterval: 60000
|
||||
pollInterval: 60000*5
|
||||
});
|
||||
|
||||
const { data } = scoreboardSubscription;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Col, Row } from "antd";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useMemo } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { createStructuredSelector } from "reselect";
|
||||
import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import dayjs from "../../utils/day";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
@@ -86,7 +86,7 @@ export function ScoreboardTimeTicketsStats({ bodyshop }) {
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
pollInterval: 60000,
|
||||
pollInterval: 60000*5,
|
||||
skip: !fixedPeriods
|
||||
});
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { useQuery } from "@apollo/client";
|
||||
import { Col, Row } from "antd";
|
||||
import _ from "lodash";
|
||||
import dayjs from "../../utils/day";
|
||||
import queryString from "query-string";
|
||||
import React, { useMemo } from "react";
|
||||
import { useLocation } from "react-router-dom";
|
||||
import { QUERY_TIME_TICKETS_IN_RANGE_SB } from "../../graphql/timetickets.queries";
|
||||
import dayjs from "../../utils/day";
|
||||
import AlertComponent from "../alert/alert.component";
|
||||
import LoadingSpinner from "../loading-spinner/loading-spinner.component";
|
||||
import * as Utils from "../scoreboard-targets-table/scoreboard-targets-table.util";
|
||||
@@ -68,7 +68,7 @@ export default function ScoreboardTimeTickets() {
|
||||
},
|
||||
fetchPolicy: "network-only",
|
||||
nextFetchPolicy: "network-only",
|
||||
pollInterval: 60000,
|
||||
pollInterval: 60000*5,
|
||||
skip: !fixedPeriods
|
||||
});
|
||||
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import { Button, Card, Space, Switch, Table } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import dayjs from "../../utils/day";
|
||||
import {
|
||||
CheckCircleFilled,
|
||||
CheckCircleOutlined,
|
||||
@@ -15,9 +8,16 @@ import {
|
||||
PlusCircleFilled,
|
||||
SyncOutlined
|
||||
} from "@ant-design/icons";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
|
||||
import { Button, Card, Space, Switch, Table } from "antd";
|
||||
import queryString from "query-string";
|
||||
import React, { useCallback, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
||||
import { setModalContext } from "../../redux/modals/modals.actions";
|
||||
import { pageLimit } from "../../utils/config";
|
||||
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter.jsx";
|
||||
import dayjs from "../../utils/day";
|
||||
|
||||
/**
|
||||
* Task List Component
|
||||
@@ -140,6 +140,17 @@ function TaskListComponent({
|
||||
render: (text, record) => <DateTimeFormatter>{record.created_at}</DateTimeFormatter>
|
||||
});
|
||||
|
||||
columns.push({
|
||||
title: t("tasks.fields.created_by"),
|
||||
dataIndex: "created_by",
|
||||
key: "created_by",
|
||||
width: "8%",
|
||||
defaultSortOrder: "descend",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "created_by" && sortorder,
|
||||
render: (text, record) => record.created_by
|
||||
});
|
||||
|
||||
if (!onlyMine) {
|
||||
columns.push({
|
||||
title: t("tasks.fields.assigned_to"),
|
||||
@@ -155,65 +166,70 @@ function TaskListComponent({
|
||||
});
|
||||
}
|
||||
|
||||
if (showRo) {
|
||||
columns.push({
|
||||
title: t("tasks.fields.job.ro_number"),
|
||||
dataIndex: ["job", "ro_number"],
|
||||
key: "job.ro_number",
|
||||
width: "8%",
|
||||
render: (text, record) =>
|
||||
record.job ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?tab=tasks`}>{record.job.ro_number || t("general.labels.na")}</Link>
|
||||
) : (
|
||||
t("general.labels.na")
|
||||
)
|
||||
});
|
||||
}
|
||||
columns.push({
|
||||
title: t("tasks.fields.related_items"),
|
||||
key: "related_items",
|
||||
width: "12%",
|
||||
render: (text, record) => {
|
||||
const items = [];
|
||||
|
||||
// Job
|
||||
if (showRo && record.job) {
|
||||
items.push(
|
||||
<Link key="job" to={`/manage/jobs/${record.job.id}?tab=tasks`}>
|
||||
{t("tasks.fields.job.ro_number")}: {record.job.ro_number}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
if (showRo && !record.job) {
|
||||
items.push(`${t("tasks.fields.job.ro_number")}: ${t("general.labels.na")}`);
|
||||
}
|
||||
|
||||
// Jobline
|
||||
if (record.jobline?.line_desc) {
|
||||
items.push(
|
||||
<span key="jobline">
|
||||
{t("tasks.fields.jobline")}: {record.jobline.line_desc}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
// Parts Order
|
||||
if (record.parts_order) {
|
||||
const { order_number, vendor } = record.parts_order;
|
||||
const partsOrderText =
|
||||
order_number && vendor?.name ? `${order_number} - ${vendor.name}` : t("general.labels.na");
|
||||
items.push(
|
||||
<Link
|
||||
key="parts_order"
|
||||
to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}
|
||||
>
|
||||
{t("tasks.fields.parts_order")}: {partsOrderText}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
// Bill
|
||||
if (record.bill) {
|
||||
const { invoice_number, vendor } = record.bill;
|
||||
const billText = invoice_number && vendor?.name ? `${invoice_number} - ${vendor.name}` : t("general.labels.na");
|
||||
items.push(
|
||||
<Link key="bill" to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
|
||||
{t("tasks.fields.bill")}: {billText}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return items.length > 0 ? <Space direction="vertical">{items}</Space> : null;
|
||||
}
|
||||
});
|
||||
|
||||
columns.push(
|
||||
{
|
||||
title: t("tasks.fields.jobline"),
|
||||
dataIndex: ["jobline", "id"],
|
||||
key: "jobline.id",
|
||||
width: "8%",
|
||||
render: (text, record) => record?.jobline?.line_desc || ""
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.parts_order"),
|
||||
dataIndex: ["parts_order", "id"],
|
||||
key: "part_order.id",
|
||||
width: "8%",
|
||||
render: (text, record) =>
|
||||
record.parts_order ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?partsorderid=${record.parts_order.id}&tab=partssublet`}>
|
||||
{record.parts_order.order_number && record.parts_order.vendor && record.parts_order.vendor.name
|
||||
? `${record.parts_order.order_number} - ${record.parts_order.vendor.name}`
|
||||
: t("general.labels.na")}
|
||||
</Link>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.bill"),
|
||||
dataIndex: ["bill", "id"],
|
||||
key: "bill.id",
|
||||
width: "10%",
|
||||
render: (text, record) =>
|
||||
record.bill ? (
|
||||
<Link to={`/manage/jobs/${record.job.id}?billid=${record.bill.id}&tab=partssublet`}>
|
||||
{record.bill.invoice_number && record.bill.vendor && record.bill.vendor.name
|
||||
? `${record.bill.invoice_number} - ${record.bill.vendor.name}`
|
||||
: t("general.labels.na")}
|
||||
</Link>
|
||||
) : (
|
||||
""
|
||||
)
|
||||
},
|
||||
{
|
||||
title: t("tasks.fields.title"),
|
||||
dataIndex: "title",
|
||||
key: "title",
|
||||
minWidth: "20%",
|
||||
sorter: true,
|
||||
sortOrder: sortcolumn === "title" && sortorder
|
||||
},
|
||||
@@ -247,7 +263,7 @@ function TaskListComponent({
|
||||
{
|
||||
title: t("tasks.fields.actions"),
|
||||
key: "toggleCompleted",
|
||||
width: "5%",
|
||||
width: "8%",
|
||||
render: (text, record) => (
|
||||
<Space direction="horizontal">
|
||||
<Button
|
||||
|
||||
@@ -39,7 +39,7 @@ export function TimeTicketList({
|
||||
extra
|
||||
}) {
|
||||
const [state, setState] = useState({
|
||||
sortedInfo: {},
|
||||
sortedInfo: { columnKey: 'date', order: 'descend' },
|
||||
filteredInfo: { text: "" }
|
||||
});
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { Button, Form, Modal, notification, Space } from "antd";
|
||||
import { PageHeader } from "@ant-design/pro-layout";
|
||||
import dayjs from "../../utils/day";
|
||||
import { useMutation, useQuery } from "@apollo/client";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import { Button, Form, Modal, notification, Space } from "antd";
|
||||
import React, { useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -11,9 +11,9 @@ import { INSERT_NEW_TIME_TICKET, UPDATE_TIME_TICKET } from "../../graphql/timeti
|
||||
import { toggleModalVisible } from "../../redux/modals/modals.actions";
|
||||
import { selectTimeTicket } from "../../redux/modals/modals.selectors";
|
||||
import { selectBodyshop } from "../../redux/user/user.selectors";
|
||||
import TimeTicketModalComponent from "./time-ticket-modal.component";
|
||||
import dayjs from "../../utils/day";
|
||||
import TimeTicketsCommitToggleComponent from "../time-tickets-commit-toggle/time-tickets-commit-toggle.component";
|
||||
import { useSplitTreatments } from "@splitsoftware/splitio-react";
|
||||
import TimeTicketModalComponent from "./time-ticket-modal.component";
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
timeTicketModal: selectTimeTicket,
|
||||
@@ -87,7 +87,7 @@ export function TimeTicketModalContainer({ timeTicketModal, toggleModalVisible,
|
||||
if (enterAgain) {
|
||||
//Capture the existing information and repopulate it.
|
||||
|
||||
const prev = form.getFieldsValue(["date", "employeeid"]);
|
||||
const prev = form.getFieldsValue(["date", "employeeid", "flat_rate"]);
|
||||
|
||||
form.resetFields();
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -29,7 +29,7 @@ export function TimeTicketsCommit({ bodyshop, currentUser, timeticket, disabled,
|
||||
? { commited_by: null, committed_at: null }
|
||||
: {
|
||||
commited_by: currentUser.email,
|
||||
committed_at: day()
|
||||
committed_at: dayjs()
|
||||
};
|
||||
|
||||
const result = await updateTimeTicket({
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useMutation } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -34,7 +34,7 @@ export function TimeTicketsCommit({
|
||||
timeticketIds: timetickets.map((ticket) => ticket.id),
|
||||
timeticket: {
|
||||
commited_by: currentUser.email,
|
||||
committed_at: day()
|
||||
committed_at: dayjs()
|
||||
}
|
||||
},
|
||||
update(cache) {
|
||||
@@ -47,7 +47,7 @@ export function TimeTicketsCommit({
|
||||
return {
|
||||
...ticket,
|
||||
commited_by: currentUser.email,
|
||||
committed_at: day()
|
||||
committed_at: dayjs()
|
||||
};
|
||||
}
|
||||
return ticket;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useApolloClient } from "@apollo/client";
|
||||
import { Button, notification } from "antd";
|
||||
import _ from "lodash";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import React, { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { connect } from "react-redux";
|
||||
@@ -49,7 +49,7 @@ export function TtApproveButton({
|
||||
})),
|
||||
approvalIds: selectedTickets,
|
||||
approvalUpdate: {
|
||||
approved_at: day(),
|
||||
approved_at: dayjs(),
|
||||
approved_by: currentUser.email
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { useEffect, useState, useRef } from "react";
|
||||
import { useEffect, useRef, 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";
|
||||
import { addAlerts, setWssStatus } from "../../redux/application/application.actions";
|
||||
|
||||
const useSocket = (bodyshop) => {
|
||||
const socketRef = useRef(null);
|
||||
const [clientId, setClientId] = useState(null);
|
||||
@@ -31,6 +32,14 @@ const useSocket = (bodyshop) => {
|
||||
socketRef.current = socketInstance;
|
||||
|
||||
const handleBodyshopMessage = (message) => {
|
||||
if (!message || !message?.type) return;
|
||||
|
||||
switch (message.type) {
|
||||
case "alert-update":
|
||||
store.dispatch(addAlerts(message.payload));
|
||||
break;
|
||||
}
|
||||
|
||||
if (!import.meta.env.DEV) return;
|
||||
console.log(`Received message for bodyshop ${bodyshop.id}:`, message);
|
||||
};
|
||||
@@ -39,22 +48,22 @@ const useSocket = (bodyshop) => {
|
||||
console.log("Socket connected:", socketInstance.id);
|
||||
socketInstance.emit("join-bodyshop-room", bodyshop.id);
|
||||
setClientId(socketInstance.id);
|
||||
store.dispatch(setWssStatus("connected"))
|
||||
store.dispatch(setWssStatus("connected"));
|
||||
};
|
||||
|
||||
const handleReconnect = (attempt) => {
|
||||
console.log(`Socket reconnected after ${attempt} attempts`);
|
||||
store.dispatch(setWssStatus("connected"))
|
||||
store.dispatch(setWssStatus("connected"));
|
||||
};
|
||||
|
||||
const handleConnectionError = (err) => {
|
||||
console.error("Socket connection error:", err);
|
||||
store.dispatch(setWssStatus("error"))
|
||||
store.dispatch(setWssStatus("error"));
|
||||
};
|
||||
|
||||
const handleDisconnect = () => {
|
||||
console.log("Socket disconnected");
|
||||
store.dispatch(setWssStatus("disconnected"))
|
||||
store.dispatch(setWssStatus("disconnected"));
|
||||
};
|
||||
|
||||
socketInstance.on("connect", handleConnect);
|
||||
|
||||
@@ -48,6 +48,7 @@ export const QUERY_ALL_ACTIVE_APPOINTMENTS = gql`
|
||||
v_model_desc
|
||||
est_ct_fn
|
||||
est_ct_ln
|
||||
comment
|
||||
labhrs: joblines_aggregate(where: { mod_lbr_ty: { _neq: "LAR" }, removed: { _eq: false } }) {
|
||||
aggregate {
|
||||
sum {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { gql } from "@apollo/client";
|
||||
|
||||
export const QUERY_TICKETS_BY_JOBID = gql`
|
||||
query QUERY_TICKETS_BY_JOBID($jobid: uuid!) {
|
||||
timetickets(where: { jobid: { _eq: $jobid } }, order_by: { date: desc_nulls_first }) {
|
||||
timetickets(where: { jobid: { _eq: $jobid } }) {
|
||||
actualhrs
|
||||
cost_center
|
||||
ciecacode
|
||||
@@ -26,7 +26,7 @@ export const QUERY_TICKETS_BY_JOBID = gql`
|
||||
|
||||
export const QUERY_TIME_TICKETS_IN_RANGE = gql`
|
||||
query QUERY_TIME_TICKETS_IN_RANGE($start: date!, $end: date!) {
|
||||
timetickets(where: { date: { _gte: $start, _lte: $end } }, order_by: { date: desc_nulls_first }) {
|
||||
timetickets(where: { date: { _gte: $start, _lte: $end } }) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
clockoff
|
||||
@@ -69,7 +69,6 @@ export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
|
||||
) {
|
||||
timetickets(
|
||||
where: { date: { _gte: $start, _lte: $end }, employeeid: { _eq: $employeeid } }
|
||||
order_by: { date: desc_nulls_first }
|
||||
) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
@@ -101,7 +100,6 @@ export const QUERY_TIME_TICKETS_TECHNICIAN_IN_RANGE = gql`
|
||||
}
|
||||
fixedperiod: timetickets(
|
||||
where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, employeeid: { _eq: $employeeid } }
|
||||
order_by: { date: desc_nulls_first }
|
||||
) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
@@ -145,7 +143,6 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
|
||||
) {
|
||||
timetickets(
|
||||
where: { date: { _gte: $start, _lte: $end }, cost_center: { _neq: "timetickets.labels.shift" } }
|
||||
order_by: { date: desc_nulls_first }
|
||||
) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
@@ -180,7 +177,6 @@ export const QUERY_TIME_TICKETS_IN_RANGE_SB = gql`
|
||||
}
|
||||
fixedperiod: timetickets(
|
||||
where: { date: { _gte: $fixedStart, _lte: $fixedEnd }, cost_center: { _neq: "timetickets.labels.shift" } }
|
||||
order_by: { date: desc_nulls_first }
|
||||
) {
|
||||
actualhrs
|
||||
ciecacode
|
||||
@@ -335,7 +331,6 @@ export const UPDATE_TIME_TICKETS = gql`
|
||||
export const QUERY_ACTIVE_TIME_TICKETS = gql`
|
||||
query QUERY_ACTIVE_TIME_TICKETS($employeeId: uuid) {
|
||||
timetickets(
|
||||
order_by: { date: desc_nulls_first }
|
||||
where: {
|
||||
_and: {
|
||||
clockoff: { _is_null: true }
|
||||
|
||||
@@ -71,7 +71,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
...logs,
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "WARNING",
|
||||
level: "WARN",
|
||||
message: "Reconnected to CDK Export Service"
|
||||
}
|
||||
];
|
||||
@@ -123,10 +123,9 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader }) {
|
||||
socket.emit("set-log-level", value);
|
||||
}}
|
||||
>
|
||||
<Select.Option key="TRACE">TRACE</Select.Option>
|
||||
<Select.Option key="DEBUG">DEBUG</Select.Option>
|
||||
<Select.Option key="INFO">INFO</Select.Option>
|
||||
<Select.Option key="WARNING">WARNING</Select.Option>
|
||||
<Select.Option key="WARN">WARN</Select.Option>
|
||||
<Select.Option key="ERROR">ERROR</Select.Option>
|
||||
</Select>
|
||||
<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
||||
|
||||
@@ -90,7 +90,7 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
...logs,
|
||||
{
|
||||
timestamp: new Date(),
|
||||
level: "WARNING",
|
||||
level: "warn",
|
||||
message: "Reconnected to CDK Export Service"
|
||||
}
|
||||
];
|
||||
@@ -173,10 +173,9 @@ export function DmsContainer({ bodyshop, setBreadcrumbs, setSelectedHeader, inse
|
||||
socket.emit("set-log-level", value);
|
||||
}}
|
||||
>
|
||||
<Select.Option key="TRACE">TRACE</Select.Option>
|
||||
<Select.Option key="DEBUG">DEBUG</Select.Option>
|
||||
<Select.Option key="INFO">INFO</Select.Option>
|
||||
<Select.Option key="WARNING">WARNING</Select.Option>
|
||||
<Select.Option key="WARN">WARN</Select.Option>
|
||||
<Select.Option key="ERROR">ERROR</Select.Option>
|
||||
</Select>
|
||||
<Button onClick={() => setLogs([])}>Clear Logs</Button>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FloatButton, Layout, Spin } from "antd";
|
||||
import { FloatButton, Layout, notification, Spin } from "antd";
|
||||
// import preval from "preval.macro";
|
||||
import React, { lazy, Suspense, useContext, useEffect, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
@@ -21,11 +21,12 @@ import ShopSubStatusComponent from "../../components/shop-sub-status/shop-sub-st
|
||||
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 { selectAlerts } from "../../redux/application/application.selectors.js";
|
||||
import { addAlerts } from "../../redux/application/application.actions.js";
|
||||
|
||||
const JobsPage = lazy(() => import("../jobs/jobs.page"));
|
||||
|
||||
@@ -104,16 +105,80 @@ const { Content, Footer } = Layout;
|
||||
|
||||
const mapStateToProps = createStructuredSelector({
|
||||
conflict: selectInstanceConflict,
|
||||
bodyshop: selectBodyshop
|
||||
bodyshop: selectBodyshop,
|
||||
alerts: selectAlerts
|
||||
});
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({});
|
||||
const ALERT_FILE_URL = InstanceRenderManager({
|
||||
imex: "https://images.imex.online/alerts/alerts-imex.json",
|
||||
rome: "https://images.imex.online/alerts/alerts-rome.json"
|
||||
});
|
||||
|
||||
export function Manage({ conflict, bodyshop }) {
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
setAlerts: (alerts) => dispatch(addAlerts(alerts))
|
||||
});
|
||||
|
||||
export function Manage({ conflict, bodyshop, alerts, setAlerts }) {
|
||||
const { t } = useTranslation();
|
||||
const [chatVisible] = useState(false);
|
||||
const { socket, clientId } = useContext(SocketContext);
|
||||
|
||||
// State to track displayed alerts
|
||||
const [displayedAlertIds, setDisplayedAlertIds] = useState([]);
|
||||
|
||||
// Fetch displayed alerts from localStorage on mount
|
||||
useEffect(() => {
|
||||
const displayedAlerts = JSON.parse(localStorage.getItem("displayedAlerts") || "[]");
|
||||
setDisplayedAlertIds(displayedAlerts);
|
||||
}, []);
|
||||
|
||||
// Fetch alerts from the JSON file and dispatch to Redux store
|
||||
useEffect(() => {
|
||||
const fetchAlerts = async () => {
|
||||
try {
|
||||
const response = await fetch(ALERT_FILE_URL);
|
||||
const fetchedAlerts = await response.json();
|
||||
setAlerts(fetchedAlerts);
|
||||
} catch (error) {
|
||||
console.error("Error fetching alerts:", error);
|
||||
}
|
||||
};
|
||||
|
||||
fetchAlerts();
|
||||
}, []);
|
||||
|
||||
// Use useEffect to watch for new alerts
|
||||
useEffect(() => {
|
||||
if (alerts && Object.keys(alerts).length > 0) {
|
||||
// Convert the alerts object into an array
|
||||
const alertArray = Object.values(alerts);
|
||||
|
||||
// Filter out alerts that have already been dismissed
|
||||
const newAlerts = alertArray.filter((alert) => !displayedAlertIds.includes(alert.id));
|
||||
|
||||
newAlerts.forEach((alert) => {
|
||||
// Display the notification
|
||||
notification.open({
|
||||
key: "notification-alerts-" + alert.id,
|
||||
message: alert.message,
|
||||
description: alert.description,
|
||||
type: alert.type || "info",
|
||||
duration: 0,
|
||||
placement: "bottomRight",
|
||||
closable: true,
|
||||
onClose: () => {
|
||||
// When the notification is closed, update displayed alerts state and localStorage
|
||||
setDisplayedAlertIds((prevIds) => {
|
||||
const updatedIds = [...prevIds, alert.id];
|
||||
localStorage.setItem("displayedAlerts", JSON.stringify(updatedIds));
|
||||
return updatedIds;
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}, [alerts, displayedAlertIds]);
|
||||
|
||||
useEffect(() => {
|
||||
const widgetId = InstanceRenderManager({
|
||||
imex: "IABVNO4scRKY11XBQkNr",
|
||||
|
||||
@@ -67,6 +67,12 @@ export const setUpdateAvailable = (isUpdateAvailable) => ({
|
||||
type: ApplicationActionTypes.SET_UPDATE_AVAILABLE,
|
||||
payload: isUpdateAvailable
|
||||
});
|
||||
|
||||
export const addAlerts = (alerts) => ({
|
||||
type: ApplicationActionTypes.ADD_ALERTS,
|
||||
payload: alerts
|
||||
});
|
||||
|
||||
export const setWssStatus = (status) => ({
|
||||
type: ApplicationActionTypes.SET_WSS_STATUS,
|
||||
payload: status
|
||||
|
||||
@@ -15,7 +15,8 @@ const INITIAL_STATE = {
|
||||
error: null
|
||||
},
|
||||
jobReadOnly: false,
|
||||
partnerVersion: null
|
||||
partnerVersion: null,
|
||||
alerts: {}
|
||||
};
|
||||
|
||||
const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
@@ -91,6 +92,18 @@ const applicationReducer = (state = INITIAL_STATE, action) => {
|
||||
case ApplicationActionTypes.SET_WSS_STATUS: {
|
||||
return { ...state, wssStatus: action.payload };
|
||||
}
|
||||
|
||||
case ApplicationActionTypes.ADD_ALERTS: {
|
||||
const newAlertsMap = { ...state.alerts };
|
||||
|
||||
action.payload.alerts.forEach((alert) => {
|
||||
newAlertsMap[alert.id] = alert;
|
||||
});
|
||||
return {
|
||||
...state,
|
||||
alerts: newAlertsMap
|
||||
};
|
||||
}
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -23,3 +23,4 @@ export const selectOnline = createSelector([selectApplication], (application) =>
|
||||
export const selectProblemJobs = createSelector([selectApplication], (application) => application.problemJobs);
|
||||
export const selectUpdateAvailable = createSelector([selectApplication], (application) => application.updateAvailable);
|
||||
export const selectWssStatus = createSelector([selectApplication], (application) => application.wssStatus);
|
||||
export const selectAlerts = createSelector([selectApplication], (application) => application.alerts);
|
||||
|
||||
@@ -13,6 +13,7 @@ const ApplicationActionTypes = {
|
||||
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_WSS_STATUS: "SET_WSS_STATUS",
|
||||
ADD_ALERTS: "ADD_ALERTS"
|
||||
};
|
||||
export default ApplicationActionTypes;
|
||||
|
||||
@@ -28,7 +28,7 @@ import {
|
||||
} from "../../firebase/firebase.utils";
|
||||
import { QUERY_EULA } from "../../graphql/bodyshop.queries";
|
||||
import client from "../../utils/GraphQLClient";
|
||||
import day from "../../utils/day";
|
||||
import dayjs from "../../utils/day";
|
||||
import InstanceRenderManager from "../../utils/instanceRenderMgr";
|
||||
import {
|
||||
checkInstanceId,
|
||||
@@ -96,7 +96,7 @@ export function* isUserAuthenticated() {
|
||||
const eulaQuery = yield client.query({
|
||||
query: QUERY_EULA,
|
||||
variables: {
|
||||
now: day()
|
||||
now: dayjs()
|
||||
}
|
||||
});
|
||||
|
||||
@@ -314,8 +314,7 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
|
||||
try {
|
||||
const userEmail = yield select((state) => state.user.currentUser.email);
|
||||
try {
|
||||
console.log("Setting shop timezone.");
|
||||
day.tz.setDefault(payload.timezone);
|
||||
dayjs.tz.setDefault(payload.timezone);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
@@ -1338,6 +1338,8 @@
|
||||
},
|
||||
"job_lifecycle": {
|
||||
"columns": {
|
||||
"average_human_readable": "Average Human Readable",
|
||||
"average_value": "Average Value",
|
||||
"duration": "Duration",
|
||||
"end": "End",
|
||||
"human_readable": "Human Readable",
|
||||
@@ -3187,6 +3189,7 @@
|
||||
"billid": "Bill",
|
||||
"completed": "Completed",
|
||||
"created_at": "Created At",
|
||||
"created_by": "Created By",
|
||||
"description": "Description",
|
||||
"due_date": "Due Date",
|
||||
"job": {
|
||||
@@ -3203,6 +3206,7 @@
|
||||
"medium": "Medium"
|
||||
},
|
||||
"priority": "Priority",
|
||||
"related_items": "Related Items",
|
||||
"remind_at": "Remind At",
|
||||
"title": "Title"
|
||||
},
|
||||
|
||||
@@ -1338,6 +1338,8 @@
|
||||
},
|
||||
"job_lifecycle": {
|
||||
"columns": {
|
||||
"average_human_readable": "",
|
||||
"average_value": "",
|
||||
"duration": "",
|
||||
"end": "",
|
||||
"human_readable": "",
|
||||
@@ -3187,6 +3189,7 @@
|
||||
"billid": "",
|
||||
"completed": "",
|
||||
"created_at": "",
|
||||
"created_by": "",
|
||||
"description": "",
|
||||
"due_date": "",
|
||||
"job": {
|
||||
@@ -3203,6 +3206,7 @@
|
||||
"medium": ""
|
||||
},
|
||||
"priority": "",
|
||||
"related_items": "",
|
||||
"remind_at": "",
|
||||
"title": ""
|
||||
},
|
||||
|
||||
@@ -1338,6 +1338,8 @@
|
||||
},
|
||||
"job_lifecycle": {
|
||||
"columns": {
|
||||
"average_human_readable": "",
|
||||
"average_value": "",
|
||||
"duration": "",
|
||||
"end": "",
|
||||
"human_readable": "",
|
||||
@@ -3187,6 +3189,7 @@
|
||||
"billid": "",
|
||||
"completed": "",
|
||||
"created_at": "",
|
||||
"created_by": "",
|
||||
"description": "",
|
||||
"due_date": "",
|
||||
"job": {
|
||||
@@ -3203,6 +3206,7 @@
|
||||
"medium": ""
|
||||
},
|
||||
"priority": "",
|
||||
"related_items": "",
|
||||
"remind_at": "",
|
||||
"title": ""
|
||||
},
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import dayjs from "dayjs";
|
||||
|
||||
import "dayjs/locale/en";
|
||||
import dayjsBusinessDays from "dayjs-business-days2";
|
||||
import isSameOrAfter from "dayjs/plugin/isSameOrAfter";
|
||||
import updateLocale from "dayjs/plugin/updateLocale";
|
||||
@@ -64,4 +65,6 @@ dayjs.extend(minMax);
|
||||
dayjs.extend(isBetween);
|
||||
dayjs.extend(dayjsBusinessDays);
|
||||
|
||||
dayjs.locale("en");
|
||||
|
||||
export default dayjs;
|
||||
|
||||
@@ -74,7 +74,7 @@ services:
|
||||
volumes:
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
environment:
|
||||
- SERVICES=ses,secretsmanager,cloudwatch,logs
|
||||
- SERVICES=s3,ses,secretsmanager,cloudwatch,logs
|
||||
- DEBUG=0
|
||||
- AWS_ACCESS_KEY_ID=test
|
||||
- AWS_SECRET_ACCESS_KEY=test
|
||||
@@ -115,7 +115,8 @@ services:
|
||||
aws --endpoint-url=http://localstack:4566 ses verify-domain-identity --domain imex.online --region ca-central-1
|
||||
aws --endpoint-url=http://localstack:4566 ses verify-email-identity --email-address noreply@imex.online --region ca-central-1
|
||||
aws --endpoint-url=http://localstack:4566 secretsmanager create-secret --name CHATTER_PRIVATE_KEY --secret-string file:///tmp/certs/id_rsa
|
||||
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
||||
aws --endpoint-url=http://localstack:4566 logs create-log-group --log-group-name development --region ca-central-1
|
||||
aws --endpoint-url=http://localstack:4566 s3api create-bucket --bucket imex-large-log --create-bucket-configuration LocationConstraint=ca-central-1
|
||||
"
|
||||
# Node App: The Main IMEX API
|
||||
node-app:
|
||||
@@ -167,6 +168,28 @@ services:
|
||||
# volumes:
|
||||
# - redis-insight-data:/db
|
||||
|
||||
# ##Optional Container for SFTP/SSH Server for testing
|
||||
# ssh-sftp-server:
|
||||
# image: atmoz/sftp:alpine # Using an image with SFTP support
|
||||
# container_name: ssh-sftp-server
|
||||
# hostname: ssh-sftp-server
|
||||
# networks:
|
||||
# - redis-cluster-net
|
||||
# ports:
|
||||
# - "2222:22" # Expose port 22 for SSH/SFTP (mapped to 2222 on the host)
|
||||
# volumes:
|
||||
# - ./certs/id_rsa.pub:/home/user/.ssh/keys/id_rsa.pub:ro # Mount the SSH public key
|
||||
# - ./upload:/home/user/upload # Mount a local directory for SFTP uploads
|
||||
# environment:
|
||||
# - SFTP_USERS=user:password:1000::upload
|
||||
# command: >
|
||||
# /bin/sh -c "
|
||||
# chmod -R 007 /home/user/upload &&
|
||||
# echo 'Match User user' >> /etc/ssh/sshd_config &&
|
||||
# sed -i -e 's#ForceCommand internal-sftp#ForceCommand internal-sftp -d /upload#' /etc/ssh/sshd_config &&
|
||||
# /usr/sbin/sshd -D
|
||||
# "
|
||||
|
||||
networks:
|
||||
redis-cluster-net:
|
||||
driver: bridge
|
||||
|
||||
@@ -69,7 +69,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
jobline:
|
||||
job:
|
||||
@@ -180,7 +179,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -387,7 +385,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -504,7 +501,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bill:
|
||||
job:
|
||||
@@ -671,7 +667,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
_and:
|
||||
- job:
|
||||
@@ -1285,7 +1280,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
courtesycar:
|
||||
bodyshop:
|
||||
@@ -1526,7 +1520,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -1786,7 +1779,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -1920,7 +1912,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
_or:
|
||||
- job:
|
||||
@@ -2105,7 +2096,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
employee_team:
|
||||
bodyshop:
|
||||
@@ -2268,7 +2258,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
employee:
|
||||
bodyshop:
|
||||
@@ -2449,7 +2438,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -2696,7 +2684,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -2808,7 +2795,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
conversation:
|
||||
bodyshop:
|
||||
@@ -3123,7 +3109,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
@@ -4232,7 +4217,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -4248,41 +4232,41 @@
|
||||
enable_manual: false
|
||||
update:
|
||||
columns:
|
||||
- clm_no
|
||||
- v_make_desc
|
||||
- date_next_contact
|
||||
- status
|
||||
- employee_csr
|
||||
- employee_prep
|
||||
- clm_total
|
||||
- suspended
|
||||
- employee_body
|
||||
- ro_number
|
||||
- actual_in
|
||||
- ownr_co_nm
|
||||
- v_model_yr
|
||||
- comment
|
||||
- job_totals
|
||||
- v_vin
|
||||
- ownr_fn
|
||||
- scheduled_completion
|
||||
- special_coverage_policy
|
||||
- v_color
|
||||
- ca_gst_registrant
|
||||
- scheduled_delivery
|
||||
- actual_delivery
|
||||
- actual_completion
|
||||
- kanbanparent
|
||||
- est_ct_fn
|
||||
- alt_transport
|
||||
- v_model_desc
|
||||
- clm_no
|
||||
- v_make_desc
|
||||
- date_next_contact
|
||||
- status
|
||||
- employee_csr
|
||||
- actual_in
|
||||
- v_model_yr
|
||||
- comment
|
||||
- job_totals
|
||||
- ownr_fn
|
||||
- v_color
|
||||
- ca_gst_registrant
|
||||
- employee_refinish
|
||||
- ownr_ph1
|
||||
- date_last_contacted
|
||||
- alt_transport
|
||||
- inproduction
|
||||
- est_ct_ln
|
||||
- production_vars
|
||||
- category
|
||||
- v_model_desc
|
||||
- date_invoiced
|
||||
- est_co_nm
|
||||
- ownr_ln
|
||||
@@ -4295,6 +4279,12 @@
|
||||
- name: event-secret
|
||||
value_from_env: EVENT_SECRET
|
||||
request_transform:
|
||||
body:
|
||||
action: transform
|
||||
template: |-
|
||||
{
|
||||
"data": {{$body?.event?.data?.new}}
|
||||
}
|
||||
method: POST
|
||||
query_params: {}
|
||||
template_engine: Kriti
|
||||
@@ -4496,7 +4486,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
conversation:
|
||||
bodyshop:
|
||||
@@ -4670,7 +4659,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
@@ -4805,7 +4793,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -5110,7 +5097,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
parts_order:
|
||||
job:
|
||||
@@ -5243,7 +5229,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
@@ -5419,7 +5404,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
@@ -5559,7 +5543,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -5670,7 +5653,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
_or:
|
||||
- parentjob_rel:
|
||||
@@ -5760,7 +5742,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
job:
|
||||
bodyshop:
|
||||
@@ -6045,7 +6026,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -6541,7 +6521,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
@@ -6698,7 +6677,6 @@
|
||||
delete_permissions:
|
||||
- role: user
|
||||
permission:
|
||||
backend_only: false
|
||||
filter:
|
||||
bodyshop:
|
||||
associations:
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE INDEX idx_timetickets_date ON timetickets (date );
|
||||
@@ -0,0 +1 @@
|
||||
CREATE INDEX idx_timetickets_date ON timetickets (date );
|
||||
@@ -0,0 +1,9 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE INDEX idx_jobs_ownr_fn ON jobs USING gin (ownr_fn gin_trgm_ops);
|
||||
-- CREATE INDEX idx_jobs_ownr_ln ON jobs USING gin (ownr_ln gin_trgm_ops);
|
||||
-- CREATE INDEX idx_jobs_ownr_co_nm ON jobs USING gin (ownr_co_nm gin_trgm_ops);
|
||||
-- CREATE INDEX idx_jobs_clm_no ON jobs USING gin (clm_no gin_trgm_ops);
|
||||
-- CREATE INDEX idx_jobs_v_make_desc ON jobs USING gin (v_make_desc gin_trgm_ops);
|
||||
-- CREATE INDEX idx_jobs_v_model_desc ON jobs USING gin (v_model_desc gin_trgm_ops);
|
||||
-- CREATE INDEX idx_jobs_plate_no ON jobs USING gin (plate_no gin_trgm_ops);
|
||||
7
hasura/migrations/1730517308367_run_sql_migration/up.sql
Normal file
7
hasura/migrations/1730517308367_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,7 @@
|
||||
CREATE INDEX idx_jobs_ownr_fn ON jobs USING gin (ownr_fn gin_trgm_ops);
|
||||
CREATE INDEX idx_jobs_ownr_ln ON jobs USING gin (ownr_ln gin_trgm_ops);
|
||||
CREATE INDEX idx_jobs_ownr_co_nm ON jobs USING gin (ownr_co_nm gin_trgm_ops);
|
||||
CREATE INDEX idx_jobs_clm_no ON jobs USING gin (clm_no gin_trgm_ops);
|
||||
CREATE INDEX idx_jobs_v_make_desc ON jobs USING gin (v_make_desc gin_trgm_ops);
|
||||
CREATE INDEX idx_jobs_v_model_desc ON jobs USING gin (v_model_desc gin_trgm_ops);
|
||||
CREATE INDEX idx_jobs_plate_no ON jobs USING gin (plate_no gin_trgm_ops);
|
||||
@@ -0,0 +1,3 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE INDEX idx_exportlog_createdat_desc ON exportlog (created_at desc);
|
||||
1
hasura/migrations/1730518121867_run_sql_migration/up.sql
Normal file
1
hasura/migrations/1730518121867_run_sql_migration/up.sql
Normal file
@@ -0,0 +1 @@
|
||||
CREATE INDEX idx_exportlog_createdat_desc ON exportlog (created_at desc);
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Could not auto-generate a down migration.
|
||||
-- Please write an appropriate down migration for the SQL below:
|
||||
-- CREATE index idx_messages_unread_agg ON messages (read, isoutbound)
|
||||
-- WHERE read = false AND isoutbound = false;
|
||||
2
hasura/migrations/1730521661838_run_sql_migration/up.sql
Normal file
2
hasura/migrations/1730521661838_run_sql_migration/up.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
CREATE index idx_messages_unread_agg ON messages (read, isoutbound)
|
||||
WHERE read = false AND isoutbound = false;
|
||||
1286
package-lock.json
generated
1286
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-cloudwatch-logs": "^3.679.0",
|
||||
"@aws-sdk/client-elasticache": "^3.675.0",
|
||||
"@aws-sdk/client-s3": "^3.689.0",
|
||||
"@aws-sdk/client-secrets-manager": "^3.675.0",
|
||||
"@aws-sdk/client-ses": "^3.675.0",
|
||||
"@aws-sdk/credential-provider-node": "^3.675.0",
|
||||
|
||||
35
server.js
35
server.js
@@ -21,7 +21,7 @@ const { applyRedisHelpers } = require("./server/utils/redisHelpers");
|
||||
const { applyIOHelpers } = require("./server/utils/ioHelpers");
|
||||
const { redisSocketEvents } = require("./server/web-sockets/redisSocketEvents");
|
||||
const { ElastiCacheClient, DescribeCacheClustersCommand } = require("@aws-sdk/client-elasticache");
|
||||
const { default: InstanceManager } = require("./server/utils/instanceMgr");
|
||||
const { InstanceRegion } = require("./server/utils/instanceMgr");
|
||||
|
||||
const CLUSTER_RETRY_BASE_DELAY = 100;
|
||||
const CLUSTER_RETRY_MAX_DELAY = 5000;
|
||||
@@ -114,10 +114,7 @@ const applyRoutes = ({ app }) => {
|
||||
*/
|
||||
const getRedisNodesFromAWS = async () => {
|
||||
const client = new ElastiCacheClient({
|
||||
region: InstanceManager({
|
||||
imex: "ca-central-1",
|
||||
rome: "us-east-2"
|
||||
})
|
||||
region: InstanceRegion()
|
||||
});
|
||||
|
||||
const params = {
|
||||
@@ -153,18 +150,13 @@ const connectToRedisCluster = async () => {
|
||||
} else {
|
||||
// Use the Dockerized Redis cluster in development
|
||||
if (isEmpty(process.env?.REDIS_URL) || !isString(process.env?.REDIS_URL)) {
|
||||
logger.log(`[${process.env.NODE_ENV}] No or Malformed REDIS_URL present.`, "ERROR", "redis", "api");
|
||||
logger.log(`No or Malformed REDIS_URL present.`, "ERROR", "redis", "api");
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
redisServers = JSON.parse(process.env.REDIS_URL);
|
||||
} catch (error) {
|
||||
logger.log(
|
||||
`[${process.env.NODE_ENV}] Failed to parse REDIS_URL: ${error.message}. Exiting...`,
|
||||
"ERROR",
|
||||
"redis",
|
||||
"api"
|
||||
);
|
||||
logger.log(`Failed to parse REDIS_URL: ${error.message}. Exiting...`, "ERROR", "redis", "api");
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
@@ -172,12 +164,7 @@ const connectToRedisCluster = async () => {
|
||||
const clusterRetryStrategy = (times) => {
|
||||
const delay =
|
||||
Math.min(CLUSTER_RETRY_BASE_DELAY + times * 50, CLUSTER_RETRY_MAX_DELAY) + Math.random() * CLUSTER_RETRY_JITTER;
|
||||
logger.log(
|
||||
`[${process.env.NODE_ENV}] Redis cluster not yet ready. Retrying in ${delay.toFixed(2)}ms`,
|
||||
"WARN",
|
||||
"redis",
|
||||
"api"
|
||||
);
|
||||
logger.log(`Redis cluster not yet ready. Retrying in ${delay.toFixed(2)}ms`, "WARN", "redis", "api");
|
||||
return delay;
|
||||
};
|
||||
|
||||
@@ -194,12 +181,12 @@ const connectToRedisCluster = async () => {
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
redisCluster.on("ready", () => {
|
||||
logger.log(`[${process.env.NODE_ENV}] Redis cluster connection established.`, "INFO", "redis", "api");
|
||||
logger.log(`Redis cluster connection established.`, "INFO", "redis", "api");
|
||||
resolve(redisCluster);
|
||||
});
|
||||
|
||||
redisCluster.on("error", (err) => {
|
||||
logger.log(`[${process.env.NODE_ENV}] Redis cluster connection failed: ${err.message}`, "ERROR", "redis", "api");
|
||||
logger.log(`Redis cluster connection failed: ${err.message}`, "ERROR", "redis", "api");
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
@@ -215,7 +202,7 @@ const applySocketIO = async ({ server, app }) => {
|
||||
|
||||
// Handle errors
|
||||
redisCluster.on("error", (err) => {
|
||||
logger.log(`[${process.env.NODE_ENV}] Redis ERROR`, "ERROR", "redis", "api");
|
||||
logger.log(`Redis ERROR`, "ERROR", "redis", "api");
|
||||
});
|
||||
|
||||
const pubClient = redisCluster;
|
||||
@@ -249,7 +236,7 @@ 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");
|
||||
logger.log(`Initializing Redis Admin UI....`, "INFO", "redis", "api");
|
||||
instrument(ioRedis, {
|
||||
auth: {
|
||||
type: "basic",
|
||||
@@ -312,9 +299,9 @@ const main = async () => {
|
||||
|
||||
try {
|
||||
await server.listen(port);
|
||||
logger.log(`[${process.env.NODE_ENV}] Server started on port ${port}`, "INFO", "api");
|
||||
logger.log(`Server started on port ${port}`, "INFO", "api");
|
||||
} catch (error) {
|
||||
logger.log(`[${process.env.NODE_ENV}] Server failed to start on port ${port}`, "ERROR", "api", error);
|
||||
logger.log(`Server failed to start on port ${port}`, "ERROR", "api", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ axios.interceptors.request.use((x) => {
|
||||
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
|
||||
//console.log(printable);
|
||||
|
||||
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
|
||||
CdkBase.createJsonEvent(socket, "SILLY", `Raw Request: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
@@ -36,7 +36,7 @@ axios.interceptors.response.use((x) => {
|
||||
|
||||
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
|
||||
//console.log(printable);
|
||||
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
|
||||
CdkBase.createJsonEvent(socket, "SILLY", `Raw Response: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
@@ -181,7 +181,7 @@ async function QueryBillData(socket, billids) {
|
||||
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)}`);
|
||||
CdkBase.createLogEvent(socket, "SILLY", `Bill data query result ${JSON.stringify(result, null, 2)}`);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ axios.interceptors.request.use((x) => {
|
||||
} | ${JSON.stringify(x.data)} | ${JSON.stringify(headers)}`;
|
||||
//console.log(printable);
|
||||
|
||||
CdkBase.createJsonEvent(socket, "TRACE", `Raw Request: ${printable}`, x.data);
|
||||
CdkBase.createJsonEvent(socket, "SILLY", `Raw Request: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
@@ -38,7 +38,7 @@ axios.interceptors.response.use((x) => {
|
||||
|
||||
const printable = `${new Date()} | Response: ${x.status} | ${JSON.stringify(x.data)}`;
|
||||
//console.log(printable);
|
||||
CdkBase.createJsonEvent(socket, "TRACE", `Raw Response: ${printable}`, x.data);
|
||||
CdkBase.createJsonEvent(socket, "SILLY", `Raw Response: ${printable}`, x.data);
|
||||
|
||||
return x;
|
||||
});
|
||||
@@ -118,7 +118,7 @@ async function CheckForErrors(socket, response) {
|
||||
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)}`);
|
||||
CdkBase.createLogEvent(socket, "SILLY", `Error received from DMS: ${JSON.stringify(response)}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +130,7 @@ async function QueryJobData(socket, jobid) {
|
||||
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)}`);
|
||||
CdkBase.createLogEvent(socket, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
return result.jobs_by_pk;
|
||||
}
|
||||
|
||||
@@ -611,7 +611,7 @@ async function InsertFailedExportLog(socket, error) {
|
||||
bodyshopid: socket.JobData.bodyshop.id,
|
||||
jobid: socket.JobData.id,
|
||||
successful: false,
|
||||
message: [error],
|
||||
message: JSON.stringify(error),
|
||||
useremail: socket.user.email
|
||||
}
|
||||
});
|
||||
|
||||
@@ -63,7 +63,7 @@ exports.default = function ({ bodyshop, jobs_by_pk, qbo = false, items, taxCodes
|
||||
);
|
||||
|
||||
if (!account) {
|
||||
logger.log("qbxml-receivables-no-account", "warn", null, jobline.id, null);
|
||||
logger.log("qbxml-receivables-no-account", "warn", null, jobline.id);
|
||||
throw new Error(
|
||||
`A matching account does not exist for the part allocation. Center: ${jobline.profitcenter_part}`
|
||||
);
|
||||
|
||||
@@ -45,7 +45,7 @@ exports.default = async (req, res) => {
|
||||
const BearerToken = req.BearerToken;
|
||||
const client = req.userGraphQLClient;
|
||||
|
||||
logger.log("qbo-payable-create", "DEBUG", req.user.email, billsToQuery);
|
||||
logger.log("qbo-payable-create", "DEBUG", req.user.email, null, { billsToQuery });
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: BearerToken })
|
||||
@@ -91,6 +91,12 @@ exports.default = async (req, res) => {
|
||||
|
||||
ret.push({ billid: bill.id, success: true });
|
||||
} catch (error) {
|
||||
logger.log("qbo-paybles-create-error", "ERROR", req.user.email, null, {
|
||||
error:
|
||||
(error && error.authResponse && error.authResponse.body) ||
|
||||
error.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
|
||||
(error && error.message)
|
||||
});
|
||||
ret.push({
|
||||
billid: bill.id,
|
||||
success: false,
|
||||
@@ -122,7 +128,7 @@ exports.default = async (req, res) => {
|
||||
res.status(200).json(ret);
|
||||
} catch (error) {
|
||||
//console.log(error);
|
||||
logger.log("qbo-payable-create-error", "ERROR", req.user.email, {
|
||||
logger.log("qbo-payable-create-error", "ERROR", req.user.email, null, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
@@ -161,7 +167,7 @@ async function QueryVendorRecord(oauthClient, qbo_realmId, req, bill) {
|
||||
|
||||
async function InsertVendorRecord(oauthClient, qbo_realmId, req, bill) {
|
||||
const Vendor = {
|
||||
DisplayName: bill.vendor.name
|
||||
DisplayName: StandardizeName(bill.vendor.name)
|
||||
};
|
||||
try {
|
||||
const result = await oauthClient.makeApiCall({
|
||||
|
||||
@@ -49,7 +49,7 @@ exports.default = async (req, res) => {
|
||||
const BearerToken = req.BearerToken;
|
||||
const client = req.userGraphQLClient;
|
||||
|
||||
logger.log("qbo-payment-create", "DEBUG", req.user.email, paymentsToQuery);
|
||||
logger.log("qbo-payment-create", "DEBUG", req.user.email, null, { paymentsToQuery });
|
||||
|
||||
const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
|
||||
payments: paymentsToQuery
|
||||
@@ -152,7 +152,7 @@ exports.default = async (req, res) => {
|
||||
|
||||
ret.push({ paymentid: payment.id, success: true });
|
||||
} catch (error) {
|
||||
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
|
||||
logger.log("qbo-payment-create-error", "ERROR", req.user.email, null, {
|
||||
error: (error && error.authResponse && error.authResponse.body) || (error && error.message)
|
||||
});
|
||||
//Add the export log error.
|
||||
@@ -183,7 +183,7 @@ exports.default = async (req, res) => {
|
||||
res.status(200).json(ret);
|
||||
} catch (error) {
|
||||
//console.log(error);
|
||||
logger.log("qbo-payment-create-error", "ERROR", req.user.email, {
|
||||
logger.log("qbo-payment-create-error", "ERROR", req.user.email, null, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ function urlBuilder(realmId, object, query = null) {
|
||||
}
|
||||
|
||||
function StandardizeName(str) {
|
||||
return str.replace(new RegExp(/'/g), "\\'");
|
||||
return str.replace(new RegExp(/'/g), "\\'").trim();
|
||||
}
|
||||
|
||||
exports.urlBuilder = urlBuilder;
|
||||
|
||||
@@ -42,9 +42,10 @@ exports.default = async (req, res) => {
|
||||
//For each invoice.
|
||||
res.status(200).json(QbXmlToExecute);
|
||||
} catch (error) {
|
||||
logger.log("qbxml-payable-error", "ERROR", req.user.email, req.body.billsToQuery, {
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
logger.log("qbxml-payable-error", "ERROR", req?.user?.email, null, {
|
||||
billsToQuery: req?.body?.billsToQuery,
|
||||
error: error?.message,
|
||||
stack: error?.stack
|
||||
});
|
||||
res.status(400).send(JSON.stringify(error));
|
||||
}
|
||||
|
||||
@@ -21,7 +21,9 @@ exports.default = async (req, res) => {
|
||||
const client = req.userGraphQLClient;
|
||||
|
||||
try {
|
||||
logger.log("qbxml-payments-create", "DEBUG", req.user.email, req.body.paymentsToQuery, null);
|
||||
logger.log("qbxml-payments-create", "DEBUG", req?.user?.email, null, {
|
||||
paymentsToQuery: req.body?.paymentsToQuery
|
||||
});
|
||||
|
||||
const result = await client.setHeaders({ Authorization: BearerToken }).request(queries.QUERY_PAYMENTS_FOR_EXPORT, {
|
||||
payments: paymentsToQuery
|
||||
@@ -62,7 +64,8 @@ exports.default = async (req, res) => {
|
||||
|
||||
res.status(200).json(QbXmlToExecute);
|
||||
} catch (error) {
|
||||
logger.log("qbxml-payments-error", "error", req.user.email, req.body.paymentsToQuery, {
|
||||
logger.log("qbxml-payments-error", "error", req?.user?.email, null, {
|
||||
paymentsToQuery: req.body?.paymentsToQuery,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
@@ -23,7 +23,9 @@ exports.default = async (req, res) => {
|
||||
const client = req.userGraphQLClient;
|
||||
|
||||
try {
|
||||
logger.log("qbxml-receivables-create", "DEBUG", req.user.email, req.body.jobIds, null);
|
||||
logger.log("qbxml-receivables-create", "DEBUG", req?.user?.email, null, {
|
||||
jobIds: req?.body?.jobIds
|
||||
});
|
||||
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: BearerToken })
|
||||
@@ -74,7 +76,8 @@ exports.default = async (req, res) => {
|
||||
|
||||
res.status(200).json(QbXmlToExecute);
|
||||
} catch (error) {
|
||||
logger.log("qbxml-receivables-error", "error", req.user.email, req.body.jobIds, {
|
||||
logger.log("qbxml-receivables-error", "error", req?.user?.email, null, {
|
||||
jobIds: req.body?.jobIds,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
|
||||
76
server/alerts/alertcheck.js
Normal file
76
server/alerts/alertcheck.js
Normal file
@@ -0,0 +1,76 @@
|
||||
const axios = require("axios");
|
||||
const _ = require("lodash");
|
||||
const { default: InstanceMgr } = require("../utils/instanceMgr"); // For deep object comparison
|
||||
|
||||
// Constants
|
||||
const ALERTS_REDIS_KEY = "alerts_data"; // The key under which we'll store alerts in Redis
|
||||
const GLOBAL_SOCKET_ID = "global"; // Use 'global' as a socketId to store global data
|
||||
|
||||
const ALERT_FILE_URL = InstanceMgr({
|
||||
imex: "https://images.imex.online/alerts/alerts-imex.json",
|
||||
rome: "https://images.imex.online/alerts/alerts-rome.json"
|
||||
});
|
||||
|
||||
const alertCheck = async (req, res) => {
|
||||
// Access Redis helper functions
|
||||
const { ioRedis, logger } = req;
|
||||
const { getSessionData, setSessionData } = req.sessionUtils;
|
||||
|
||||
try {
|
||||
// Get the JSON Alert file from the server
|
||||
const response = await axios.get(ALERT_FILE_URL);
|
||||
const currentAlerts = response.data;
|
||||
// Retrieve stored alerts from Redis using a global socketId
|
||||
const storedAlerts = await getSessionData(GLOBAL_SOCKET_ID, ALERTS_REDIS_KEY);
|
||||
if (!storedAlerts) {
|
||||
// Alerts not in Redis, store them
|
||||
await setSessionData(GLOBAL_SOCKET_ID, ALERTS_REDIS_KEY, currentAlerts);
|
||||
logger.logger.debug("Alerts added to Redis for the first time.");
|
||||
|
||||
// Emit to clients
|
||||
if (ioRedis) {
|
||||
ioRedis.emit("bodyshop-message", {
|
||||
type: "alert-update",
|
||||
payload: currentAlerts
|
||||
});
|
||||
logger.logger.debug("Alerts emitted to clients for the first time.");
|
||||
} else {
|
||||
logger.log("Socket.IO instance not found. (1)", "error");
|
||||
}
|
||||
|
||||
return res.status(200).send("Alerts added to Redis and emitted to clients.");
|
||||
} else {
|
||||
// Alerts are in Redis, compare them
|
||||
if (!_.isEqual(currentAlerts, storedAlerts)) {
|
||||
// Alerts are different, update Redis and emit to clients
|
||||
await setSessionData(GLOBAL_SOCKET_ID, ALERTS_REDIS_KEY, currentAlerts);
|
||||
logger.logger.debug("Alerts updated in Redis.");
|
||||
|
||||
// Emit the new alerts to all connected clients
|
||||
if (ioRedis) {
|
||||
ioRedis.emit("bodyshop-message", {
|
||||
type: "alert-update",
|
||||
payload: currentAlerts
|
||||
});
|
||||
logger.logger.debug("Alerts emitted to clients after update.");
|
||||
} else {
|
||||
logger.log("Socket.IO instance not found. (2)", "error");
|
||||
}
|
||||
|
||||
return res.status(200).send("Alerts updated in Redis and emitted to clients.");
|
||||
} else {
|
||||
return res.status(200).send("No changes in alerts.");
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log("Error in alertCheck:", "error", null, null, {
|
||||
error: {
|
||||
message: error.message,
|
||||
stack: error.stack
|
||||
}
|
||||
});
|
||||
return res.status(500).send("Internal server error.");
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = { alertCheck };
|
||||
@@ -42,7 +42,7 @@ async function QueryJobData(connectionData, token, jobid) {
|
||||
CdkBase.createLogEvent(connectionData, "DEBUG", `Querying job data for id ${jobid}`);
|
||||
const client = new GraphQLClient(process.env.GRAPHQL_ENDPOINT, {});
|
||||
const result = await client.setHeaders({ Authorization: token }).request(queries.GET_CDK_ALLOCATIONS, { id: jobid });
|
||||
CdkBase.createLogEvent(connectionData, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
CdkBase.createLogEvent(connectionData, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
return result.jobs_by_pk;
|
||||
}
|
||||
|
||||
@@ -373,13 +373,19 @@ function calculateAllocations(connectionData, job) {
|
||||
});
|
||||
//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) {
|
||||
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(
|
||||
connectionData,
|
||||
|
||||
@@ -39,8 +39,8 @@ exports.default = async function ReloadCdkMakes(req, res) {
|
||||
const deleteResult = await client
|
||||
.setHeaders({ Authorization: BearerToken })
|
||||
.request(queries.DELETE_ALL_DMS_VEHICLES, {});
|
||||
console.log("🚀 ~ file: cdk-get-makes.js ~ line 53 ~ deleteResult", deleteResult);
|
||||
|
||||
// logger.logger.debug("🚀 ~ file: cdk-get-makes.js ~ line 53 ~ deleteResult", { deleteResult });
|
||||
//Insert the new ones.
|
||||
|
||||
const insertResult = await client.setHeaders({ Authorization: BearerToken }).request(queries.INSERT_DMS_VEHICLES, {
|
||||
@@ -59,6 +59,7 @@ exports.default = async function ReloadCdkMakes(req, res) {
|
||||
cdk_dealerid,
|
||||
count: newList.length
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
logger.log("cdk-replace-makes-models-error", "ERROR", req.user.email, null, {
|
||||
|
||||
@@ -151,7 +151,7 @@ async function QueryJobData(socket, jobid) {
|
||||
const result = await client
|
||||
.setHeaders({ Authorization: `Bearer ${socket.handshake.auth.token}` })
|
||||
.request(queries.QUERY_JOBS_FOR_CDK_EXPORT, { id: jobid });
|
||||
CdkBase.createLogEvent(socket, "TRACE", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
CdkBase.createLogEvent(socket, "SILLY", `Job data query result ${JSON.stringify(result, null, 2)}`);
|
||||
return result.jobs_by_pk;
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ async function CalculateDmsVid(socket, JobData) {
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"SILLY",
|
||||
`soapClientVehicleInsertUpdate.getVehIdsAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseVehicleInsertUpdate);
|
||||
@@ -214,7 +214,7 @@ async function QueryDmsVehicleById(socket, JobData, DMSVid) {
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"SILLY",
|
||||
`soapClientVehicleInsertUpdate.readAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.readAsync response.`);
|
||||
@@ -246,7 +246,7 @@ async function QueryDmsCustomerById(socket, JobData, CustomerId) {
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.readAsync response.`);
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"SILLY",
|
||||
`soapClientCustomerInsertUpdate.readAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
|
||||
@@ -295,7 +295,7 @@ async function QueryDmsCustomerByName(socket, JobData) {
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"SILLY",
|
||||
`soapClientCustomerSearch.executeSearchBulkAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseCustomerSearch);
|
||||
@@ -337,7 +337,7 @@ async function GenerateDmsCustomerNumber(socket) {
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"SILLY",
|
||||
`soapClientCustomerInsertUpdate.getCustomerNumberAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
|
||||
@@ -425,7 +425,7 @@ async function InsertDmsCustomer(socket, newCustomerNumber) {
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientCustomerInsertUpdate.insertAsync response.`);
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"SILLY",
|
||||
`soapClientCustomerInsertUpdate.insertAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CheckCdkResponseForError(socket, soapResponseCustomerInsertUpdate);
|
||||
@@ -505,7 +505,7 @@ async function InsertDmsVehicle(socket) {
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"SILLY",
|
||||
`soapClientVehicleInsertUpdate.insertAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.insertAsync response.`);
|
||||
@@ -611,7 +611,7 @@ async function UpdateDmsVehicle(socket) {
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"DEBUG",
|
||||
`soapClientVehicleInsertUpdate.updateAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientVehicleInsertUpdate.updateAsync response.`);
|
||||
@@ -650,7 +650,7 @@ async function InsertServiceVehicleHistory(socket) {
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"SILLY",
|
||||
`soapClientServiceHistoryInsert.serviceHistoryHeaderInsert Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientServiceHistoryInsert.serviceHistoryHeaderInsert response.`);
|
||||
@@ -690,7 +690,7 @@ async function InsertDmsStartWip(socket) {
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"SILLY",
|
||||
`soapClientAccountingGLInsertUpdate.doStartWIPAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doStartWIPAsync response.`);
|
||||
@@ -721,7 +721,7 @@ async function InsertDmsBatchWip(socket) {
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"SILLY",
|
||||
`soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doTransBatchWIPAsync response.`);
|
||||
@@ -885,7 +885,7 @@ async function PostDmsBatchWip(socket) {
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"SILLY",
|
||||
`soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`);
|
||||
@@ -914,7 +914,7 @@ async function QueryDmsErrWip(socket) {
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"DEBUG",
|
||||
`soapClientAccountingGLInsertUpdate.doErrWIPAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doErrWIPAsync response.`);
|
||||
@@ -945,7 +945,7 @@ async function DeleteDmsWip(socket) {
|
||||
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"TRACE",
|
||||
"SILLY",
|
||||
`soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync Result ${JSON.stringify(result, null, 2)}`
|
||||
);
|
||||
CdkBase.createXmlEvent(socket, rawResponse, `soapClientAccountingGLInsertUpdate.doPostBatchWIPAsync response.`);
|
||||
@@ -995,7 +995,7 @@ async function InsertFailedExportLog(socket, error) {
|
||||
bodyshopid: socket.JobData.bodyshop.id,
|
||||
jobid: socket.JobData.id,
|
||||
successful: false,
|
||||
message: [error],
|
||||
message: JSON.stringify(error),
|
||||
useremail: socket.user.email
|
||||
}
|
||||
});
|
||||
|
||||
@@ -20,7 +20,7 @@ function CheckCdkResponseForError(socket, soapResponse) {
|
||||
//The response was null, this might be ok, it might not.
|
||||
CdkBase.createLogEvent(
|
||||
socket,
|
||||
"WARNING",
|
||||
"warn",
|
||||
`Warning detected in CDK Response - it appears to be null. Stack: ${new Error().stack}`
|
||||
);
|
||||
return;
|
||||
|
||||
@@ -13,6 +13,7 @@ let Client = require("ssh2-sftp-client");
|
||||
|
||||
const client = require("../graphql-client/graphql-client").client;
|
||||
const { sendServerEmail } = require("../email/sendemail");
|
||||
|
||||
const AHDineroFormat = "0.00";
|
||||
const AhDateFormat = "MMDDYYYY";
|
||||
|
||||
@@ -26,170 +27,177 @@ const ftpSetup = {
|
||||
password: process.env.AUTOHOUSE_PASSWORD,
|
||||
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
|
||||
algorithms: {
|
||||
serverHostKey: ["ssh-rsa", "ssh-dss"]
|
||||
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
|
||||
}
|
||||
};
|
||||
|
||||
const allxmlsToUpload = [];
|
||||
const allErrors = [];
|
||||
|
||||
exports.default = async (req, res) => {
|
||||
// Only process if in production environment.
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
//Query for the List of Bodyshop Clients.
|
||||
logger.log("autohouse-start", "DEBUG", "api", null, null);
|
||||
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS);
|
||||
|
||||
const specificShopIds = req.body.bodyshopIds; // ['uuid]
|
||||
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
|
||||
// Only process if the appropriate token is provided.
|
||||
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
|
||||
res.sendStatus(401);
|
||||
return;
|
||||
}
|
||||
const allxmlsToUpload = [];
|
||||
const allErrors = [];
|
||||
|
||||
// Send immediate response and continue processing.
|
||||
res.status(200).send();
|
||||
|
||||
try {
|
||||
for (const bodyshop of specificShopIds ? bodyshops.filter((b) => specificShopIds.includes(b.id)) : bodyshops) {
|
||||
logger.log("autohouse-start", "DEBUG", "api", null, null);
|
||||
const { bodyshops } = await client.request(queries.GET_AUTOHOUSE_SHOPS); //Query for the List of Bodyshop Clients.
|
||||
const specificShopIds = req.body.bodyshopIds; // ['uuid];
|
||||
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
|
||||
|
||||
const batchSize = 10;
|
||||
|
||||
const shopsToProcess =
|
||||
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
|
||||
logger.log("autohouse-shopsToProcess-generated", "DEBUG", "api", null, null);
|
||||
|
||||
if (shopsToProcess.length === 0) {
|
||||
logger.log("autohouse-shopsToProcess-empty", "DEBUG", "api", null, null);
|
||||
return;
|
||||
}
|
||||
const batchPromises = [];
|
||||
for (let i = 0; i < shopsToProcess.length; i += batchSize) {
|
||||
const batch = shopsToProcess.slice(i, i + batchSize);
|
||||
const batchPromise = (async () => {
|
||||
await processBatch(batch, start, end);
|
||||
|
||||
if (skipUpload) {
|
||||
for (const xmlObj of allxmlsToUpload) {
|
||||
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
|
||||
}
|
||||
} else {
|
||||
await uploadViaSFTP(allxmlsToUpload);
|
||||
}
|
||||
})();
|
||||
batchPromises.push(batchPromise);
|
||||
}
|
||||
await Promise.all(batchPromises);
|
||||
await sendServerEmail({
|
||||
subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
|
||||
text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify(
|
||||
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count, result: x.result })),
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
});
|
||||
|
||||
logger.log("autohouse-end", "DEBUG", "api", null, null);
|
||||
} catch (error) {
|
||||
logger.log("autohouse-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
||||
}
|
||||
};
|
||||
|
||||
async function processBatch(batch, start, end) {
|
||||
for (const bodyshop of batch) {
|
||||
const erroredJobs = [];
|
||||
try {
|
||||
logger.log("autohouse-start-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
shopname: bodyshop.shopname
|
||||
});
|
||||
const erroredJobs = [];
|
||||
try {
|
||||
const { jobs, bodyshops_by_pk } = await client.request(queries.AUTOHOUSE_QUERY, {
|
||||
bodyshopid: bodyshop.id,
|
||||
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
|
||||
...(end && { end: moment(end).endOf("day") })
|
||||
});
|
||||
|
||||
const autoHouseObject = {
|
||||
AutoHouseExport: {
|
||||
RepairOrder: jobs.map((j) =>
|
||||
CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
|
||||
erroredJobs.push({ job: job, error: error.toString() });
|
||||
})
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
if (erroredJobs.length > 0) {
|
||||
logger.log("autohouse-failed-jobs", "ERROR", "api", bodyshop.id, {
|
||||
count: erroredJobs.length,
|
||||
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
|
||||
});
|
||||
}
|
||||
|
||||
var ret = builder
|
||||
.create(
|
||||
{
|
||||
// version: "1.0",
|
||||
// encoding: "UTF-8",
|
||||
//keepNullNodes: true,
|
||||
},
|
||||
autoHouseObject
|
||||
)
|
||||
.end({ allowEmptyTags: true });
|
||||
|
||||
allxmlsToUpload.push({
|
||||
count: autoHouseObject.AutoHouseExport.RepairOrder.length,
|
||||
xml: ret,
|
||||
filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml`
|
||||
});
|
||||
|
||||
logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
shopname: bodyshop.shopname
|
||||
});
|
||||
} catch (error) {
|
||||
//Error at the shop level.
|
||||
logger.log("autohouse-error-shop", "ERROR", "api", bodyshop.id, {
|
||||
...error
|
||||
});
|
||||
|
||||
allErrors.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
autuhouseid: bodyshop.autuhouseid,
|
||||
fatal: true,
|
||||
errors: [error.toString()]
|
||||
});
|
||||
} finally {
|
||||
allErrors.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
autohouseid: bodyshop.autohouseid,
|
||||
errors: erroredJobs.map((ej) => ({
|
||||
ro_number: ej.job?.ro_number,
|
||||
jobid: ej.job?.id,
|
||||
error: ej.error
|
||||
}))
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (skipUpload) {
|
||||
for (const xmlObj of allxmlsToUpload) {
|
||||
fs.writeFileSync(`./logs/${xmlObj.filename}`, xmlObj.xml);
|
||||
}
|
||||
|
||||
res.json(allxmlsToUpload);
|
||||
sendServerEmail({
|
||||
subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
|
||||
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||
Uploaded: ${JSON.stringify(
|
||||
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||
null,
|
||||
2
|
||||
)}
|
||||
`
|
||||
const { jobs, bodyshops_by_pk } = await client.request(queries.AUTOHOUSE_QUERY, {
|
||||
bodyshopid: bodyshop.id,
|
||||
start: start ? moment(start).startOf("day") : moment().subtract(5, "days").startOf("day"),
|
||||
...(end && { end: moment(end).endOf("day") })
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let sftp = new Client();
|
||||
sftp.on("error", (errors) =>
|
||||
logger.log("autohouse-sftp-error", "ERROR", "api", null, {
|
||||
...errors
|
||||
})
|
||||
);
|
||||
try {
|
||||
//Connect to the FTP and upload all.
|
||||
const autoHouseObject = {
|
||||
AutoHouseExport: {
|
||||
RepairOrder: jobs.map((j) =>
|
||||
CreateRepairOrderTag({ ...j, bodyshop: bodyshops_by_pk }, function ({ job, error }) {
|
||||
erroredJobs.push({ job: job, error: error.toString() });
|
||||
})
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
await sftp.connect(ftpSetup);
|
||||
|
||||
for (const xmlObj of allxmlsToUpload) {
|
||||
logger.log("autohouse-sftp-upload", "DEBUG", "api", null, {
|
||||
filename: xmlObj.filename
|
||||
});
|
||||
|
||||
const uploadResult = await sftp.put(Buffer.from(xmlObj.xml), `/${xmlObj.filename}`);
|
||||
logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, {
|
||||
uploadResult
|
||||
if (erroredJobs.length > 0) {
|
||||
logger.log("autohouse-failed-jobs", "ERROR", "api", bodyshop.id, {
|
||||
count: erroredJobs.length,
|
||||
jobs: JSON.stringify(erroredJobs.map((j) => j.job.ro_number))
|
||||
});
|
||||
}
|
||||
|
||||
//***TODO Change filing naming when creating the cron job. IM_ShopInternalName_DDMMYYYY_HHMMSS.xml
|
||||
const ret = builder.create({}, autoHouseObject).end({ allowEmptyTags: true });
|
||||
|
||||
allxmlsToUpload.push({
|
||||
count: autoHouseObject.AutoHouseExport.RepairOrder.length,
|
||||
xml: ret,
|
||||
filename: `IM_${bodyshop.autohouseid}_${moment().format("DDMMYYYY_HHMMss")}.xml`
|
||||
});
|
||||
|
||||
logger.log("autohouse-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
shopname: bodyshop.shopname
|
||||
});
|
||||
} catch (error) {
|
||||
logger.log("autohouse-sftp-error", "ERROR", "api", null, {
|
||||
...error
|
||||
//Error at the shop level.
|
||||
logger.log("autohouse-error-shop", "ERROR", "api", bodyshop.id, { error: error.message, stack: error.stack });
|
||||
|
||||
allErrors.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
autuhouseid: bodyshop.autuhouseid,
|
||||
fatal: true,
|
||||
errors: [error.toString()]
|
||||
});
|
||||
} finally {
|
||||
sftp.end();
|
||||
allErrors.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
autuhouseid: bodyshop.autuhouseid,
|
||||
errors: erroredJobs.map((ej) => ({
|
||||
ro_number: ej.job?.ro_number,
|
||||
jobid: ej.job?.id,
|
||||
error: ej.error
|
||||
}))
|
||||
});
|
||||
}
|
||||
sendServerEmail({
|
||||
subject: `Autohouse Report ${moment().format("MM-DD-YY")}`,
|
||||
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||
Uploaded: ${JSON.stringify(
|
||||
allxmlsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||
null,
|
||||
2
|
||||
)}
|
||||
`
|
||||
});
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
res.status(200).json(error);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
async function uploadViaSFTP(allxmlsToUpload) {
|
||||
const sftp = new Client();
|
||||
sftp.on("error", (errors) =>
|
||||
logger.log("autohouse-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack })
|
||||
);
|
||||
try {
|
||||
//Connect to the FTP and upload all.
|
||||
await sftp.connect(ftpSetup);
|
||||
|
||||
for (const xmlObj of allxmlsToUpload) {
|
||||
try {
|
||||
logger.log("autohouse-sftp-upload", "DEBUG", "api", null, { filename: xmlObj.filename });
|
||||
xmlObj.result = await sftp.put(Buffer.from(xmlObj.xml), `${xmlObj.filename}`);
|
||||
logger.log("autohouse-sftp-upload-result", "DEBUG", "api", null, {
|
||||
filename: xmlObj.filename,
|
||||
result: xmlObj.result
|
||||
});
|
||||
} catch (error) {
|
||||
logger.log("autohouse-sftp-upload-error", "ERROR", "api", null, {
|
||||
filename: xmlObj.filename,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log("autohouse-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
||||
throw error;
|
||||
} finally {
|
||||
sftp.end();
|
||||
}
|
||||
}
|
||||
|
||||
const CreateRepairOrderTag = (job, errorCallback) => {
|
||||
//Level 2
|
||||
@@ -287,8 +295,8 @@ const CreateRepairOrderTag = (job, errorCallback) => {
|
||||
InsuranceCo: job.ins_co_nm || "",
|
||||
CompanyName: job.ins_co_nm || "",
|
||||
Address: job.ins_addr1 || "",
|
||||
City: job.ins_addr1 || "",
|
||||
State: job.ins_city || "",
|
||||
City: job.ins_city || "",
|
||||
State: job.ins_st || "",
|
||||
Zip: job.ins_zip || "",
|
||||
Phone: job.ins_ph1 || "",
|
||||
Fax: job.ins_fax || "",
|
||||
|
||||
@@ -22,135 +22,129 @@ const ftpSetup = {
|
||||
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
|
||||
}
|
||||
};
|
||||
|
||||
const allcsvsToUpload = [];
|
||||
const allErrors = [];
|
||||
|
||||
exports.default = async (req, res) => {
|
||||
// Only process if in production environment.
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
res.sendStatus(403);
|
||||
return;
|
||||
}
|
||||
|
||||
// Only process if the appropriate token is provided.
|
||||
if (req.headers["x-imex-auth"] !== process.env.AUTOHOUSE_AUTH_TOKEN) {
|
||||
res.sendStatus(401);
|
||||
return;
|
||||
}
|
||||
//Query for the List of Bodyshop Clients.
|
||||
logger.log("chatter-start", "DEBUG", "api", null, null);
|
||||
const { bodyshops } = await client.request(queries.GET_CHATTER_SHOPS);
|
||||
const specificShopIds = req.body.bodyshopIds; // ['uuid]
|
||||
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
|
||||
|
||||
const allcsvsToUpload = [];
|
||||
const allErrors = [];
|
||||
// Send immediate response and continue processing.
|
||||
res.status(200).send();
|
||||
|
||||
try {
|
||||
for (const bodyshop of specificShopIds ? bodyshops.filter((b) => specificShopIds.includes(b.id)) : bodyshops) {
|
||||
logger.log("chatter-start-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
shopname: bodyshop.shopname
|
||||
});
|
||||
try {
|
||||
const { jobs, bodyshops_by_pk } = await client.request(queries.CHATTER_QUERY, {
|
||||
bodyshopid: bodyshop.id,
|
||||
start: start ? moment(start).startOf("day") : moment().subtract(1, "days").startOf("day"),
|
||||
...(end && { end: moment(end).endOf("day") })
|
||||
});
|
||||
logger.log("chatter-start", "DEBUG", "api", null, null);
|
||||
const { bodyshops } = await client.request(queries.GET_CHATTER_SHOPS); //Query for the List of Bodyshop Clients.
|
||||
const specificShopIds = req.body.bodyshopIds; // ['uuid];
|
||||
const { start, end, skipUpload } = req.body; //YYYY-MM-DD
|
||||
|
||||
const chatterObject = jobs.map((j) => {
|
||||
return {
|
||||
poc_trigger_code: bodyshops_by_pk.chatterid,
|
||||
firstname: j.ownr_co_nm ? null : j.ownr_fn,
|
||||
lastname: j.ownr_co_nm ? j.ownr_co_nm : j.ownr_ln,
|
||||
transaction_id: j.ro_number,
|
||||
email: j.ownr_ea,
|
||||
phone_number: j.ownr_ph1
|
||||
};
|
||||
});
|
||||
const batchSize = 10;
|
||||
|
||||
const ret = converter.json2csv(chatterObject, { emptyFieldValue: "" });
|
||||
const shopsToProcess =
|
||||
specificShopIds?.length > 0 ? bodyshops.filter((shop) => specificShopIds.includes(shop.id)) : bodyshops;
|
||||
logger.log("chatter-shopsToProcess-generated", "DEBUG", "api", null, null);
|
||||
|
||||
allcsvsToUpload.push({
|
||||
count: chatterObject.length,
|
||||
csv: ret,
|
||||
filename: `${bodyshop.shopname}_solicitation_${moment().format("YYYYMMDD")}.csv`
|
||||
});
|
||||
|
||||
logger.log("chatter-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
shopname: bodyshop.shopname
|
||||
});
|
||||
} catch (error) {
|
||||
//Error at the shop level.
|
||||
logger.log("chatter-error-shop", "ERROR", "api", bodyshop.id, {
|
||||
...error
|
||||
});
|
||||
|
||||
allErrors.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
shopname: bodyshop.shopname,
|
||||
fatal: true,
|
||||
errors: [error.toString()]
|
||||
});
|
||||
} finally {
|
||||
allErrors.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
shopname: bodyshop.shopname
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (skipUpload) {
|
||||
for (const csvObj of allcsvsToUpload) {
|
||||
fs.writeFile(`./logs/${csvObj.filename}`, csvObj.csv);
|
||||
}
|
||||
|
||||
sendServerEmail({
|
||||
subject: `Chatter Report ${moment().format("MM-DD-YY")}`,
|
||||
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||
Uploaded: ${JSON.stringify(
|
||||
allcsvsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||
null,
|
||||
2
|
||||
)}
|
||||
`
|
||||
});
|
||||
res.json(allcsvsToUpload);
|
||||
if (shopsToProcess.length === 0) {
|
||||
logger.log("chatter-shopsToProcess-empty", "DEBUG", "api", null, null);
|
||||
return;
|
||||
}
|
||||
|
||||
const sftp = new Client();
|
||||
sftp.on("error", (errors) => logger.log("chatter-sftp-error", "ERROR", "api", null, { ...errors }));
|
||||
try {
|
||||
//Get the private key from AWS Secrets Manager.
|
||||
ftpSetup.privateKey = await getPrivateKey();
|
||||
|
||||
//Connect to the FTP and upload all.
|
||||
await sftp.connect(ftpSetup);
|
||||
|
||||
for (const csvObj of allcsvsToUpload) {
|
||||
logger.log("chatter-sftp-upload", "DEBUG", "api", null, { filename: csvObj.filename });
|
||||
|
||||
const uploadResult = await sftp.put(Buffer.from(csvObj.xml), `/${csvObj.filename}`);
|
||||
logger.log("chatter-sftp-upload-result", "DEBUG", "api", null, { uploadResult });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log("chatter-sftp-error", "ERROR", "api", null, { ...error });
|
||||
} finally {
|
||||
sftp.end();
|
||||
const batchPromises = [];
|
||||
for (let i = 0; i < shopsToProcess.length; i += batchSize) {
|
||||
const batch = shopsToProcess.slice(i, i + batchSize);
|
||||
const batchPromise = (async () => {
|
||||
await processBatch(batch, start, end);
|
||||
if (skipUpload) {
|
||||
for (const csvObj of allcsvsToUpload) {
|
||||
await fs.promises.writeFile(`./logs/${csvObj.filename}`, csvObj.csv);
|
||||
}
|
||||
} else {
|
||||
await uploadViaSFTP(allcsvsToUpload);
|
||||
}
|
||||
})();
|
||||
batchPromises.push(batchPromise);
|
||||
}
|
||||
sendServerEmail({
|
||||
await Promise.all(batchPromises);
|
||||
await sendServerEmail({
|
||||
subject: `Chatter Report ${moment().format("MM-DD-YY")}`,
|
||||
text: `Errors: ${allErrors.map((e) => JSON.stringify(e, null, 2))}
|
||||
Uploaded: ${JSON.stringify(
|
||||
allcsvsToUpload.map((x) => ({ filename: x.filename, count: x.count })),
|
||||
text: `Errors:\n${JSON.stringify(allErrors, null, 2)}\n\nUploaded:\n${JSON.stringify(
|
||||
allcsvsToUpload.map((x) => ({ filename: x.filename, count: x.count, result: x.result })),
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
});
|
||||
res.sendStatus(200);
|
||||
|
||||
logger.log("chatter-end", "DEBUG", "api", null, null);
|
||||
} catch (error) {
|
||||
res.status(200).json(error);
|
||||
logger.log("chatter-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
||||
}
|
||||
};
|
||||
|
||||
async function processBatch(batch, start, end) {
|
||||
for (const bodyshop of batch) {
|
||||
try {
|
||||
logger.log("chatter-start-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
shopname: bodyshop.shopname
|
||||
});
|
||||
|
||||
const { jobs, bodyshops_by_pk } = await client.request(queries.CHATTER_QUERY, {
|
||||
bodyshopid: bodyshop.id,
|
||||
start: start ? moment(start).startOf("day") : moment().subtract(1, "days").startOf("day"),
|
||||
...(end && { end: moment(end).endOf("day") })
|
||||
});
|
||||
|
||||
const chatterObject = jobs.map((j) => {
|
||||
return {
|
||||
poc_trigger_code: bodyshops_by_pk.chatterid,
|
||||
firstname: j.ownr_co_nm ? null : j.ownr_fn,
|
||||
lastname: j.ownr_co_nm ? j.ownr_co_nm : j.ownr_ln,
|
||||
transaction_id: j.ro_number,
|
||||
email: j.ownr_ea,
|
||||
phone_number: j.ownr_ph1
|
||||
};
|
||||
});
|
||||
|
||||
const ret = converter.json2csv(chatterObject, { emptyFieldValue: "" });
|
||||
|
||||
allcsvsToUpload.push({
|
||||
count: chatterObject.length,
|
||||
csv: ret,
|
||||
filename: `${bodyshop.shopname}_solicitation_${moment().format("YYYYMMDD")}.csv`
|
||||
});
|
||||
|
||||
logger.log("chatter-end-shop-extract", "DEBUG", "api", bodyshop.id, {
|
||||
shopname: bodyshop.shopname
|
||||
});
|
||||
} catch (error) {
|
||||
//Error at the shop level.
|
||||
logger.log("chatter-error-shop", "ERROR", "api", bodyshop.id, { error: error.message, stack: error.stack });
|
||||
|
||||
allErrors.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
shopname: bodyshop.shopname,
|
||||
fatal: true,
|
||||
errors: [error.toString()]
|
||||
});
|
||||
} finally {
|
||||
allErrors.push({
|
||||
bodyshopid: bodyshop.id,
|
||||
imexshopid: bodyshop.imexshopid,
|
||||
shopname: bodyshop.shopname
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function getPrivateKey() {
|
||||
// Connect to AWS Secrets Manager
|
||||
const client = new SecretsManagerClient({ region: "ca-central-1" });
|
||||
@@ -160,9 +154,47 @@ async function getPrivateKey() {
|
||||
try {
|
||||
const { SecretString, SecretBinary } = await client.send(command);
|
||||
if (SecretString || SecretBinary) logger.log("chatter-retrieved-private-key", "DEBUG", "api", null, null);
|
||||
return SecretString || Buffer.from(SecretBinary, "base64").toString("ascii");
|
||||
const chatterPrivateKey = SecretString ? SecretString : Buffer.from(SecretBinary, "base64").toString("ascii");
|
||||
return chatterPrivateKey;
|
||||
} catch (error) {
|
||||
logger.log("chatter-get-private-key", "ERROR", "api", null, error);
|
||||
throw err;
|
||||
logger.log("chatter-get-private-key", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function uploadViaSFTP(allcsvsToUpload) {
|
||||
const sftp = new Client();
|
||||
sftp.on("error", (errors) =>
|
||||
logger.log("chatter-sftp-connection-error", "ERROR", "api", null, { error: errors.message, stack: errors.stack })
|
||||
);
|
||||
try {
|
||||
//Get the private key from AWS Secrets Manager.
|
||||
const privateKey = await getPrivateKey();
|
||||
|
||||
//Connect to the FTP and upload all.
|
||||
await sftp.connect({ ...ftpSetup, privateKey });
|
||||
|
||||
for (const csvObj of allcsvsToUpload) {
|
||||
try {
|
||||
logger.log("chatter-sftp-upload", "DEBUG", "api", null, { filename: csvObj.filename });
|
||||
csvObj.result = await sftp.put(Buffer.from(csvObj.csv), `${csvObj.filename}`);
|
||||
logger.log("chatter-sftp-upload-result", "DEBUG", "api", null, {
|
||||
filename: csvObj.filename,
|
||||
result: csvObj.result
|
||||
});
|
||||
} catch (error) {
|
||||
logger.log("chatter-sftp-upload-error", "ERROR", "api", null, {
|
||||
filename: csvObj.filename,
|
||||
error: error.message,
|
||||
stack: error.stack
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
logger.log("chatter-sftp-error", "ERROR", "api", null, { error: error.message, stack: error.stack });
|
||||
throw error;
|
||||
} finally {
|
||||
sftp.end();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,21 @@
|
||||
const { isString, isEmpty } = require("lodash");
|
||||
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
|
||||
const { default: InstanceManager } = require("../utils/instanceMgr");
|
||||
const { InstanceRegion } = require("../utils/instanceMgr");
|
||||
const aws = require("@aws-sdk/client-ses");
|
||||
const nodemailer = require("nodemailer");
|
||||
const logger = require("../utils/logger");
|
||||
|
||||
const isLocal = isString(process.env?.LOCALSTACK_HOSTNAME) && !isEmpty(process.env?.LOCALSTACK_HOSTNAME);
|
||||
|
||||
const sesConfig = {
|
||||
apiVersion: "latest",
|
||||
credentials: defaultProvider(),
|
||||
region: isLocal
|
||||
? "ca-central-1"
|
||||
: InstanceManager({
|
||||
imex: "ca-central-1",
|
||||
rome: "us-east-2"
|
||||
})
|
||||
region: InstanceRegion()
|
||||
};
|
||||
|
||||
if (isLocal) {
|
||||
sesConfig.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
|
||||
console.log(`SES Mailer set to LocalStack end point: ${sesConfig.endpoint}`);
|
||||
logger.logger.debug(`SES Mailer set to LocalStack end point: ${sesConfig.endpoint}`);
|
||||
}
|
||||
|
||||
const ses = new aws.SES(sesConfig);
|
||||
|
||||
@@ -21,7 +21,7 @@ const getImage = async (imageUrl) => {
|
||||
// Log the email in the database
|
||||
const logEmail = async (req, email) => {
|
||||
try {
|
||||
const insertresult = await client.request(queries.INSERT_EMAIL_AUDIT, {
|
||||
await client.request(queries.INSERT_EMAIL_AUDIT, {
|
||||
email: {
|
||||
to: email.to,
|
||||
cc: email.cc,
|
||||
@@ -34,13 +34,13 @@ const logEmail = async (req, email) => {
|
||||
status: "Sent"
|
||||
}
|
||||
});
|
||||
console.log(insertresult);
|
||||
} catch (error) {
|
||||
logger.log("email-log-error", "error", req.user.email, null, {
|
||||
logger.log("email-log-error", "error", req?.user?.email, null, {
|
||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||
to: req.body.to,
|
||||
cc: req.body.cc,
|
||||
subject: req.body.subject
|
||||
to: req?.body?.to,
|
||||
cc: req?.body?.cc,
|
||||
subject: req?.body?.subject,
|
||||
email
|
||||
// info,
|
||||
});
|
||||
}
|
||||
@@ -70,12 +70,11 @@ const sendServerEmail = async ({ subject, text }) => {
|
||||
}
|
||||
},
|
||||
(err, info) => {
|
||||
console.log(err || info);
|
||||
logger.log("server-email-failure", err ? "error" : "debug", null, null, { message: err || info });
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
logger.log("server-email-failure", "error", null, null, error);
|
||||
logger.log("server-email-failure", "error", null, null, { error });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -88,8 +87,7 @@ const sendProManagerWelcomeEmail = async ({ to, subject, html }) => {
|
||||
html
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
logger.log("server-email-failure", "error", null, null, error);
|
||||
logger.log("server-email-failure", "error", null, null, { error });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -108,12 +106,12 @@ const sendTaskEmail = async ({ to, subject, type = "text", html, text, attachmen
|
||||
attachments: attachments || null
|
||||
},
|
||||
(err, info) => {
|
||||
console.log(err || info);
|
||||
// (message, type, user, record, meta
|
||||
logger.log("server-email", err ? "error" : "debug", null, null, { message: err || info });
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
logger.log("server-email-failure", "error", null, null, error);
|
||||
logger.log("server-email-failure", "error", null, null, { error });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -184,9 +182,8 @@ const sendEmail = async (req, res) => {
|
||||
}
|
||||
},
|
||||
(err, info) => {
|
||||
console.log(err || info);
|
||||
if (info) {
|
||||
logger.log("send-email-success", "DEBUG", req.user.email, null, {
|
||||
logger.log("send-email-success", "DEBUG", req?.user?.email, null, {
|
||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||
replyTo: req.body.ReplyTo.Email,
|
||||
to: req.body.to,
|
||||
@@ -205,7 +202,7 @@ const sendEmail = async (req, res) => {
|
||||
success: true //response: info
|
||||
});
|
||||
} else {
|
||||
logger.log("send-email-failure", "ERROR", req.user.email, null, {
|
||||
logger.log("send-email-failure", "ERROR", req?.user?.email, null, {
|
||||
from: `${req.body.from.name} <${req.body.from.address}>`,
|
||||
replyTo: req.body.ReplyTo.Email,
|
||||
to: req.body.to,
|
||||
@@ -290,7 +287,9 @@ ${body.bounce?.bouncedRecipients.map(
|
||||
`
|
||||
},
|
||||
(err, info) => {
|
||||
console.log("***", err || info);
|
||||
logger.log("sns-error", err ? "error" : "debug", "api", null, {
|
||||
message: err ? JSON.stringify(error) : info
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ const tasksEmailQueue = taskEmailQueue();
|
||||
const tasksEmailQueueCleanup = async () => {
|
||||
try {
|
||||
// Example async operation
|
||||
console.log("Performing Tasks Email Reminder process cleanup...");
|
||||
// console.log("Performing Tasks Email Reminder process cleanup...");
|
||||
await new Promise((resolve) => tasksEmailQueue.destroy(() => resolve()));
|
||||
} catch (err) {
|
||||
console.error("Tasks Email Reminder process cleanup failed:", err);
|
||||
// console.error("Tasks Email Reminder process cleanup failed:", err);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -77,6 +77,7 @@ const formatPriority = (priority) => {
|
||||
* @param priority
|
||||
* @param description
|
||||
* @param dueDate
|
||||
* @param createdBy
|
||||
* @param bodyshop
|
||||
* @param job
|
||||
* @param taskId
|
||||
@@ -96,11 +97,11 @@ const getEndpoints = (bodyshop) =>
|
||||
: "https://romeonline.io"
|
||||
});
|
||||
|
||||
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId, dateLine) => {
|
||||
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId, dateLine, createdBy) => {
|
||||
const endPoints = getEndpoints(bodyshop);
|
||||
return {
|
||||
header: title,
|
||||
subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)}`,
|
||||
subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)} | Created By: ${createdBy || "N/A"}`,
|
||||
body: `Reference: ${job.ro_number || "N/A"} | ${job.ownr_co_nm ? job.ownr_co_nm : `${job.ownr_fn || ""} ${job.ownr_ln || ""}`.trim()} | ${`${job.v_model_yr || ""} ${job.v_make_desc || ""} ${job.v_model_desc || ""}`.trim()}<br>${description ? description.concat("<br>") : ""}<a href="${endPoints}/manage/tasks/alltasks?taskid=${taskId}">View this task.</a>`,
|
||||
dateLine
|
||||
};
|
||||
@@ -181,7 +182,8 @@ const taskAssignedEmail = async (req, res) => {
|
||||
tasks_by_pk.bodyshop,
|
||||
tasks_by_pk.job,
|
||||
newTask.id,
|
||||
dateLine
|
||||
dateLine,
|
||||
newTask.created_by
|
||||
)
|
||||
),
|
||||
null,
|
||||
@@ -268,7 +270,8 @@ const tasksRemindEmail = async (req, res) => {
|
||||
onlyTask.bodyshop,
|
||||
onlyTask.job,
|
||||
onlyTask.id,
|
||||
dateLine
|
||||
dateLine,
|
||||
onlyTask.created_by
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -10,8 +10,9 @@ const logger = require("../utils/logger");
|
||||
const taskEmailQueue = () =>
|
||||
new Queue(
|
||||
(taskIds, cb) => {
|
||||
console.log("Processing reminds for taskIds: ", taskIds.join(", "));
|
||||
|
||||
logger.log("Processing reminds for taskIds: ", "silly", null, null, {
|
||||
taskIds: taskIds?.join(", ")
|
||||
});
|
||||
// Set the remind_at_sent to the current time.
|
||||
const now = moment().toISOString();
|
||||
|
||||
|
||||
@@ -269,10 +269,14 @@ const sendNotification = async (req, res) => {
|
||||
})
|
||||
.then((response) => {
|
||||
// Response is a message ID string.
|
||||
console.log("Successfully sent message:", response);
|
||||
logger.log("Successfully sent message:", "debug", req?.user?.email, null, {
|
||||
response
|
||||
});
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log("Error sending message:", error);
|
||||
logger.log("Successfully sent message:", "error", req?.user?.email, null, {
|
||||
error
|
||||
});
|
||||
});
|
||||
|
||||
res.sendStatus(200);
|
||||
|
||||
@@ -2506,6 +2506,7 @@ exports.QUERY_TASK_BY_ID = `
|
||||
query QUERY_TASK_BY_ID($id: uuid!) {
|
||||
tasks_by_pk(id: $id) {
|
||||
id
|
||||
created_by
|
||||
assigned_to_employee{
|
||||
id
|
||||
user_email
|
||||
|
||||
@@ -17,12 +17,10 @@ require("dotenv").config({
|
||||
const domain = process.env.NODE_ENV ? "secure" : "test";
|
||||
|
||||
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
|
||||
const { InstanceRegion } = require("../utils/instanceMgr");
|
||||
|
||||
const client = new SecretsManagerClient({
|
||||
region: InstanceManager({
|
||||
imex: "ca-central-1",
|
||||
rome: "us-east-2"
|
||||
})
|
||||
region: InstanceRegion()
|
||||
});
|
||||
|
||||
const gqlClient = require("../graphql-client/graphql-client").client;
|
||||
|
||||
@@ -35,7 +35,7 @@ exports.default = async (req, res) => {
|
||||
|
||||
res.sendStatus(200);
|
||||
} catch (error) {
|
||||
logger.log("ioevent-error", "trace", user, null, {
|
||||
logger.log("ioevent-error", "silly", user, null, {
|
||||
operationname: operationName,
|
||||
time,
|
||||
dbevent,
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user