Files
bodyshop/server/notifications/scenarioMapper.js
2025-03-04 17:07:31 -05:00

228 lines
7.5 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
const {
jobAssignedToMeBuilder,
billPostedHandler,
newNoteAddedBuilder,
scheduledDatesChangedBuilder,
tasksUpdatedCreatedBuilder,
jobStatusChangeBuilder,
jobsAddedToProductionBuilder,
alternateTransportChangedBuilder,
newTimeTicketPostedBuilder,
intakeDeliveryChecklistCompletedBuilder,
paymentCollectedCompletedBuilder,
newMediaAddedReassignedBuilder,
criticalPartsStatusChangedBuilder,
supplementImportedBuilder,
partMarkedBackOrderedBuilder
} = require("./scenarioBuilders");
const { isFunction } = require("lodash");
/**
* An array of notification scenario definitions.
*
* Each scenario object can include the following properties:
* - key {string}: The unique scenario name.
* - table {string}: The table name to check for changes.
* - fields {Array<string>}: Fields to check for changes.
* - matchToUserFields {Array<string>}: Fields used to match scenarios to user data.
* - onNew {boolean|Array<boolean>}: Indicates whether the scenario should be triggered on new data.
* - builder {Function}: A function to handle the scenario.
* - onlyTruthyValues {boolean|Array<string>}: Specifies fields that must have truthy values for the scenario to match.
* */
const notificationScenarios = [
{
key: "job-assigned-to-me",
table: "jobs",
fields: ["employee_prep", "employee_body", "employee_csr", "employee_refinish"],
matchToUserFields: ["employee_prep", "employee_body", "employee_csr", "employee_refinish"],
builder: jobAssignedToMeBuilder
},
{
key: "bill-posted",
table: "bills",
builder: billPostedHandler,
onNew: true
},
{
key: "new-note-added",
table: "notes",
builder: newNoteAddedBuilder,
onNew: true
},
{
key: "schedule-dates-changed",
table: "jobs",
fields: ["scheduled_in", "scheduled_completion", "scheduled_delivery"],
builder: scheduledDatesChangedBuilder
},
{
key: "tasks-updated-created",
table: "tasks",
fields: ["updated_at"],
// onNew: true,
builder: tasksUpdatedCreatedBuilder
},
{
key: "job-status-change",
table: "jobs",
fields: ["status"],
builder: jobStatusChangeBuilder
},
{
key: "job-added-to-production",
table: "jobs",
fields: ["inproduction"],
onlyTruthyValues: ["inproduction"],
builder: jobsAddedToProductionBuilder
},
{
key: "alternate-transport-changed",
table: "jobs",
fields: ["alt_transport"],
builder: alternateTransportChangedBuilder
},
{
key: "new-time-ticket-posted",
table: "timetickets",
builder: newTimeTicketPostedBuilder
},
{
key: "intake-delivery-checklist-completed",
table: "jobs",
fields: ["intakechecklist", "deliverchecklist"],
builder: intakeDeliveryChecklistCompletedBuilder
},
{
key: "payment-added",
table: "payments",
onNew: true,
builder: paymentCollectedCompletedBuilder
},
{
// MAKE SURE YOU ARE NOT ON A LMS ENVIRONMENT
key: "new-media-added-reassigned",
table: "documents",
fields: ["jobid"],
builder: newMediaAddedReassignedBuilder
},
{
key: "critical-parts-status-changed",
table: "joblines",
fields: ["critical"],
onlyTruthyValues: ["critical"],
builder: criticalPartsStatusChangedBuilder
},
{
key: "part-marked-back-ordered",
table: "joblines",
fields: ["status"],
builder: partMarkedBackOrderedBuilder
},
// -------------- Difficult ---------------
// Holding off on this one for now
{
key: "supplement-imported",
builder: supplementImportedBuilder
// spans multiple tables,
}
];
/**
* Returns an array of scenarios that match the given event data.
*
* @param {Object} eventData - The parsed event data.
* Expected properties:
* - table: an object with a `name` property (e.g. { name: "tasks", schema: "public" })
* - changedFieldNames: an array of changed field names (e.g. [ "description", "updated_at" ])
* - isNew: boolean indicating whether the record is new or updated
* - data: the new data object (used to check field values)
* - (other properties may be added such as jobWatchers, bodyShopId, etc.)
*
* @returns {Array<Object>} An array of matching scenario objects.
*/
/**
* Returns an array of scenarios that match the given event data.
*
* @param {Object} eventData - The parsed event data.
* Expected properties:
* - table: an object with a `name` property (e.g. { name: "tasks", schema: "public" })
* - changedFieldNames: an array of changed field names (e.g. [ "description", "updated_at" ])
* - isNew: boolean indicating whether the record is new or updated
* - data: the new data object (used to check field values)
* - (other properties may be added such as jobWatchers, bodyShopId, etc.)
*
* @returns {Array<Object>} An array of matching scenario objects.
*/
const getMatchingScenarios = (eventData) =>
notificationScenarios.filter((scenario) => {
// If eventData has a table, then only scenarios with a table property that matches should be considered.
if (eventData.table) {
if (!scenario.table || eventData.table.name !== scenario.table) {
return false;
}
}
// Check the onNew flag.
// Allow onNew to be either a boolean or an array of booleans.
if (Object.prototype.hasOwnProperty.call(scenario, "onNew")) {
if (Array.isArray(scenario.onNew)) {
if (!scenario.onNew.includes(eventData.isNew)) return false;
} else {
if (eventData.isNew !== scenario.onNew) return false;
}
}
// If the scenario defines fields, ensure at least one of them is present in changedFieldNames.
if (scenario.fields && scenario.fields.length > 0) {
const hasMatchingField = scenario.fields.some((field) => eventData.changedFieldNames.includes(field));
if (!hasMatchingField) {
return false;
}
}
// OnlyTruthyValues logic:
// If onlyTruthyValues is defined, check that the new values of specified fields (or all changed fields if true)
// are truthy. If an array, only check the listed fields, which must be in scenario.fields.
if (Object.prototype.hasOwnProperty.call(scenario, "onlyTruthyValues")) {
let fieldsToCheck;
if (scenario.onlyTruthyValues === true) {
// If true, check all fields in the scenario that changed
fieldsToCheck = scenario.fields.filter((field) => eventData.changedFieldNames.includes(field));
} else if (Array.isArray(scenario.onlyTruthyValues) && scenario.onlyTruthyValues.length > 0) {
// If an array, check only the specified fields, ensuring they are in scenario.fields
fieldsToCheck = scenario.onlyTruthyValues.filter(
(field) => scenario.fields.includes(field) && eventData.changedFieldNames.includes(field)
);
// If no fields in onlyTruthyValues match the scenarios fields or changed fields, skip this scenario
if (fieldsToCheck.length === 0) {
return false;
}
} else {
// Invalid onlyTruthyValues (not true or a non-empty array), skip this scenario
return false;
}
// Ensure all fields to check have truthy new values
const allTruthy = fieldsToCheck.every((field) => Boolean(eventData.data[field]));
if (!allTruthy) {
return false;
}
}
// Execute the callback if defined, passing eventData, and filter based on its return value
if (isFunction(scenario?.callback)) {
const shouldInclude = scenario.callback(eventData);
if (!shouldInclude) {
return false;
}
}
return true;
});
module.exports = {
notificationScenarios,
getMatchingScenarios
};