//@ts-nocheck import moment from 'moment' import { schema } from 'nexus' import { send as sendNotification } from '../../utils/notification' import { logger } from '../../utils/logger' import * as Auth from '../../utils/auth' import GeneralUtils from '../../utils/general' import { PrismaClient, Comment } from 'nexus-plugin-prisma/client' import { customAlphabet } from 'nanoid/async' import { prismaClient as prisma } from '../../prismaClient' const TASK_FRAGMENT = { id: true, identificationNumber: true, movedToCompleteListAt: true, description: true, associatedResident: true, category: { select: { id: true, title: true, }, }, assignedTo: { select: { id: true, }, }, status: { select: { id: true, title: true, actAsComplete: true, }, }, associatedProject: { select: { id: true, }, }, associatedProperty: { select: { id: true, address: { select: { apartmentNumber: true, }, }, }, }, attachments: { select: { id: true, name: true, }, }, pictures: { select: { id: true, name: true, }, }, comments: { select: { id: true, message: true, mentionedUsers: { select: { id: true, }, }, }, }, collaborators: { select: { id: true, firstName: true, lastName: true, }, }, } const addTaskActivities = async (oldTask, updatedTask, currentUserId, data) => { if (!data) { return } const sendAssigneeNotification = async () => { const currentUser = await prisma.user.findOne({ where: { id: currentUserId } }) return sendNotification({ type: 'TASK_ASSIGNEE', title: `${GeneralUtils.getUserName(currentUser)} assigned you to a new task`, message: `Category: ${updatedTask.category.title}${ updatedTask.description ? `\nDescription: ${updatedTask.description}` : '' } `, users: [updatedTask.assignedTo], isFromWalter: true, projectId: updatedTask.associatedProject.id, data: { id: updatedTask.id, }, }) } const commonData = { task: { connect: { id: updatedTask.id, }, }, createdBy: { connect: { id: currentUserId, }, }, } // CATEGORY CHANGE if (data.category?.connect && oldTask?.category && updatedTask?.category?.title) { await prisma.taskActivity.create({ data: { text: `Changed category from ${oldTask.category.title} to ${updatedTask.category.title}`, ...commonData, }, }) } else if (data.category?.connect && updatedTask?.category?.title) { await prisma.taskActivity.create({ data: { text: `Added the category ${updatedTask.category.title} to this task`, ...commonData, }, }) } else if (data.category?.disconnect && oldTask?.category?.title) { await prisma.taskActivity.create({ data: { text: `Removed the category ${oldTask.category.title} from this task`, ...commonData, }, }) } // STATUS CHANGE if (data.status?.connect && oldTask?.status?.title && updatedTask?.status?.title) { await prisma.taskActivity.create({ data: { text: `Moved this task from ${oldTask.status.title} to ${updatedTask.status.title}`, ...commonData, }, }) } else if (data.status?.connect && updatedTask?.status?.title) { await prisma.taskActivity.create({ data: { text: `Moved this task to ${updatedTask.status.title}`, ...commonData, }, }) } else if (data.status?.disconnect && oldTask?.status?.title) { await prisma.taskActivity.create({ data: { text: `Removed this task from ${oldTask.status.title}`, ...commonData, }, }) } // ARCHIVED if (data.isArchived) { await prisma.taskActivity.create({ data: { text: 'archived this task', ...commonData, }, }) } // ASSIGNEE CHANGE if (data.assignedTo?.connect && oldTask?.assignedTo && updatedTask?.assignedTo) { await prisma.taskActivity.create({ data: { text: `Removed ${GeneralUtils.getUserName( oldTask.assignedTo )} and assigned ${GeneralUtils.getUserName(updatedTask.assignedTo)} to this task`, ...commonData, }, }) await sendAssigneeNotification() } else if (data.assignedTo?.connect && updatedTask.assignedTo) { await prisma.taskActivity.create({ data: { text: `Assigned ${GeneralUtils.getUserName(updatedTask.assignedTo)} to this task`, ...commonData, }, }) await sendAssigneeNotification() } else if (data.assignedTo?.disconnect && oldTask?.assignedTo) { await prisma.taskActivity.create({ data: { text: `Removed ${GeneralUtils.getUserName(oldTask.assignedTo)} from this task`, ...commonData, }, }) } // ASSCOIATED RESIDENT CHANGE if ( data.associatedResident?.connect && oldTask?.associatedResident && updatedTask.associatedResident ) { await prisma.taskActivity.create({ data: { text: `Removed ${await GeneralUtils.getUserNameAndUnits( oldTask.associatedResident, oldTask.associatedProject )} and associated ${await GeneralUtils.getUserNameAndUnits( updatedTask.associatedResident, updatedTask.associatedProject )} to this task`, ...commonData, }, }) } else if (data.associatedResident?.connect && updatedTask.associatedResident) { await prisma.taskActivity.create({ data: { text: `Associated this task to ${await GeneralUtils.getUserNameAndUnits( updatedTask.associatedResident, updatedTask.associatedProject )}`, ...commonData, }, }) } else if (data.associatedResident?.disconnect && oldTask?.associatedResident) { await prisma.taskActivity.create({ data: { text: `Removed ${await GeneralUtils.getUserNameAndUnits( oldTask.associatedResident, oldTask.associatedProject )} from this task`, ...commonData, }, }) } // ASSCOIATED PROPERTY CHANGE if ( data.associatedProperty?.connect && oldTask?.associatedProperty && updatedTask.associatedProperty ) { await prisma.taskActivity.create({ data: { text: `Removed unit #${oldTask.associatedProperty.address.apartmentNumber} and associated unit #${updatedTask.associatedProperty.address.apartmentNumber} to this task`, ...commonData, }, }) } else if (data.associatedProperty?.connect && updatedTask.associatedProperty) { await prisma.taskActivity.create({ data: { text: `Associated this task to unit #${updatedTask.associatedProperty.address.apartmentNumber}`, ...commonData, }, }) } else if (data.associatedProperty?.disconnect && oldTask?.associatedProperty) { await prisma.taskActivity.create({ data: { text: `Removed unit #${oldTask.associatedProperty.address.apartmentNumber} from this task`, ...commonData, }, }) } // ATTACHMENTS CHANGE if (data.attachments?.create && (data.attachments?.deleteMany || data.attachments?.delete)) { const removedAttachments = data.attachments?.deleteMany || data.attachments?.delete const newAttachments = Array.isArray(data.attachments.create) ? data.attachments.create : [data.attachments.create] await Promise.all( newAttachments.map((attachment) => prisma.taskActivity.create({ data: { text: `Attached ${attachment?.name || 'new attachment'} to this task`, ...commonData, }, }) ) ) await Promise.all( removedAttachments.map((attachment) => prisma.taskActivity.create({ data: { text: `Removed ${ attachment?.name || oldTask?.attachments.find( (oldAttachment) => oldAttachment?.name === attachment.name || oldAttachment?.id === attachment.id )?.name || 'attachment' } to this task`, ...commonData, }, }) ) ) } // PICTURES CHANGE if (data.pictures?.create && (data.pictures?.deleteMany || data.pictures?.delete)) { const removedPictures = data.pictures?.deleteMany || data.pictures?.delete const newPictures = Array.isArray(data.pictures.create) ? data.pictures.create : [data.pictures.create] await Promise.all( newPictures.map((picture) => prisma.taskActivity.create({ data: { text: `Attached ${picture?.name || 'new picture'} to this task`, ...commonData, }, }) ) ) await Promise.all( removedPictures.map((picture) => prisma.taskActivity.create({ data: { text: `Removed ${ picture?.name || oldTask?.pictures.find( (oldPicture) => oldPicture?.name === picture.name || oldPicture?.id === picture.id )?.name || 'picture' } to this task`, ...commonData, }, }) ) ) } } export const Task = schema.objectType({ name: 'Task', definition(t) { t.model.id() t.model.createdAt() t.model.updatedAt() t.model.identificationNumber() t.model.title() t.model.description() t.model.isArchived() t.model.movedToCompleteListAt() t.model.assignedTo() t.model.associatedProject() t.model.associatedProperty() t.model.associatedResident() t.model.category() t.model.createdBy() t.model.status() t.model.comments() t.model.activities() t.model.customFields() t.model.attachments() t.model.pictures() t.model.collaborators() }, }) export const TaskQueries = schema.extendType({ type: 'Query', definition(t) { t.crud.task() t.crud.tasks({ filtering: true, ordering: true, pagination: true }) t.field('tasks', { type: 'Task', list: true, args: { where: schema.arg({ type: 'TaskWhereInput' }), orderBy: schema.arg({ type: 'TaskOrderByInput', list: true }), first: schema.arg({ type: 'Int' }), skip: schema.arg({ type: 'Int' }), }, resolve: (parent, args, ctx: NexusContext) => { return ctx.prisma.task.findMany({ where: args.where, take: args.first, skip: args.skip, ...(args.orderBy?.length > 0 && { orderBy: args.orderBy[0] }), }) }, }) }, }) export const TaskMutation = schema.extendType({ type: 'Mutation', definition: (t) => { t.crud.updateOneTask({ alias: 'updateTask' }) t.crud.createOneTask({ alias: 'createTask' }) t.crud.deleteOneTask({ alias: 'deleteTask' }) t.field('createTask', { type: 'Task', args: { data: schema.arg({ type: 'TaskCreateInput' }), orderBy: schema.arg({ type: 'TaskOrderByInput' }), }, resolve: async (parent, { data }, ctx: NexusContext) => { const id = Auth.getUserId(ctx) const userThatCreatedTask = await prisma.user.findOne({ where: { id } }) const nanoid = await customAlphabet('1234567890ABCDEF', 6) data.identificationNumber = await nanoid() // In case we don't specify a status // For example in the resident app if (!data.status) { if (!data.associatedProject) { throw new Error('Need to have a project') } const taskStatuses = await ctx.prisma.taskStatus.findMany({ orderBy: { orderPosition: 'asc', }, select: { id: true, }, where: { managingCompany: { projects: { some: { id: data.associatedProject.connect.id, }, }, }, }, }) data.status = { connect: { id: taskStatuses[0].id, }, } } let createdTask = await ctx.prisma.task.create({ data, select: TASK_FRAGMENT }) if (!createdTask.movedToCompleteListAt && createdTask?.status?.actAsComplete) { createdTask = await ctx.prisma.task.update({ where: { id: createdTask.id }, data: { movedToCompleteListAt: moment().toDate() }, select: TASK_FRAGMENT, }) } try { await ctx.prisma.taskActivity.create({ data: { text: `Added this task to ${createdTask.status.title}`, task: { connect: { id: createdTask.id, }, }, createdBy: { connect: { id, }, }, }, }) await addTaskActivities(null, createdTask, id, data) } catch (error) { logger.error(error) } // Tell the manager that there's a new task created by a resident if (userThatCreatedTask.role === 'RESIDENT') { ctx.prisma.project .findOne({ where: { id: data.associatedProject.connect.id, }, }) .managingCompany() .users() .then(async (managingCompanyUsers) => { return sendNotification({ type: 'NEW_TASK', title: `New task created by ${await GeneralUtils.getUserNameAndUnits( userThatCreatedTask, { id: data.associatedProject.connect.id } // The project )}`, message: `Category: ${ createdTask.category?.title || 'No category' }\n\nDescription: ${createdTask.description || 'No description'}`, users: managingCompanyUsers, projectId: createdTask.associatedProject.id, data: { id: createdTask.id, }, }) }) .catch(logger.error) } // Tell resident of unit about the task if (createdTask.associatedProperty) { ctx.prisma.user .findMany({ where: { OR: [ { property: { id: createdTask.associatedProperty.id, }, }, { properties: { some: { id: createdTask.associatedProperty.id }, }, }, ], }, select: { id: true, firstName: true, lastName: true, }, }) .then((usersAssociatedToProperty) => { return sendNotification({ type: 'NEW_TASK', title: 'New task associated to your unit', titleFr: 'Nouvelle demande associée à votre unité', message: `Category: ${createdTask.category.title}`, messageFr: `Categorie: ${createdTask.category.title}`, users: usersAssociatedToProperty, projectId: createdTask.associatedProject.id, data: { id: createdTask.id, }, }) }) .catch(logger.error) } return ctx.prisma.task.findOne({ where: { id: createdTask.id, }, }) }, }) t.field('updateTask', { type: 'Task', args: { data: schema.arg({ type: 'TaskUpdateInput' }), where: schema.arg({ type: 'TaskWhereUniqueInput' }), }, resolve: async (parent, { where, data }, ctx: NexusContext) => { const prisma: PrismaClient = ctx.prisma const id = Auth.getUserId(ctx) const currentUser = await prisma.user.findOne({ where: { id: id } }) await prisma.task.findOne({ where, select: TASK_FRAGMENT }) const [oldTask, updatedTask] = await Promise.all([ prisma.task.findOne({ where, select: TASK_FRAGMENT }), prisma.task.update({ where, data, select: TASK_FRAGMENT }), ]) if (!updatedTask.movedToCompleteListAt && updatedTask.status.actAsComplete) { updatedTask = await prisma.task.update({ where, data: { movedToCompleteListAt: moment().toDate() }, select: TASK_FRAGMENT, }) } if (updatedTask.comments.length > oldTask.comments.length) { const newComments = updatedTask.comments.filter( (newComment) => !oldTask.comments.some((comment) => newComment.id === comment.id) ) // Send notifications to managers that are subscribed if (newComments.length > 0) { newComments.forEach((comment) => { if (comment.mentionedUsers?.length) { // Protection to make sure that if a user get mentioned, he is put in the list of collaborators // Will do in FE also but just to make sure because it makes sense that if you get mentioned, // You have to be involved and receive notifications about task progress const collaboratorsThatAreNotThereButSupposedToBe = comment.mentionedUsers.filter( (user) => !updatedTask.collaborators.some((collaborator) => user.id === collaborator.id) ) if (collaboratorsThatAreNotThereButSupposedToBe.length) { prisma.task .update({ where: { id: updatedTask.id }, data: { collaborators: { connect: collaboratorsThatAreNotThereButSupposedToBe.map(({ id }) => ({ id, })), }, }, }) .catch(logger.error) } // Send to sendNotification({ title: `${GeneralUtils.getUserName(currentUser)} mentioned you in a task`, message: comment.message, users: comment.mentionedUsers, type: 'NEW_COMMENT_WITH_MENTION', projectId: updatedTask.associatedProject.id, data: comment, }).catch(logger.error) } else { // Send to collaborators sendNotification({ title: `${GeneralUtils.getUserName( currentUser )} added a new comment in a task that you've subscribed to`, message: `Task #: ${updatedTask.identificationNumber}\nCategory: ${ updatedTask.category?.title || 'No categorie' }\nDescription: ${updatedTask.description || 'No description'}\n\nMessage: ${ comment.message }`, users: updatedTask.collaborators, type: 'NEW_COMMENT_SUBSCRIBED', projectId: updatedTask.associatedProject.id, data: comment, }).catch(logger.error) } }) } } try { await addTaskActivities(oldTask, updatedTask, id, data) } catch (error) { logger.error(error) } return prisma.task.findOne({ where }) }, }) }, })