diff --git a/src/ctask/ctask.schema.ts b/src/ctask/ctask.schema.ts index a217683..c03dcc9 100644 --- a/src/ctask/ctask.schema.ts +++ b/src/ctask/ctask.schema.ts @@ -1,4 +1,4 @@ -import mongoose from "mongoose"; +import mongoose, { Schema } from "mongoose"; import { z } from "zod"; import { files } from "../file/file.schema"; import { buildJsonSchemas } from "fastify-zod"; @@ -25,7 +25,7 @@ const taskSchema = new mongoose.Schema({ ref: "user", }, assignedTo: { - type: mongoose.Types.ObjectId, + type: [Schema.Types.ObjectId], ref: "user", }, taggedUsers: Array, @@ -41,7 +41,7 @@ const createTaskInput = z.object({ title: z.string(), dueDate: z.date().optional(), files: z.array(files).optional(), - assignedTo: z.string().optional(), + assignedTo: z.array(z.string()).optional(), labels: z.array(z.string()).optional(), priority: z.string().optional(), stage: z @@ -64,7 +64,7 @@ const updateTaskInput = z.object({ title: z.string().optional(), dueDate: z.date().optional(), files: z.array(files).optional(), - assignedTo: z.string().optional(), + assignedTo: z.array(z.string()).optional(), labels: z.array(z.string()).optional(), priority: z.string().optional(), stage: z diff --git a/src/ctask/ctask.service.ts b/src/ctask/ctask.service.ts index 9b81381..3f8d90b 100644 --- a/src/ctask/ctask.service.ts +++ b/src/ctask/ctask.service.ts @@ -163,12 +163,14 @@ export async function listTasks( }, }, assignedTo: { - $let: { - vars: { assignedTo: { $arrayElemAt: ["$assignedTo", 0] } }, + $map: { + input: "$assignedTo", + as: "user", in: { - _id: "$$assignedTo._id", - pid: "$$assignedTo.pid", - name: "$$assignedTo.name", + _id: "$$user._id", + pid: "$$user.pid", + name: "$$user.name", + avatar: "$$user.avatar", }, }, }, @@ -272,12 +274,14 @@ export async function searchTasks( }, }, assignedTo: { - $let: { - vars: { assignedTo: { $arrayElemAt: ["$assignedTo", 0] } }, + $map: { + input: "$assignedTo", + as: "user", in: { - _id: "$$assignedTo._id", - pid: "$$assignedTo.pid", - name: "$$assignedTo.name", + _id: "$$user._id", + pid: "$$user.pid", + name: "$$user.name", + avatar: "$$user.avatar", }, }, }, diff --git a/src/note/note.service.ts b/src/note/note.service.ts index f9afdf0..501de27 100644 --- a/src/note/note.service.ts +++ b/src/note/note.service.ts @@ -46,7 +46,10 @@ export async function createNote( if (!obj.taggedUsers) { await model.updateOne( { pid: resourceId }, - { $set: { taggedUsers: taggedUsers, assignedTo: userIds[0] } } + { + $set: { taggedUsers: taggedUsers }, + $addToSet: { assignedTo: userIds[0] }, + } ); } else { for (const user of taggedUsers) { @@ -57,7 +60,11 @@ export async function createNote( else obj.taggedUsers.push(user); } - obj.assignedTo = userIds[0]; + const assignee = obj.assignedTo.find( + (item) => item.toString() == userIds[0] + ); + + if (!assignee) obj.assignedTo.push(userIds[0]); obj.markModified("taggedUsers", "assignedTo"); await obj.save(); diff --git a/src/notification/notification.schema.ts b/src/notification/notification.schema.ts index b6ea3b2..57ae789 100644 --- a/src/notification/notification.schema.ts +++ b/src/notification/notification.schema.ts @@ -1,5 +1,5 @@ import { buildJsonSchemas } from "fastify-zod"; -import mongoose from "mongoose"; +import mongoose, { Schema } from "mongoose"; import { z } from "zod"; import { pageQueryParams } from "../pagination"; @@ -27,7 +27,7 @@ const notificationSchema = new mongoose.Schema({ createdAt: Date, updatedAt: Date, assignedTo: { - type: mongoose.Types.ObjectId, + type: [Schema.Types.ObjectId], ref: "user", }, taggedUsers: Array, @@ -52,12 +52,12 @@ const createNotificationInput = z.object({ county: z.object({}).passthrough(), client: z.string(), clientData: z.object({}).passthrough(), - assignedTo: z.string().optional(), + assignedTo: z.array(z.string()).optional(), }); const updateNotificationInput = z.object({ status: z.string().optional(), - assignedTo: z.string().optional(), + assignedTo: z.array(z.string()).optional(), }); export type CreateNotificationInput = z.infer; diff --git a/src/notification/notification.service.ts b/src/notification/notification.service.ts index 7359237..a96c4b5 100644 --- a/src/notification/notification.service.ts +++ b/src/notification/notification.service.ts @@ -17,6 +17,7 @@ import { import { getUser } from "../user/user.service"; import { createNote } from "../note/note.service"; import { createAlert } from "../alert/alert.service"; +import { arrayDiff } from "../utils/diff"; export async function createNotification( input: CreateNotificationInput, @@ -51,6 +52,11 @@ export async function updateNotification( input: UpdateNotificationInput, user: AuthenticatedUser ) { + const oldNotification = await notificationModel.findOne( + { pid: notifId }, + { assignedTo: 1 } + ); + const updateNotificationResult = await notificationModel .findOneAndUpdate( { $and: [{ tenantId: user.tenantId }, { pid: notifId }] }, @@ -64,16 +70,11 @@ export async function updateNotification( if (updateNotificationResult) { for (const key in input) { - if (["status", "assignedTo"].includes(key)) { + if (["status"].includes(key)) { let msg = ""; if (input[key] === null) { msg = `Cleared ${key}`; - } else if (key == "assignedTo") { - const user = await getUser(input.assignedTo); - if (!user) continue; - - msg = `Assigned to ${user.firstName + " " + user.lastName}`; } else { msg = `Updated ${key} to '${input[key]}'`; } @@ -86,23 +87,39 @@ export async function updateNotification( "notifications", user ); + } else if (key == "assignedTo") { + const newAssignees = arrayDiff( + updateNotificationResult.assignedTo.map((item) => item._id), + oldNotification.assignedTo + ); + + if (newAssignees.length > 0) { + let msg = "Assigned to:\n\n"; + + for (const assignee of newAssignees) { + const user = await getUser(assignee); + if (!user) continue; + + msg += `${user.firstName + " " + user.lastName}\n`; + + await createAlert( + user.tenantId, + `You are assigned to ${updateNotificationResult.permitNumber}`, + "user", + assignee, + updateNotificationResult.pid, + "permits" + ); + } - if (key === "assignedTo") { await createNote( - { content: msg }, - updateNotificationResult.permitId, + { + content: msg, + }, + notifId, "notifications", user ); - - await createAlert( - user.tenantId, - `You are assigned to ${updateNotificationResult.permitNumber}`, - "user", - input.assignedTo, - updateNotificationResult.permitId, - "permits" - ); } } } @@ -190,13 +207,14 @@ export async function listNotifications( updatedAt: 1, taggedUsers: 1, assignedTo: { - $let: { - vars: { assignedTo: { $arrayElemAt: ["$assignedTo", 0] } }, + $map: { + input: "$assignedTo", + as: "user", in: { - _id: "$$assignedTo._id", - pid: "$$assignedTo.pid", - name: "$$assignedTo.name", - avatar: "$$assignedTo.avatar", + _id: "$$user._id", + pid: "$$user.pid", + name: "$$user.name", + avatar: "$$user.avatar", }, }, }, diff --git a/src/permit/permit.schema.ts b/src/permit/permit.schema.ts index 0603210..27d9ffd 100644 --- a/src/permit/permit.schema.ts +++ b/src/permit/permit.schema.ts @@ -208,7 +208,7 @@ const permitCore = { recordId: z.string().optional(), relatedRecords: z .object({ - custon_id: z.string(), + custom_id: z.string(), relationship: z.string(), type_text: z.string(), }) diff --git a/src/processed/processed.schema.ts b/src/processed/processed.schema.ts index 4f49f90..1059393 100644 --- a/src/processed/processed.schema.ts +++ b/src/processed/processed.schema.ts @@ -1,5 +1,5 @@ import z from "zod"; -import mongoose from "mongoose"; +import mongoose, { Schema } from "mongoose"; import { pageQueryParams } from "../pagination"; import { buildJsonSchemas } from "fastify-zod"; @@ -38,7 +38,7 @@ const processedSchema = new mongoose.Schema({ permitType: String, utility: String, assignedTo: { - type: mongoose.Types.ObjectId, + type: [Schema.Types.ObjectId], ref: "user", }, link: String, @@ -131,13 +131,13 @@ const updateProcessedInput = z.object({ block: z.string().nullable().optional(), jobNumber: z.string().nullable().optional(), startDate: z.date().nullable().optional(), - assignedTo: z.string().nullable().optional(), + assignedTo: z.array(z.string()).nullable().optional(), noc: z.string().optional(), deed: z.string().optional(), requests: z.array(z.string()).optional(), relatedRecords: z .object({ - custon_id: z.string(), + custom_id: z.string(), relationship: z.string(), type_text: z.string(), }) diff --git a/src/processed/processed.service.ts b/src/processed/processed.service.ts index 39424b9..58fab04 100644 --- a/src/processed/processed.service.ts +++ b/src/processed/processed.service.ts @@ -15,6 +15,7 @@ import { createNote } from "../note/note.service"; import { createAlert } from "../alert/alert.service"; import { getUser } from "../user/user.service"; import { orgModel } from "../organization/organization.schema"; +import { arrayDiff } from "../utils/diff"; export async function getProcessedPermit( permitId: String, @@ -38,6 +39,10 @@ export async function updateProcessed( permitId: string, user: AuthenticatedUser ) { + const oldPermitResult = await processedModel.findOne( + { pid: permitId }, + { assignedTo: 1 } + ); const updateProcessedResult = await processedModel .findOneAndUpdate( { @@ -52,16 +57,11 @@ export async function updateProcessed( if (updateProcessedResult) { for (const key in input) { - if (["manualStatus", "utility", "assignedTo"].includes(key)) { + if (["manualStatus", "utility"].includes(key)) { let msg = ""; if (input[key] === null) { msg = `Cleared ${key}`; - } else if (key == "assignedTo") { - const user = await getUser(input.assignedTo); - if (!user) continue; - - msg = `Assigned to ${user.firstName + " " + user.lastName}`; } else { msg = `Updated ${key} to '${input[key]}'`; } @@ -74,17 +74,6 @@ export async function updateProcessed( "processed", user ); - - if (key == "assignedTo" && input[key] != null) { - await createAlert( - user.tenantId, - `You are assigned to ${updateProcessedResult.permitNumber}`, - "user", - input.assignedTo, - updateProcessedResult.pid, - "processed" - ); - } } else if (key == "client") { const orgInDb = await orgModel.findById(input.client); if (orgInDb) { @@ -98,6 +87,40 @@ export async function updateProcessed( updateProcessedResult.markModified("clientData"); await updateProcessedResult.save(); } + } else if (key == "assignedTo") { + const newAssignees = arrayDiff( + updateProcessedResult.assignedTo.map((item) => item._id), + oldPermitResult.assignedTo + ); + + if (newAssignees.length == 0) continue; + + let msg = "Assigned to:\n\n"; + + for (const assignee of newAssignees) { + const user = await getUser(assignee); + if (!user) continue; + + msg += `${user.firstName + " " + user.lastName}\n`; + + await createAlert( + user.tenantId, + `You are assigned to ${updateProcessedResult.permitNumber}`, + "user", + assignee, + updateProcessedResult.pid, + "processed" + ); + } + + await createNote( + { + content: msg, + }, + permitId, + "processed", + user + ); } } @@ -152,7 +175,7 @@ export async function listProcessedPermits( from: "users", localField: "assignedTo", foreignField: "_id", - as: "assignedRec", + as: "assignedTo", }, }, { @@ -205,13 +228,14 @@ export async function listProcessedPermits( deed: 1, requests: 1, assignedTo: { - $let: { - vars: { assigned: { $arrayElemAt: ["$assignedRec", 0] } }, + $map: { + input: "$assignedTo", + as: "user", in: { - _id: "$$assigned._id", - pid: "$$assigned.pid", - name: "$$assigned.name", - avatar: "$$assigned.avatar", + _id: "$$user._id", + pid: "$$user.pid", + name: "$$user.name", + avatar: "$$user.avatar", }, }, }, diff --git a/src/rts/rts.schema.ts b/src/rts/rts.schema.ts index 2a6d0a9..5c14817 100644 --- a/src/rts/rts.schema.ts +++ b/src/rts/rts.schema.ts @@ -1,5 +1,5 @@ import { buildJsonSchemas } from "fastify-zod"; -import mongoose from "mongoose"; +import mongoose, { Schema } from "mongoose"; import z from "zod"; import { files } from "../file/file.schema"; import { pageQueryParams } from "../pagination"; @@ -51,7 +51,7 @@ const rtsSchema = new mongoose.Schema({ ref: "user", }, assignedTo: { - type: mongoose.Types.ObjectId, + type: [Schema.Types.ObjectId], ref: "user", }, taggedUsers: Array, @@ -85,7 +85,7 @@ const rtsCreateInput = z.object({ currentStage: z.number(), }) .optional(), - assignedTo: z.string().optional(), + assignedTo: z.array(z.string()).optional(), status: z.string().optional(), }); @@ -109,7 +109,7 @@ const rtsUpdateInput = z.object({ currentStage: z.number(), }) .optional(), - assignedTo: z.string().optional(), + assignedTo: z.array(z.string()).optional(), status: z.string().optional(), fileValidationStatus: z.string().optional(), }); diff --git a/src/rts/rts.service.ts b/src/rts/rts.service.ts index 358aa5f..5392e50 100644 --- a/src/rts/rts.service.ts +++ b/src/rts/rts.service.ts @@ -13,11 +13,12 @@ import { getTaggedUsersFilter, PageQueryParams, } from "../pagination"; -import { getUserWithoutPopulate } from "../user/user.service"; +import { getUser, getUserWithoutPopulate } from "../user/user.service"; import mongoose from "mongoose"; import { rtsPipeline } from "../utils/pipeline"; import { createAlert } from "../alert/alert.service"; import { createNote } from "../note/note.service"; +import { arrayDiff } from "../utils/diff"; export async function createRts( input: CreateRtsInput, @@ -126,7 +127,7 @@ export async function listRts( from: "users", localField: "assignedTo", foreignField: "_id", - as: "assignedToRec", + as: "assignedTo", }, }, { @@ -180,13 +181,14 @@ export async function listRts( }, }, assignedTo: { - $let: { - vars: { assignedTo: { $arrayElemAt: ["$assignedToRec", 0] } }, + $map: { + input: "$assignedTo", + as: "user", in: { - _id: "$$assignedTo._id", - pid: "$$assignedTo.pid", - name: "$$assignedTo.name", - avatar: "$$assignedTo.avatar", + _id: "$$user._id", + pid: "$$user.pid", + name: "$$user.name", + avatar: "$$user.avatar", }, }, }, @@ -222,6 +224,7 @@ export async function updateRts( input: UpdateRtsInput, user: AuthenticatedUser ) { + const oldRts = await rtsModel.findOne({ pid: id }, { assignedTo: 1 }); const updatedRts = await rtsModel .findOneAndUpdate({ pid: id, tenantId: user.tenantId }, input, { new: true, @@ -241,14 +244,39 @@ export async function updateRts( } if (updatedRts && input.assignedTo) { - await createAlert( - user.tenantId, - `You are assigned to RTS`, - "user", - input.assignedTo, - updatedRts.pid, - "rts" + const newAssignees = arrayDiff( + updatedRts.assignedTo.map((item) => item._id), + oldRts.assignedTo ); + + if (newAssignees.length > 0) { + let msg = "Assigned to:\n\n"; + + for (const assignee of newAssignees) { + const user = await getUser(assignee); + if (!user) continue; + + msg += `${user.firstName + " " + user.lastName}\n`; + + await createAlert( + user.tenantId, + `You are assigned to RTS`, + "user", + assignee, + updatedRts.pid, + "rts" + ); + } + + await createNote( + { + content: msg, + }, + id, + "rts", + user + ); + } } return updatedRts; diff --git a/src/task/task.schema.ts b/src/task/task.schema.ts index b492f1b..b468e9c 100644 --- a/src/task/task.schema.ts +++ b/src/task/task.schema.ts @@ -1,4 +1,4 @@ -import mongoose from "mongoose"; +import mongoose, { Schema } from "mongoose"; import { z } from "zod"; import { files } from "../file/file.schema"; import { buildJsonSchemas } from "fastify-zod"; @@ -37,7 +37,7 @@ const taskSchema = new mongoose.Schema({ ref: "user", }, assignedTo: { - type: mongoose.Types.ObjectId, + type: [Schema.Types.ObjectId], ref: "user", }, taggedUsers: Array, @@ -53,7 +53,7 @@ const createTaskInput = z.object({ title: z.string(), dueDate: z.date().optional(), files: z.array(files).optional(), - assignedTo: z.string().optional(), + assignedTo: z.array(z.string()).optional(), labels: z.array(z.string()).optional(), priority: z.string().optional(), stage: z @@ -76,7 +76,7 @@ const updateTaskInput = z.object({ title: z.string().optional(), dueDate: z.date().optional(), files: z.array(files).optional(), - assignedTo: z.string().optional(), + assignedTo: z.array(z.string()).optional(), labels: z.array(z.string()).optional(), priority: z.string().optional(), stage: z diff --git a/src/task/task.service.ts b/src/task/task.service.ts index b63a2b3..77cfb15 100644 --- a/src/task/task.service.ts +++ b/src/task/task.service.ts @@ -7,6 +7,7 @@ import { getTaggedUsersFilter, PageQueryParams, } from "../pagination"; +import { arrayDiff } from "../utils/diff"; import { generateId } from "../utils/id"; import { taskPipeline } from "../utils/pipeline"; import { @@ -58,6 +59,7 @@ export async function updateTask( input: UpdateTaskInput, user: AuthenticatedUser ) { + const oldTask = await taskModel.findOne({ pid: taskId }, { assignedTo: 1 }); const updatedTask = await taskModel .findOneAndUpdate({ tenantId: user.tenantId, pid: taskId }, input, { new: true, @@ -66,14 +68,23 @@ export async function updateTask( .populate({ path: "assignedTo", select: "pid name avatar" }); if (updatedTask && input.assignedTo) { - await createAlert( - user.tenantId, - `You are assigned to ${updatedTask.title}`, - "user", - input.assignedTo, - updatedTask.pid, - "tasks" + const newAssignees = arrayDiff( + updatedTask.assignedTo.map((item) => item._id), + oldTask.assignedTo ); + + if (newAssignees.length > 0) { + for (const assignee of newAssignees) { + await createAlert( + user.tenantId, + `You are assigned to ${updatedTask.title}`, + "user", + assignee, + updatedTask.pid, + "tasks" + ); + } + } } return updatedTask; @@ -146,12 +157,14 @@ export async function listTasks( }, }, assignedTo: { - $let: { - vars: { assignedTo: { $arrayElemAt: ["$assignedTo", 0] } }, + $map: { + input: "$assignedTo", + as: "user", in: { - _id: "$$assignedTo._id", - pid: "$$assignedTo.pid", - name: "$$assignedTo.name", + _id: "$$user._id", + pid: "$$user.pid", + name: "$$user.name", + avatar: "$$user.avatar", }, }, },