Compare commits

...

20 Commits

Author SHA1 Message Date
Allan Carr
30ca34ea93 IO-2971 Export Table Size limit to 10
Signed-off-by: Allan Carr <allan.carr@thinkimex.com>
2024-10-16 13:43:25 -07:00
Dave Richer
aedad1c48f Merged in release/2024-10-11 (pull request #1814)
release/2024-10-11: Hotfix
2024-10-12 16:28:40 +00:00
Dave Richer
05cc4dd188 release/2024-10-11: Hotfix
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-12 12:26:30 -04:00
Dave Richer
ea6351ea06 Merged in release/2024-10-11 (pull request #1813)
release/2024-10-11: Hotfix
2024-10-12 16:06:06 +00:00
Dave Richer
87d3ceb408 release/2024-10-11: Hotfix
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-12 12:05:13 -04:00
Dave Richer
d08dd2b506 Merged in release/2024-10-11 (pull request #1812)
release/2024-10-11: Final touchups
2024-10-12 03:59:37 +00:00
Dave Richer
8a047d14a1 release/2024-10-11: Final touchups
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-11 23:51:39 -04:00
Dave Richer
e103772aa4 Merged in release/2024-10-11 (pull request #1811)
Release/2024-10-11 into master-AIO - IO-2791, IO-2962, IO-2971, IO-2972, IO-2979
2024-10-12 03:09:09 +00:00
Dave Richer
c332699dc8 Merge branch 'release/2024-10-11' of bitbucket.org:snaptsoft/bodyshop into release/2024-10-11 2024-10-11 23:02:17 -04:00
Dave Richer
25e6e61d10 release/2024-10-11: Final touchups
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-11 22:59:44 -04:00
Patrick Fic
cdcd6b636a Merged in feature/IO-2971-export-mutation-refactor (pull request #1809)
IO-2791 Stop gap change to limit exports to 10 records at a time.

Approved-by: Dave Richer
2024-10-11 20:07:59 +00:00
Patrick Fic
7879591bcf IO-2971 add null coalescing 2024-10-11 16:05:30 -04:00
Patrick Fic
7fc6556866 IO-2791 Stop gap change to limit exports to 10 records at a time. 2024-10-11 16:03:40 -04:00
Dave Richer
3f5489ce7e Merged in feature/IO-2979-DST-Handling (pull request #1808)
feature/IO-2979-DST-Handling - Checkpoint
2024-10-11 17:18:33 +00:00
Dave Richer
5a90854861 feature/IO-2979-DST-Handling
- Checkpoint

Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-10 13:01:36 -04:00
Dave Richer
8347a8c098 Merged in feature/IO-2979-DST-Handling (pull request #1806)
feature/IO-2979-DST-Handling - Add LocalStack and Adjust local Emailing
2024-10-09 17:03:19 +00:00
Dave Richer
2bf074d85a feature/IO-2979-DST-Handling
- Add LocalStack and Adjust local Emailing

Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-10-09 13:00:16 -04:00
Dave Richer
50d47cd679 Merged in feature/IO-2962-Task-Email-Footer-Timestamps (pull request #1804)
feature/IO-2962-Task-Email-Footer-Timestamps - Localize Date in Task Email Footer
2024-10-07 20:22:13 +00:00
Dave Richer
3a4e06eaa2 Merged in feature/IO-2972-Final-Redis-Sockets-Fixes (pull request #1803)
Feature/IO-2972 Final Redis Sockets Fixes
2024-10-07 20:21:41 +00:00
Dave Richer
74d95e7cbb feature/IO-2962-Task-Email-Footer-Timestamps - Localize Date in Task Email Footer
Signed-off-by: Dave Richer <dave@imexsystems.ca>
2024-09-27 16:27:41 -04:00
17 changed files with 233 additions and 96 deletions

View File

@@ -8,7 +8,6 @@ client
redis/dockerdata
hasura
node_modules
# Files to exclude
.ebignore
.editorconfig

View File

@@ -25,14 +25,17 @@ RUN dnf install -y \
# Set the working directory
WORKDIR /app
# This is because our test route uses a git commit hash
RUN git config --global --add safe.directory /app
# Copy package.json and package-lock.json
COPY package*.json ./
COPY package.json ./
# Install Nodemon
RUN npm install -g nodemon
# Install dependencies
RUN npm install --omit=dev
RUN npm i --no-package-lock
# Copy the rest of your application code
COPY . .

View File

@@ -138,6 +138,10 @@ sudo sysctl -p
# Install Docker and Docker Compose in WSL2
- https://docs.docker.com/desktop/wsl/
# Local Stack
- LocalStack Front end (Optional) - https://apps.microsoft.com/detail/9ntrnft9zws2?hl=en-us&gl=US
- http://localhost:4566/_aws/ses will allow you to see emails sent
# Docker Commands
## General `docker-compose` Commands:
@@ -150,6 +154,7 @@ sudo sysctl -p
7. View running Containers: `docker-compose ps`
8. View a specific containers logs: `docker-compose logs <container-name>`
9. Scale services (multiple instances of a service): `docker-compose up --scale <container-name>=<instances number> -d`
10. Watch a specific containers logs in realtime with timestamps: `docker-compose logs -f --timestamps <container-name>`
## Volume Management Commands
1. List Docker volumes: `docker volume ls`

View File

@@ -9,7 +9,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { pageLimit } from "../../utils/config";
import { exportPageLimit } from "../../utils/config";
import { alphaSort, dateSort } from "../../utils/sorters";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import PayableExportAll from "../payable-export-all-button/payable-export-all-button.component";
@@ -175,7 +175,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, bills, ref
<Table
loading={loading}
dataSource={dataSource}
pagination={{ position: "top", pageSize: pageLimit }}
pagination={{ position: "top", pageSize: exportPageLimit }}
columns={columns}
rowKey="id"
onChange={handleTableChange}

View File

@@ -8,7 +8,7 @@ import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop } from "../../redux/user/user.selectors";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter, DateTimeFormatter } from "../../utils/DateFormatter";
import { pageLimit } from "../../utils/config";
import { exportPageLimit } from "../../utils/config";
import { alphaSort, dateSort } from "../../utils/sorters";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
@@ -177,7 +177,7 @@ export function AccountingPayablesTableComponent({ bodyshop, loading, payments,
<Table
loading={loading}
dataSource={dataSource}
pagination={{ position: "top", pageSize: pageLimit }}
pagination={{ position: "top", pageSize: exportPageLimit }}
columns={columns}
rowKey="id"
onChange={handleTableChange}

View File

@@ -1,18 +1,18 @@
import { Button, Card, Input, Space, Table } from "antd";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { connect } from "react-redux";
import { Link } from "react-router-dom";
import { createStructuredSelector } from "reselect";
import { logImEXEvent } from "../../firebase/firebase.utils";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { exportPageLimit } from "../../utils/config";
import CurrencyFormatter from "../../utils/CurrencyFormatter";
import { DateFormatter } from "../../utils/DateFormatter";
import { alphaSort, dateSort, statusSort } from "../../utils/sorters";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import JobExportButton from "../jobs-close-export-button/jobs-close-export-button.component";
import JobsExportAllButton from "../jobs-export-all-button/jobs-export-all-button.component";
import { connect } from "react-redux";
import { createStructuredSelector } from "reselect";
import { selectBodyshop } from "../../redux/user/user.selectors";
import { DateFormatter } from "../../utils/DateFormatter";
import ExportLogsCountDisplay from "../export-logs-count-display/export-logs-count-display.component";
import JobMarkSelectedExported from "../jobs-mark-selected-exported/jobs-mark-selected-exported";
import OwnerNameDisplay, { OwnerNameDisplayFunction } from "../owner-name-display/owner-name-display.component";
import QboAuthorizeComponent from "../qbo-authorize/qbo-authorize.component";
@@ -201,7 +201,7 @@ export function AccountingReceivablesTableComponent({ bodyshop, loading, jobs, r
<Table
loading={loading}
dataSource={dataSource}
pagination={{ position: "top" }}
pagination={{ position: "top", pageSize: exportPageLimit }}
columns={columns}
rowKey="id"
onChange={handleTableChange}

View File

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

View File

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

View File

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

View File

@@ -314,8 +314,8 @@ export function* SetAuthLevelFromShopDetails({ payload }) {
try {
const userEmail = yield select((state) => state.user.currentUser.email);
try {
//console.log("Setting shop timezone.");
// dayjs.tz.setDefault(payload.timezone);
console.log("Setting shop timezone.");
day.tz.setDefault(payload.timezone);
} catch (error) {
console.log(error);
}

View File

@@ -1,3 +1,4 @@
// Sometimes referred to as PageSize, this variable controls the amount of records
// to show on one page during pagination.
export const pageLimit = 50;
export const exportPageLimit = 10;

View File

@@ -1,17 +1,20 @@
#############################
# Ports Exposed
# 4000 - Imex Node API
# 3333 - SocketIO Admin-UI
# 3334 - Redis-Insights
# 4000 - Imex Node API
# 4556 - LocalStack (Local AWS)
# 3333 - SocketIO Admin-UI (Optional)
# 3334 - Redis-Insights (Optional)
#############################
services:
# Redis Node 1
redis-node-1:
build:
context: ./redis
container_name: redis-node-1
hostname: redis-node-1
restart: always
restart: unless-stopped
networks:
- redis-cluster-net
volumes:
@@ -22,13 +25,14 @@ services:
interval: 10s
timeout: 5s
retries: 10
# Redis Node 2
redis-node-2:
build:
context: ./redis
container_name: redis-node-2
hostname: redis-node-2
restart: always
restart: unless-stopped
networks:
- redis-cluster-net
volumes:
@@ -40,12 +44,13 @@ services:
timeout: 5s
retries: 10
# Redis Node 3
redis-node-3:
build:
context: ./redis
container_name: redis-node-3
hostname: redis-node-3
restart: always
restart: unless-stopped
networks:
- redis-cluster-net
volumes:
@@ -57,6 +62,56 @@ services:
timeout: 5s
retries: 10
# LocalStack: Used to emulate AWS services locally, currently setup for SES
# Notes: Set the ENV Debug to 1 for additional logging
localstack:
image: localstack/localstack
container_name: localstack
hostname: localstack
networks:
- redis-cluster-net
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
environment:
- SERVICES=ses
- DEBUG=0
- AWS_ACCESS_KEY_ID=test
- AWS_SECRET_ACCESS_KEY=test
- AWS_DEFAULT_REGION=ca-central-1
- EXTRA_CORS_ALLOWED_HEADERS=Authorization,Content-Type
- EXTRA_CORS_ALLOWED_ORIGINS=*
- EXTRA_CORS_EXPOSE_HEADERS=Authorization,Content-Type
ports:
- "4566:4566"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:4566/_localstack/health"]
interval: 10s
timeout: 5s
retries: 5
start_period: 20s
# AWS-CLI - Used in conjunction with LocalStack to set required permission to send emails
aws-cli:
image: amazon/aws-cli
container_name: aws-cli
hostname: aws-cli
networks:
- redis-cluster-net
depends_on:
localstack:
condition: service_healthy
environment:
- AWS_ACCESS_KEY_ID=test
- AWS_SECRET_ACCESS_KEY=test
- AWS_DEFAULT_REGION=ca-central-1
entrypoint: /bin/sh -c
command: >
"
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
"
# Node App: The Main IMEX API
node-app:
build:
context: .
@@ -73,37 +128,44 @@ services:
condition: service_healthy
redis-node-3:
condition: service_healthy
localstack:
condition: service_healthy
aws-cli:
condition: service_completed_successfully
ports:
- "4000:4000"
volumes:
- .:/app
- /app/node_modules
- node-app-npm-cache:/app/node_modules
socketio-admin-ui:
image: maitrungduc1410/socket.io-admin-ui
container_name: socketio-admin-ui
networks:
- redis-cluster-net
ports:
- "3333:80"
redis-insight:
image: redislabs/redisinsight:latest
container_name: redis-insight
hostname: redis-insight
restart: always
ports:
- "3334:5540"
networks:
- redis-cluster-net
volumes:
- redis-insight-data:/db
# ## Optional Container to Observe SocketIO data
# socketio-admin-ui:
# image: maitrungduc1410/socket.io-admin-ui
# container_name: socketio-admin-ui
# networks:
# - redis-cluster-net
# ports:
# - "3333:80"
# ##Optional Container to Observe Redis Cluster Data
# redis-insight:
# image: redislabs/redisinsight:latest
# container_name: redis-insight
# hostname: redis-insight
# restart: unless-stopped
# ports:
# - "3334:5540"
# networks:
# - redis-cluster-net
# volumes:
# - redis-insight-data:/db
networks:
redis-cluster-net:
driver: bridge
volumes:
node-app-npm-cache:
redis-node-1-data:
redis-node-2-data:
redis-node-3-data:

35
nodemon.json Normal file
View File

@@ -0,0 +1,35 @@
{
"watch": [
"server.js",
"server"
],
"ignore": [
"client",
".circleci",
".platform",
".idea",
".vscode",
"_reference",
"firebase",
"hasura",
"logs",
"redis",
".dockerignore",
".ebignore",
".editorconfig",
".env.development",
".env.development.rome",
".eslintrc.json",
".gitignore",
".npmmrc",
".prettierrc.js",
"bodyshop_translations.babel",
"Dockerfile",
"ecosystem.config.js",
"job-totals-testing-util.js",
"nodemon.json",
"package.json",
"package-lock.json",
"setadmin.js"
]
}

31
server/email/mailer.js Normal file
View File

@@ -0,0 +1,31 @@
const { isString, isEmpty } = require("lodash");
const { defaultProvider } = require("@aws-sdk/credential-provider-node");
const { default: InstanceManager } = require("../utils/instanceMgr");
const aws = require("@aws-sdk/client-ses");
const nodemailer = require("nodemailer");
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"
})
};
if (isLocal) {
sesConfig.endpoint = `http://${process.env.LOCALSTACK_HOSTNAME}:4566`;
console.log(`SES Mailer set to LocalStack end point: ${sesConfig.endpoint}`);
}
const ses = new aws.SES(sesConfig);
let transporter = nodemailer.createTransport({
SES: { ses, aws }
});
module.exports = transporter;

View File

@@ -3,30 +3,13 @@ require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const axios = require("axios");
let nodemailer = require("nodemailer");
let aws = require("@aws-sdk/client-ses");
let { defaultProvider } = require("@aws-sdk/credential-provider-node");
const InstanceManager = require("../utils/instanceMgr").default;
const logger = require("../utils/logger");
const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");
const { isObject } = require("lodash");
const generateEmailTemplate = require("./generateTemplate");
const moment = require("moment");
const ses = new aws.SES({
// The key apiVersion is no longer supported in v3, and can be removed.
// @deprecated The client uses the "latest" apiVersion.
apiVersion: "latest",
credentials: defaultProvider(),
region: InstanceManager({
imex: "ca-central-1",
rome: "us-east-2"
})
});
let transporter = nodemailer.createTransport({
SES: { ses, aws }
});
const mailer = require("./mailer");
// Get the image from the URL and return it as a base64 string
const getImage = async (imageUrl) => {
@@ -66,7 +49,7 @@ const logEmail = async (req, email) => {
const sendServerEmail = async ({ subject, text }) => {
if (process.env.NODE_ENV === undefined) return;
try {
transporter.sendMail(
mailer.sendMail(
{
from: InstanceManager({
imex: `ImEX Online API - ${process.env.NODE_ENV} <noreply@imex.online>`,
@@ -98,7 +81,7 @@ const sendServerEmail = async ({ subject, text }) => {
const sendProManagerWelcomeEmail = async ({ to, subject, html }) => {
try {
await transporter.sendMail({
await mailer.sendMail({
from: `ProManager <noreply@promanager.web-est.com>`,
to,
subject,
@@ -112,7 +95,7 @@ const sendProManagerWelcomeEmail = async ({ to, subject, html }) => {
const sendTaskEmail = async ({ to, subject, type = "text", html, text, attachments }) => {
try {
transporter.sendMail(
mailer.sendMail(
{
from: InstanceManager({
imex: `ImEX Online <noreply@imex.online>`,
@@ -166,7 +149,7 @@ const sendEmail = async (req, res) => {
);
}
transporter.sendMail(
mailer.sendMail(
{
from: `${req.body.from.name} <${req.body.from.address}>`,
replyTo: req.body.ReplyTo.Email,
@@ -280,7 +263,7 @@ const emailBounce = async (req, res) => {
status: "Bounced",
context: message.bounce?.bouncedRecipients
});
transporter.sendMail(
mailer.sendMail(
{
from: InstanceMgr({
imex: `ImEX Online <noreply@imex.online>`,

View File

@@ -2,30 +2,14 @@ const path = require("path");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
let nodemailer = require("nodemailer");
let aws = require("@aws-sdk/client-ses");
let { defaultProvider } = require("@aws-sdk/credential-provider-node");
const InstanceManager = require("../utils/instanceMgr").default;
const logger = require("../utils/logger");
const client = require("../graphql-client/graphql-client").client;
const queries = require("../graphql-client/queries");
const generateEmailTemplate = require("./generateTemplate");
const moment = require("moment");
const moment = require("moment-timezone");
const { taskEmailQueue } = require("./tasksEmailsQueue");
const ses = new aws.SES({
apiVersion: "latest",
credentials: defaultProvider(),
region: InstanceManager({
imex: "ca-central-1",
rome: "us-east-2"
})
});
const transporter = nodemailer.createTransport({
SES: { ses, aws },
sendingRate: InstanceManager({ imex: 40, rome: 10 })
});
const mailer = require("./mailer");
// Initialize the Tasks Email Queue
const tasksEmailQueue = taskEmailQueue();
@@ -45,20 +29,22 @@ if (process.env.NODE_ENV !== "development") {
// Handling SIGINT (e.g., Ctrl+C)
process.on("SIGINT", async () => {
await tasksEmailQueueCleanup();
process.exit(0);
});
// Handling SIGTERM (e.g., sent by system shutdown)
process.on("SIGTERM", async () => {
await tasksEmailQueueCleanup();
process.exit(0);
});
// Handling uncaught exceptions
process.on("uncaughtException", async (err) => {
await tasksEmailQueueCleanup();
throw err;
process.exit(1);
});
// Handling unhandled promise rejections
process.on("unhandledRejection", async (reason, promise) => {
await tasksEmailQueueCleanup();
throw reason;
process.exit(1);
});
}
@@ -106,12 +92,13 @@ const getEndpoints = (bodyshop) =>
: "https://romeonline.io"
});
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId) => {
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId, dateLine) => {
const endPoints = getEndpoints(bodyshop);
return {
header: title,
subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)}`,
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>`
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
};
};
@@ -123,6 +110,7 @@ const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, j
* @param html
* @param taskIds
* @param successCallback
* @param requestInstance
*/
const sendMail = (type, to, subject, html, taskIds, successCallback, requestInstance) => {
const fromEmails = InstanceManager({
@@ -133,7 +121,7 @@ const sendMail = (type, to, subject, html, taskIds, successCallback, requestInst
: "Rome Online <noreply@romeonline.io>"
});
transporter.sendMail(
mailer.sendMail(
{
from: fromEmails,
to,
@@ -150,8 +138,6 @@ const sendMail = (type, to, subject, html, taskIds, successCallback, requestInst
}
}
);
// }
// });
};
/**
@@ -176,6 +162,8 @@ const taskAssignedEmail = async (req, res) => {
id: newTask.id
});
const dateLine = moment().tz(tasks_by_pk.bodyshop.timezone).format("M/DD/YYYY @ hh:mm a");
sendMail(
"assigned",
tasks_by_pk.assigned_to_employee.user_email,
@@ -188,7 +176,8 @@ const taskAssignedEmail = async (req, res) => {
newTask.due_date,
tasks_by_pk.bodyshop,
tasks_by_pk.job,
newTask.id
newTask.id,
dateLine
)
),
null,
@@ -257,6 +246,8 @@ const tasksRemindEmail = async (req, res) => {
const taskIds = groupedTasks[recipient.email].map((task) => task.id);
const dateLine = moment().tz(tasksRequest?.tasks[0].bodyshop.timezone).format("M/DD/YYYY @ hh:mm a");
// There is only the one email to send to this author.
if (recipient.count === 1) {
const onlyTask = groupedTasks[recipient.email][0];
@@ -272,7 +263,8 @@ const tasksRemindEmail = async (req, res) => {
onlyTask.due_date,
onlyTask.bodyshop,
onlyTask.job,
onlyTask.id
onlyTask.id,
dateLine
)
);
}
@@ -295,6 +287,7 @@ const tasksRemindEmail = async (req, res) => {
emailData.html = generateEmailTemplate({
header: `${allTasks.length} Tasks require your attention`,
subHeader: `Please click on the Tasks below to view the Task.`,
dateLine,
body: `<ul>
${allTasks
.map((task) =>
@@ -333,6 +326,29 @@ const tasksRemindEmail = async (req, res) => {
}
};
// Note: Uncomment this to test locally, it will call the remind_at email check every 20 seconds
// const callTaskRemindEmailInternally = () => {
// const req = {
// body: {
// // You can mock any request data here if needed
// }
// };
//
// const res = {
// status: (code) => {
// return {
// json: (data) => {
// console.log(`Response Status: ${code}`, data);
// }
// };
// }
// };
//
// // Call the taskRemindEmail function with mock req and res
// tasksRemindEmail(req, res);
// };
// setInterval(callTaskRemindEmailInternally, 20000);
module.exports = {
taskAssignedEmail,
tasksRemindEmail,

View File

@@ -2489,6 +2489,7 @@ exports.QUERY_REMIND_TASKS = `
bodyshop {
shopname
convenient_company
timezone
}
bodyshopid
}
@@ -2512,6 +2513,7 @@ query QUERY_TASK_BY_ID($id: uuid!) {
bodyshop{
shopname
convenient_company
timezone
}
job{
ro_number