Merge remote-tracking branch 'origin/release/2024-09-27' into feature/IO-2742-redis

This commit is contained in:
Dave Richer
2024-09-24 14:21:51 -04:00
115 changed files with 8098 additions and 4099 deletions

View File

@@ -94,7 +94,10 @@ exports.default = async (req, res) => {
ret.push({
billid: bill.id,
success: false,
errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message)
errorMessage:
(error && error.authResponse && error.authResponse.body) ||
error.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
(error && error.message)
});
//Add the export log error.
@@ -191,7 +194,9 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
bodyshop.md_responsibility_centers.sales_tax_codes,
classes,
taxCodes,
bodyshop.md_responsibility_centers.costs
bodyshop.md_responsibility_centers.costs,
bodyshop.accountingconfig,
bodyshop.region_config
)
);
@@ -209,14 +214,14 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
AccountBasedExpenseLineDetail: {
...(bill.job.class ? { ClassRef: { value: classes[bill.job.class] } } : {}),
AccountRef: {
value: accounts[bodyshop.md_responsibility_centers.taxes.federal.accountdesc]
value: accounts[bodyshop.md_responsibility_centers.taxes.federal_itc.accountdesc]
}
},
Amount: Dinero({
amount: Math.round(
bill.billlines.reduce((acc, val) => {
return acc + val.actual_cost * val.quantity;
return acc + (val.applicable_taxes?.federal ? (val.actual_cost * val.quantity ?? 0) : 0);
}, 0) * 100
)
})
@@ -274,6 +279,8 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
} catch (error) {
logger.log("qbo-payables-error", "DEBUG", req.user.email, bill.id, {
error: error, //(error && error.authResponse && error.authResponse.body) || (error && error.message),
validationError: JSON.stringify(error?.response?.data),
accountmeta: JSON.stringify({ accounts, taxCodes, classes }),
method: "InsertBill"
});
throw error;
@@ -293,17 +300,29 @@ async function InsertBill(oauthClient, qbo_realmId, req, bill, vendor, bodyshop)
// },
// ],
const generateBillLine = (billLine, accounts, jobClass, ioSalesTaxCodes, classes, taxCodes, costCenters) => {
const generateBillLine = (
billLine,
accounts,
jobClass,
ioSalesTaxCodes,
classes,
taxCodes,
costCenters,
accountingconfig,
region_config
) => {
const account = costCenters.find((c) => c.name === billLine.cost_center);
return {
DetailType: "AccountBasedExpenseLineDetail",
AccountBasedExpenseLineDetail: {
...(jobClass ? { ClassRef: { value: classes[jobClass] } } : {}),
TaxCodeRef: {
value: taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)]
},
TaxCodeRef:
accountingconfig.qbo && accountingconfig.qbo_usa && region_config.includes("CA_")
? {}
: {
value: taxCodes[findTaxCode(billLine.applicable_taxes, ioSalesTaxCodes)]
},
AccountRef: {
value: accounts[account.accountname]
}

View File

@@ -179,7 +179,11 @@ exports.default = async (req, res) => {
ret.push({
jobid: job.id,
success: false,
errorMessage: (error && error.authResponse && error.authResponse.body) || (error && error.message)
errorMessage:
error?.authResponse?.body ||
error?.response?.data?.Fault?.Error.map((e) => e.Detail).join(", ") ||
error?.response?.data ||
error?.message
});
console.log(error);
logger.log("qbo-receivable-create-error", "ERROR", req.user.email, {
@@ -254,7 +258,6 @@ async function InsertInsuranceCo(oauthClient, qbo_realmId, req, job, bodyshop) {
throw new Error(
`Insurance Company '${job.ins_co_nm}' not found in shop configuration. Please make sure it exists or change the insurance company name on the job to one that exists.`
);
return;
}
const Customer = {
DisplayName: job.ins_co_nm.trim(),
@@ -575,7 +578,9 @@ async function InsertInvoice(oauthClient, qbo_realmId, req, job, bodyshop, paren
} catch (error) {
logger.log("qbo-receivables-error", "DEBUG", req.user.email, job.id, {
error,
method: "InsertOwner"
method: "InsertInvoice",
validationError: JSON.stringify(error?.response?.data),
accountmeta: JSON.stringify({ items, taxCodes, classes })
});
throw error;
}

View File

@@ -54,13 +54,6 @@ async function calculateAllocations(connectionData, job) {
deubg: true,
args: [],
imex: () => ({
local: {
center: bodyshop.md_responsibility_centers.taxes.local.name,
sale: Dinero(job.job_totals.totals.local_tax),
cost: Dinero(),
profitCenter: bodyshop.md_responsibility_centers.taxes.local,
costCenter: bodyshop.md_responsibility_centers.taxes.local
},
state: {
center: bodyshop.md_responsibility_centers.taxes.state.name,
sale: Dinero(job.job_totals.totals.state_tax),

168
server/data/chatter.js Normal file
View File

@@ -0,0 +1,168 @@
const path = require("path");
const queries = require("../graphql-client/queries");
const moment = require("moment-timezone");
const converter = require("json-2-csv");
const _ = require("lodash");
const logger = require("../utils/logger");
const fs = require("fs");
const { SecretsManagerClient, GetSecretValueCommand } = require("@aws-sdk/client-secrets-manager");
require("dotenv").config({ path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`) });
let Client = require("ssh2-sftp-client");
const client = require("../graphql-client/graphql-client").client;
const { sendServerEmail } = require("../email/sendemail");
const ftpSetup = {
host: process.env.CHATTER_HOST,
port: process.env.CHATTER_PORT,
username: process.env.CHATTER_USER,
privateKey: null,
debug: (message, ...data) => logger.log(message, "DEBUG", "api", null, data),
algorithms: {
serverHostKey: ["ssh-rsa", "ssh-dss", "rsa-sha2-256", "rsa-sha2-512", "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384"]
}
};
exports.default = async (req, res) => {
// Only process if in production environment.
if (process.env.NODE_ENV !== "production") {
res.sendStatus(403);
return;
}
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 = [];
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") })
});
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
});
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);
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();
}
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.sendStatus(200);
} catch (error) {
res.status(200).json(error);
}
};
async function getPrivateKey() {
// Connect to AWS Secrets Manager
const client = new SecretsManagerClient({ region: "ca-central-1" });
const command = new GetSecretValueCommand({ SecretId: CHATTER_PRIVATE_KEY });
logger.log("chatter-get-private-key", "DEBUG", "api", null, null);
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");
} catch (error) {
logger.log("chatter-get-private-key", "ERROR", "api", null, error);
throw err;
}
}

View File

@@ -1,4 +1,5 @@
exports.arms = require("./arms").default;
exports.autohouse = require("./autohouse").default;
exports.chatter = require("./chatter").default;
exports.claimscorp = require("./claimscorp").default;
exports.kaizen = require("./kaizen").default;

View File

@@ -96,7 +96,21 @@ const sendServerEmail = async ({ subject, text }) => {
}
};
const sendTaskEmail = async ({ to, subject, text, attachments }) => {
const sendProManagerWelcomeEmail = async ({to, subject, html}) => {
try {
await transporter.sendMail({
from: `ProManager <noreply@promanager.web-est.com>`,
to,
subject,
html
});
} catch (error) {
console.log(error);
logger.log("server-email-failure", "error", null, null, error);
}
};
const sendTaskEmail = async ({ to, subject, type = "text", html, text, attachments }) => {
try {
transporter.sendMail(
{
@@ -107,7 +121,7 @@ const sendTaskEmail = async ({ to, subject, text, attachments }) => {
}),
to: to,
subject: subject,
text: text,
...(type === "text" ? { text } : { html }),
attachments: attachments || null
},
(err, info) => {
@@ -309,5 +323,6 @@ module.exports = {
sendEmail,
sendServerEmail,
sendTaskEmail,
sendProManagerWelcomeEmail,
emailBounce
};

View File

@@ -96,11 +96,12 @@ const formatPriority = (priority) => {
* @param taskId
* @returns {{header, body: string, subHeader: string}}
*/
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId) => {
const endPoints = InstanceManager({
const getEndpoints = (bodyshop) =>
InstanceManager({
imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online",
rome:
bodyshop.convenient_company === "promanager"
bodyshop?.convenient_company === "promanager"
? process.env?.NODE_ENV === "test"
? "https//test.promanager.web-est.com"
: "https://promanager.web-est.com"
@@ -108,6 +109,9 @@ const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, j
? "https//test.romeonline.io"
: "https://romeonline.io"
});
const generateTemplateArgs = (title, priority, description, dueDate, bodyshop, job, taskId) => {
const endPoints = getEndpoints(bodyshop);
return {
header: title,
subHeader: `Body Shop: ${bodyshop.shopname} | Priority: ${formatPriority(priority)} ${formatDate(dueDate)}`,
@@ -245,7 +249,7 @@ const tasksRemindEmail = async (req, res) => {
const fromEmails = InstanceManager({
imex: "ImEX Online <noreply@imex.online>",
rome:
onlyTask.bodyshop.convenient_company === "promanager"
tasksRequest?.tasks[0].bodyshop.convenient_company === "promanager"
? "ProManager <noreply@promanager.web-est.com>"
: "Rome Online <noreply@romeonline.io>"
});
@@ -281,7 +285,7 @@ const tasksRemindEmail = async (req, res) => {
const endPoints = InstanceManager({
imex: process.env?.NODE_ENV === "test" ? "https://test.imex.online" : "https://imex.online",
rome:
allTasks[0].bodyshop.convenient_company === "promanager"
tasksRequest?.tasks[0].bodyshop.convenient_company === "promanager"
? process.env?.NODE_ENV === "test"
? "https//test.promanager.web-est.com"
: "https://promanager.web-est.com"
@@ -318,7 +322,7 @@ const tasksRemindEmail = async (req, res) => {
tasksEmailQueue.push(taskId);
}
},
allTasks[0].bodyshop.convenient_company
tasksRequest?.tasks[0].bodyshop.convenient_company
);
}
});
@@ -335,5 +339,6 @@ const tasksRemindEmail = async (req, res) => {
module.exports = {
taskAssignedEmail,
tasksRemindEmail
tasksRemindEmail,
getEndpoints
};

View File

@@ -1,30 +1,28 @@
const admin = require("firebase-admin");
const logger = require("../utils/logger");
const path = require("path");
const { auth } = require("firebase-admin");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
const client = require("../graphql-client/graphql-client").client;
const admin = require("firebase-admin");
const logger = require("../utils/logger");
const { sendProManagerWelcomeEmail } = require("../email/sendemail");
const client = require("../graphql-client/graphql-client").client;
const serviceAccount = require(process.env.FIREBASE_ADMINSDK_JSON);
const adminEmail = require("../utils/adminEmail");
const generateEmailTemplate = require("../email/generateTemplate");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: process.env.FIREBASE_DATABASE_URL
});
exports.admin = admin;
exports.createUser = async (req, res) => {
const createUser = async (req, res) => {
logger.log("admin-create-user", "ADMIN", req.user.email, null, {
request: req.body,
ioadmin: true
});
const { email, displayName, password, shopid, authlevel } = req.body;
const { email, displayName, password, shopid, authlevel, validemail } = req.body;
try {
const userRecord = await admin.auth().createUser({ email, displayName, password });
@@ -42,6 +40,7 @@ exports.createUser = async (req, res) => {
user: {
email: email.toLowerCase(),
authid: userRecord.uid,
validemail,
associations: {
data: [{ shopid, authlevel, active: true }]
}
@@ -58,21 +57,115 @@ exports.createUser = async (req, res) => {
}
};
exports.updateUser = (req, res) => {
const sendPromanagerWelcomeEmail = (req, res) => {
const { authid, email } = req.body;
// Fetch user from Firebase
admin
.auth()
.getUser(authid)
.then((userRecord) => {
if (!userRecord) {
return Promise.reject({ status: 404, message: "User not found in Firebase." });
}
// Fetch user data from the database using GraphQL
return client.request(
`
query GET_USER_BY_EMAIL($email: String!) {
users(where: { email: { _eq: $email } }) {
email
validemail
associations {
id
shopid
bodyshop {
id
convenient_company
}
}
}
}`,
{ email: email.toLowerCase() }
);
})
.then((dbUserResult) => {
const dbUser = dbUserResult?.users?.[0];
if (!dbUser) {
return Promise.reject({ status: 404, message: "User not found in database." });
}
// Validate email before proceeding
if (!dbUser.validemail) {
logger.log("admin-send-welcome-email-skip", "ADMIN", req.user.email, null, {
message: "User email is not valid, skipping email.",
email
});
return res.status(200).json({ message: "User email is not valid, email not sent." });
}
// Check if the user's company is ProManager
const convenientCompany = dbUser.associations?.[0]?.bodyshop?.convenient_company;
if (convenientCompany !== "promanager") {
logger.log("admin-send-welcome-email-skip", "ADMIN", req.user.email, null, {
message: 'convenient_company is not "promanager", skipping email.',
convenientCompany
});
return res.status(200).json({ message: `convenient_company is not "promanager", email not sent.` });
}
// Generate password reset link
return admin
.auth()
.generatePasswordResetLink(dbUser.email)
.then((resetLink) => ({ dbUser, resetLink }));
})
.then(({ dbUser, resetLink }) => {
// Send welcome email (replace with your actual email-sending service)
return sendProManagerWelcomeEmail({
to: dbUser.email,
subject: "Welcome to the ProManager platform.",
html: generateEmailTemplate({
header: "",
subHeader: "",
body: `
<p>Welcome to the ProManager platform. Please click the link below to reset your password:</p>
<p><a href="${resetLink}">Reset your password</a></p>
<p>User Details:</p>
<ul>
<li>Email: ${dbUser.email}</li>
</ul>
`
})
});
})
.then(() => {
// Log success and return response
logger.log("admin-send-welcome-email", "ADMIN", req.user.email, null, {
request: req.body,
ioadmin: true,
emailSentTo: email
});
res.status(200).json({ message: "Welcome email sent successfully." });
})
.catch((error) => {
logger.log("admin-send-welcome-email-error", "ERROR", req.user.email, null, { error });
if (!res.headersSent) {
res.status(error.status || 500).json({
message: error.message || "Error sending welcome email.",
error
});
}
});
};
const updateUser = (req, res) => {
logger.log("admin-update-user", "ADMIN", req.user.email, null, {
request: req.body,
ioadmin: true
});
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
logger.log("admin-update-user-unauthorized", "ERROR", req.user.email, null, {
request: req.body,
user: req.user
});
res.sendStatus(404);
return;
}
admin
.auth()
.updateUser(
@@ -105,26 +198,45 @@ exports.updateUser = (req, res) => {
});
};
exports.getUser = (req, res) => {
const getUser = (req, res) => {
logger.log("admin-get-user", "ADMIN", req.user.email, null, {
request: req.body,
ioadmin: true
});
if (!adminEmail.includes(req.user.email) && !req.user.ioadmin) {
logger.log("admin-update-user-unauthorized", "ERROR", req.user.email, null, {
request: req.body,
user: req.user
});
res.sendStatus(404);
return;
}
admin
.auth()
.getUser(req.body.uid)
.then((userRecord) => {
res.json(userRecord);
return client
.request(
`
query GET_USER_BY_AUTHID($authid: String!) {
users(where: { authid: { _eq: $authid } }) {
email
validemail
associations {
id
shopid
bodyshop {
id
convenient_company
}
}
}
}
`,
{ authid: req.body.uid }
)
.then((dbUserResult) => {
res.json({
...userRecord,
db: {
validemail: dbUserResult?.users?.[0]?.validemail,
company: dbUserResult?.users?.[0]?.associations?.[0]?.bodyshop?.convenient_company
}
});
});
})
.catch((error) => {
logger.log("admin-get-user-error", "ERROR", req.user.email, null, {
@@ -134,7 +246,7 @@ exports.getUser = (req, res) => {
});
};
exports.sendNotification = async (req, res) => {
const sendNotification = async (req, res) => {
setTimeout(() => {
// Send a message to the device corresponding to the provided
// registration token.
@@ -167,7 +279,7 @@ exports.sendNotification = async (req, res) => {
}, 500);
};
exports.subscribe = async (req, res) => {
const subscribe = async (req, res) => {
const result = await admin
.messaging()
.subscribeToTopic(req.body.fcm_tokens, `${req.body.imexshopid}-${req.body.type}`);
@@ -175,7 +287,7 @@ exports.subscribe = async (req, res) => {
res.json(result);
};
exports.unsubscribe = async (req, res) => {
const unsubscribe = async (req, res) => {
try {
const result = await admin
.messaging()
@@ -187,6 +299,17 @@ exports.unsubscribe = async (req, res) => {
}
};
module.exports = {
admin,
createUser,
updateUser,
getUser,
sendPromanagerWelcomeEmail,
sendNotification,
subscribe,
unsubscribe
};
//Admin claims code.
// const uid = "JEqqYlsadwPEXIiyRBR55fflfko1";

View File

@@ -832,6 +832,25 @@ exports.AUTOHOUSE_QUERY = `query AUTOHOUSE_EXPORT($start: timestamptz, $bodyshop
}
}`;
exports.CHATTER_QUERY = `query CHATTER_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) {
bodyshops_by_pk(id: $bodyshopid){
id
shopname
chatterid
timezone
}
jobs(where: {_and: [{converted: {_eq: true}}, {actual_delivery: {_gt: $start}}, {actual_delivery: {_lte: $end}}, {shopid: {_eq: $bodyshopid}}, {_or: [{ownr_ph1: {_is_null: false}}, {ownr_ea: {_is_null: false}}]}]}) {
id
created_at
ro_number
ownr_fn
ownr_ln
ownr_co_nm
ownr_ph1
ownr_ea
}
}`;
exports.CLAIMSCORP_QUERY = `query CLAIMSCORP_EXPORT($start: timestamptz, $bodyshopid: uuid!, $end: timestamptz) {
bodyshops_by_pk(id: $bodyshopid){
id
@@ -1732,6 +1751,16 @@ exports.GET_AUTOHOUSE_SHOPS = `query GET_AUTOHOUSE_SHOPS {
}
}`;
exports.GET_CHATTER_SHOPS = `query GET_CHATTER_SHOPS {
bodyshops(where: {chatterid: {_is_null: false}, _or: {chatterid: {_neq: ""}}}){
id
shopname
chatterid
imexshopid
timezone
}
}`;
exports.GET_CLAIMSCORP_SHOPS = `query GET_CLAIMSCORP_SHOPS {
bodyshops(where: {claimscorpid: {_is_null: false}, _or: {claimscorpid: {_neq: ""}}}){
id
@@ -2502,6 +2531,13 @@ exports.GET_JOBS_BY_PKS = `query GET_JOBS_BY_PKS($ids: [uuid!]!) {
jobs(where: {id: {_in: $ids}}) {
id
shopid
ro_number
ownr_co_nm
ownr_fn
ownr_ln
v_make_desc
v_model_yr
v_model_desc
}
}
`;

View File

@@ -7,7 +7,9 @@ const axios = require("axios");
const moment = require("moment");
const logger = require("../utils/logger");
const InstanceManager = require("../utils/instanceMgr").default;
const { sendTaskEmail } = require("../email/sendemail");
const generateEmailTemplate = require("../email/generateTemplate");
const { getEndpoints } = require("../email/tasksEmails");
require("dotenv").config({
path: path.resolve(process.cwd(), `.env.${process.env.NODE_ENV || "development"}`)
});
@@ -129,6 +131,7 @@ exports.generate_payment_url = async (req, res) => {
//...req.body,
amount: Dinero({ amount: Math.round(req.body.amount * 100) }).toFormat("0.00"),
account: req.body.account,
comment: req.body.comment,
invoice: req.body.invoice,
createshorturl: true
//The postback URL is set at the CP teller global terminal settings page.
@@ -162,7 +165,75 @@ exports.postback = async (req, res) => {
return;
}
if (values.invoice) {
if (comment) {
//Shifted the order to have this first to retain backwards compatibility for the old style of short link.
//This has been triggered by IO and may have multiple jobs.
const parsedComment = JSON.parse(comment);
//Adding in the user email to the short pay email.
//Need to check this to ensure backwards compatibility for clients that don't update.
const partialPayments = Array.isArray(parsedComment) ? parsedComment : parsedComment.payments;
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
ids: partialPayments.map((p) => p.jobid)
});
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: partialPayments.map((p) => ({
amount: p.amount,
transactionid: values.authcode,
payer: "Customer",
type: values.cardtype,
jobid: p.jobid,
date: moment(Date.now()),
payment_responses: {
data: {
amount: values.total,
bodyshopid: jobs.jobs[0].shopid,
jobid: p.jobid,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
}
}))
});
logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, JSON.stringify(jobs), {
iprequest: values,
paymentResult
});
if (values.origin === "OneLink" && parsedComment.userEmail) {
//Send an email, it was a text to pay link.
try {
const endPoints = getEndpoints();
sendTaskEmail({
to: parsedComment.userEmail,
subject: `New Payment(s) Received - RO ${jobs.jobs.map((j) => j.ro_number).join(", ")}`,
type: "html",
html: generateEmailTemplate({
header: "New Payment(s) Received",
subHeader: "",
body: jobs.jobs
.map(
(job) =>
`Reference: <a href="${endPoints}/manage/jobs/${job.id}">${job.ro_number || "N/A"}</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()} | $${partialPayments.find((p) => p.jobid === job.id).amount}`
)
.join("<br/>")
})
});
} catch (error) {
logger.log("intellipay-postback-app-email-error", "DEBUG", req.user?.email, JSON.stringify(jobs), {
iprequest: values,
paymentResult,
error: error.message
});
}
}
res.sendStatus(200);
} else if (values.invoice) {
//This is a link email that's been sent out.
const job = await gqlClient.request(queries.GET_JOB_BY_PK, {
id: values.invoice
@@ -192,48 +263,15 @@ exports.postback = async (req, res) => {
}
});
logger.log("intellipay-postback-link-success", "DEBUG", req.user?.email, null, {
logger.log("intellipay-postback-link-success", "DEBUG", req.user?.email, values.invoice, {
iprequest: values,
responseResults,
paymentResult
});
res.sendStatus(200);
} else if (comment) {
//This has been triggered by IO and may have multiple jobs.
const partialPayments = JSON.parse(comment);
const jobs = await gqlClient.request(queries.GET_JOBS_BY_PKS, {
ids: partialPayments.map((p) => p.jobid)
});
const paymentResult = await gqlClient.request(queries.INSERT_NEW_PAYMENT, {
paymentInput: partialPayments.map((p) => ({
amount: p.amount,
transactionid: values.authcode,
payer: "Customer",
type: values.cardtype,
jobid: p.jobid,
date: moment(Date.now()),
payment_responses: {
data: {
amount: values.total,
bodyshopid: jobs.jobs[0].shopid,
jobid: p.jobid,
declinereason: "Approved",
ext_paymentid: values.paymentid,
successful: true,
response: values
}
}
}))
});
logger.log("intellipay-postback-app-success", "DEBUG", req.user?.email, null, {
iprequest: values,
paymentResult
});
res.sendStatus(200);
}
} catch (error) {
logger.log("intellipay-postback-error", "ERROR", req.user?.email, null, {
logger.log("intellipay-postback-total-error", "ERROR", req.user?.email, null, {
error: JSON.stringify(error),
body: req.body
});

View File

@@ -1,18 +1,20 @@
const express = require("express");
const router = express.Router();
const fb = require("../firebase/firebase-handler");
const validateFirebaseIdTokenMiddleware = require("../middleware/validateFirebaseIdTokenMiddleware");
const { createAssociation, createShop, updateShop, updateCounter } = require("../admin/adminops");
const { updateUser, getUser, createUser, sendPromanagerWelcomeEmail } = require("../firebase/firebase-handler");
const validateAdminMiddleware = require("../middleware/validateAdminMiddleware");
router.use(validateFirebaseIdTokenMiddleware);
router.use(validateAdminMiddleware);
router.post("/createassociation", validateAdminMiddleware, createAssociation);
router.post("/createshop", validateAdminMiddleware, createShop);
router.post("/updateshop", validateAdminMiddleware, updateShop);
router.post("/updatecounter", validateAdminMiddleware, updateCounter);
router.post("/updateuser", fb.updateUser);
router.post("/getuser", fb.getUser);
router.post("/createuser", fb.createUser);
router.post("/createassociation", createAssociation);
router.post("/createshop", createShop);
router.post("/updateshop", updateShop);
router.post("/updatecounter", updateCounter);
router.post("/updateuser", updateUser);
router.post("/getuser", getUser);
router.post("/createuser", createUser);
router.post("/promanagerwelcome", sendPromanagerWelcomeEmail);
module.exports = router;

View File

@@ -1,9 +1,10 @@
const express = require("express");
const router = express.Router();
const { autohouse, claimscorp, kaizen } = require("../data/data");
const { autohouse, claimscorp, chatter, kaizen } = require("../data/data");
router.post("/ah", autohouse);
router.post("/cc", claimscorp);
router.post("/chatter", chatter);
router.post("/kaizen", kaizen);
module.exports = router;