diff --git a/src/routes/copilotOpportunity/assign.js b/src/routes/copilotOpportunity/assign.js index 5a3d0b3f..6cab3b55 100644 --- a/src/routes/copilotOpportunity/assign.js +++ b/src/routes/copilotOpportunity/assign.js @@ -1,11 +1,15 @@ import _ from 'lodash'; import validate from 'express-validation'; import Joi from 'joi'; +import config from 'config'; import models from '../../models'; import util from '../../util'; import { PERMISSION } from '../../permissions/constants'; -import { COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, EVENT, INVITE_STATUS, PROJECT_MEMBER_ROLE, RESOURCES } from '../../constants'; +import { CONNECT_NOTIFICATION_EVENT, COPILOT_APPLICATION_STATUS, COPILOT_OPPORTUNITY_STATUS, COPILOT_REQUEST_STATUS, EVENT, INVITE_STATUS, PROJECT_MEMBER_ROLE, RESOURCES, TEMPLATE_IDS } from '../../constants'; +import { getCopilotTypeLabel } from '../../utils/copilot'; +import { createEvent } from '../../services/busApi'; +import moment from 'moment'; const assignCopilotOpportunityValidations = { body: Joi.object().keys({ @@ -45,11 +49,17 @@ module.exports = [ throw err; } + const copilotRequest = await models.CopilotRequest.findOne({ + where: { id: opportunity.copilotRequestId }, + transaction: t, + }); + const application = await models.CopilotApplication.findOne({ where: { id: applicationId, opportunityId: copilotOpportunityId }, transaction: t, }); + if (!application) { const err = new Error('No such application available'); err.status = 400; @@ -65,12 +75,101 @@ module.exports = [ const projectId = opportunity.projectId; const userId = application.userId; const activeMembers = await models.ProjectMember.getActiveProjectMembers(projectId, t); - - const existingUser = activeMembers.find(item => item.userId === userId); - if (existingUser && existingUser.role === 'copilot') { - const err = new Error(`User is already a copilot of this project`); - err.status = 400; - throw err; + const updateCopilotOpportunity = async () => { + const transaction = await models.sequelize.transaction(); + const memberDetails = await util.getMemberDetailsByUserIds([application.userId], req.log, req.id); + const member = memberDetails[0]; + req.log.debug(`Updating opportunity: ${JSON.stringify(opportunity)}`); + await opportunity.update({ + status: COPILOT_OPPORTUNITY_STATUS.COMPLETED, + }, { + transaction, + }); + req.log.debug(`Updating application: ${JSON.stringify(application)}`); + await application.update({ + status: COPILOT_APPLICATION_STATUS.ACCEPTED, + }, { + transaction, + }); + + req.log.debug(`Updating request: ${JSON.stringify(copilotRequest)}`); + await copilotRequest.update({ + status: COPILOT_REQUEST_STATUS.FULFILLED, + }, { + transaction, + }); + + req.log.debug(`Updating other applications: ${JSON.stringify(copilotRequest)}`); + await models.CopilotApplication.update({ + status: COPILOT_APPLICATION_STATUS.CANCELED, + }, { + where: { + opportunityId: opportunity.id, + id: { + $ne: application.id, + }, + } + }); + + req.log.debug(`All updations done`); + transaction.commit(); + + req.log.debug(`Sending email notification`); + const emailEventType = CONNECT_NOTIFICATION_EVENT.EXTERNAL_ACTION_EMAIL; + const copilotPortalUrl = config.get('copilotPortalUrl'); + const requestData = copilotRequest.data; + createEvent(emailEventType, { + data: { + opportunity_details_url: `${copilotPortalUrl}/opportunity/${opportunity.id}`, + work_manager_url: config.get('workManagerUrl'), + opportunity_type: getCopilotTypeLabel(requestData.projectType), + opportunity_title: requestData.opportunityTitle, + start_date: moment.utc(requestData.startDate).format('DD-MM-YYYY'), + user_name: member ? member.handle : "", + }, + sendgrid_template_id: TEMPLATE_IDS.COPILOT_ALREADY_PART_OF_PROJECT, + recipients: [member.email], + version: 'v3', + }, req.log); + + req.log.debug(`Email sent`); + }; + + const existingMember = activeMembers.find(item => item.userId === userId); + if (existingMember) { + req.log.debug(`User already part of project: ${JSON.stringify(existingMember)}`); + if (['copilot', 'manager'].includes(existingMember.role)) { + req.log.debug(`User is a copilot or manager`); + await updateCopilotOpportunity(); + } else { + req.log.debug(`User has read/write role`); + await models.ProjectMember.update({ + role: 'copilot', + }, { + where: { + id: existingMember.id, + }, + }); + + const projectMember = await models.ProjectMember.findOne({ + where: { + id: existingMember.id, + }, + }); + + req.log.debug(`Updated project member: ${JSON.stringify(projectMember.get({plain: true}))}`); + + util.sendResourceToKafkaBus( + req, + EVENT.ROUTING_KEY.PROJECT_MEMBER_UPDATED, + RESOURCES.PROJECT_MEMBER, + projectMember.get({ plain: true }), + existingMember); + req.log.debug(`Member updated in kafka`); + await updateCopilotOpportunity(); + } + res.status(200).send({ id: applicationId }); + return; } const existingInvite = await models.ProjectMemberInvite.findAll({ diff --git a/src/routes/copilotOpportunityApply/list.js b/src/routes/copilotOpportunityApply/list.js index 69aea8fe..6051b027 100644 --- a/src/routes/copilotOpportunityApply/list.js +++ b/src/routes/copilotOpportunityApply/list.js @@ -31,19 +31,68 @@ module.exports = [ canAccessAllApplications ? {} : { createdBy: userId }, ); - return models.CopilotApplication.findAll({ - where: whereCondition, - include: [ - { - model: models.CopilotOpportunity, - as: 'copilotOpportunity', - }, - ], - order: [[sortParams[0], sortParams[1]]], + return models.CopilotOpportunity.findOne({ + where: { + id: opportunityId, + } + }).then((opportunity) => { + if (!opportunity) { + const err = new Error('No opportunity found'); + err.status = 404; + throw err; + } + return models.CopilotApplication.findAll({ + where: whereCondition, + include: [ + { + model: models.CopilotOpportunity, + as: 'copilotOpportunity', + }, + ], + order: [[sortParams[0], sortParams[1]]], + }) + .then(copilotApplications => { + req.log.debug(`CopilotApplications ${JSON.stringify(copilotApplications)}`); + return models.ProjectMember.getActiveProjectMembers(opportunity.projectId).then((members) => { + req.log.debug(`Fetched existing active members ${JSON.stringify(members)}`); + req.log.debug(`Applications ${JSON.stringify(copilotApplications)}`); + const enrichedApplications = copilotApplications.map(application => { + const m = members.find(m => m.userId === application.userId); + + // Using spread operator fails in lint check + // While Object.assign fails silently during run time + // So using this method + const enriched = { + id: application.id, + opportunityId: application.opportunityId, + notes: application.notes, + status: application.status, + userId: application.userId, + deletedAt: application.deletedAt, + createdAt: application.createdAt, + updatedAt: application.updatedAt, + deletedBy: application.deletedBy, + createdBy: application.createdBy, + updatedBy: application.updatedBy, + copilotOpportunity: application.copilotOpportunity, + }; + + if (m) { + enriched.existingMembership = m; + } + + req.log.debug(`Existing member to application ${JSON.stringify(enriched)}`); + + return enriched; + }); + + req.log.debug(`Enriched Applications ${JSON.stringify(enrichedApplications)}`); + res.status(200).send(enrichedApplications); + }); + }) }) - .then(copilotApplications => res.json(copilotApplications)) - .catch((err) => { - util.handleError('Error fetching copilot applications', err, req, next); - }); + .catch((err) => { + util.handleError('Error fetching copilot applications', err, req, next); + }); }, ]; diff --git a/src/routes/projectMembers/update.js b/src/routes/projectMembers/update.js index e38b6e14..882ddfdc 100644 --- a/src/routes/projectMembers/update.js +++ b/src/routes/projectMembers/update.js @@ -163,8 +163,6 @@ const completeAllCopilotRequests = async (req, projectId, _transaction, _member) req.log.debug(`Sent email to ${member.email}`); }); - - await _transaction.commit(); }; module.exports = [ @@ -263,8 +261,8 @@ module.exports = [ projectMember = projectMember.get({ plain: true }); projectMember = _.omit(projectMember, ['deletedAt']); - if (['observer', 'customer'].includes(updatedProps.role)) { - await completeAllCopilotRequests(req, projectId, _transaction, _member); + if (['observer', 'customer'].includes(previousValue.role) && ['copilot', 'manager'].includes(updatedProps.role)) { + await completeAllCopilotRequests(req, projectId, _transaction, projectMember); } }) .then(() => (