11import _ from 'lodash' ;
22import validate from 'express-validation' ;
33import Joi from 'joi' ;
4+ import config from 'config' ;
45
56import models from '../../models' ;
67import util from '../../util' ;
78import { PERMISSION } from '../../permissions/constants' ;
8- import { COPILOT_APPLICATION_STATUS , COPILOT_OPPORTUNITY_STATUS , COPILOT_REQUEST_STATUS , EVENT , INVITE_STATUS , PROJECT_MEMBER_ROLE , RESOURCES } from '../../constants' ;
9+ import { CONNECT_NOTIFICATION_EVENT , COPILOT_APPLICATION_STATUS , COPILOT_OPPORTUNITY_STATUS , COPILOT_REQUEST_STATUS , EVENT , INVITE_STATUS , PROJECT_MEMBER_ROLE , RESOURCES , TEMPLATE_IDS , USER_ROLE } from '../../constants' ;
10+ import { getCopilotTypeLabel } from '../../utils/copilot' ;
11+ import { createEvent } from '../../services/busApi' ;
12+ import moment from 'moment' ;
13+ import { Op } from 'sequelize' ;
914
1015const assignCopilotOpportunityValidations = {
1116 body : Joi . object ( ) . keys ( {
@@ -27,6 +32,34 @@ module.exports = [
2732 return next ( err ) ;
2833 }
2934
35+ const sendEmailToAllApplicants = async ( copilotRequest , allApplications ) => {
36+
37+ const userIds = allApplications . map ( item => item . userId ) ;
38+
39+ const users = await util . getMemberDetailsByUserIds ( userIds , req . log , req . id ) ;
40+
41+ users . forEach ( async ( user ) => {
42+ req . log . debug ( `Sending email notification to copilots who are not accepted` ) ;
43+ const emailEventType = CONNECT_NOTIFICATION_EVENT . EXTERNAL_ACTION_EMAIL ;
44+ const copilotPortalUrl = config . get ( 'copilotPortalUrl' ) ;
45+ const requestData = copilotRequest . data ;
46+ createEvent ( emailEventType , {
47+ data : {
48+ opportunity_details_url : copilotPortalUrl ,
49+ work_manager_url : config . get ( 'workManagerUrl' ) ,
50+ opportunity_title : requestData . opportunityTitle ,
51+ user_name : user ? user . handle : "" ,
52+ } ,
53+ sendgrid_template_id : TEMPLATE_IDS . COPILOT_OPPORTUNITY_COMPLETED ,
54+ recipients : [ user . email ] ,
55+ version : 'v3' ,
56+ } , req . log ) ;
57+
58+ req . log . debug ( `Email sent to copilots who are not accepted` ) ;
59+ } ) ;
60+
61+ } ;
62+
3063 return models . sequelize . transaction ( async ( t ) => {
3164 const opportunity = await models . CopilotOpportunity . findOne ( {
3265 where : { id : copilotOpportunityId } ,
@@ -45,11 +78,17 @@ module.exports = [
4578 throw err ;
4679 }
4780
81+ const copilotRequest = await models . CopilotRequest . findOne ( {
82+ where : { id : opportunity . copilotRequestId } ,
83+ transaction : t ,
84+ } ) ;
85+
4886 const application = await models . CopilotApplication . findOne ( {
4987 where : { id : applicationId , opportunityId : copilotOpportunityId } ,
5088 transaction : t ,
5189 } ) ;
5290
91+
5392 if ( ! application ) {
5493 const err = new Error ( 'No such application available' ) ;
5594 err . status = 400 ;
@@ -65,59 +104,179 @@ module.exports = [
65104 const projectId = opportunity . projectId ;
66105 const userId = application . userId ;
67106 const activeMembers = await models . ProjectMember . getActiveProjectMembers ( projectId , t ) ;
107+ const updateCopilotOpportunity = async ( ) => {
108+ const transaction = await models . sequelize . transaction ( ) ;
109+ const memberDetails = await util . getMemberDetailsByUserIds ( [ application . userId ] , req . log , req . id ) ;
110+ const member = memberDetails [ 0 ] ;
111+ req . log . debug ( `Updating opportunity: ${ JSON . stringify ( opportunity ) } ` ) ;
112+ await opportunity . update ( {
113+ status : COPILOT_OPPORTUNITY_STATUS . COMPLETED ,
114+ } , {
115+ transaction,
116+ } ) ;
117+ req . log . debug ( `Updating application: ${ JSON . stringify ( application ) } ` ) ;
118+ await application . update ( {
119+ status : COPILOT_APPLICATION_STATUS . ACCEPTED ,
120+ } , {
121+ transaction,
122+ } ) ;
68123
69- const existingUser = activeMembers . find ( item => item . userId === userId ) ;
70- if ( existingUser && existingUser . role === 'copilot' ) {
71- const err = new Error ( `User is already a copilot of this project` ) ;
72- err . status = 400 ;
73- throw err ;
74- }
124+ req . log . debug ( `Updating request: ${ JSON . stringify ( copilotRequest ) } ` ) ;
125+ await copilotRequest . update ( {
126+ status : COPILOT_REQUEST_STATUS . FULFILLED ,
127+ } , {
128+ transaction ,
129+ } ) ;
75130
76- const existingInvite = await models . ProjectMemberInvite . findAll ( {
77- where : {
78- userId,
79- projectId,
80- role : PROJECT_MEMBER_ROLE . COPILOT ,
81- status : INVITE_STATUS . PENDING ,
82- } ,
83- transaction : t ,
84- } ) ;
131+ req . log . debug ( `Updating other applications: ${ JSON . stringify ( copilotRequest ) } ` ) ;
132+ await models . CopilotApplication . update ( {
133+ status : COPILOT_APPLICATION_STATUS . CANCELED ,
134+ } , {
135+ where : {
136+ opportunityId : opportunity . id ,
137+ id : {
138+ $ne : application . id ,
139+ } ,
140+ }
141+ } ) ;
85142
86- if ( existingInvite && existingInvite . length ) {
87- const err = new Error ( `User already has an pending invite to the project` ) ;
88- err . status = 400 ;
89- throw err ;
143+ req . log . debug ( `All updations done` ) ;
144+ transaction . commit ( ) ;
145+
146+ req . log . debug ( `Sending email notification` ) ;
147+ const emailEventType = CONNECT_NOTIFICATION_EVENT . EXTERNAL_ACTION_EMAIL ;
148+ const copilotPortalUrl = config . get ( 'copilotPortalUrl' ) ;
149+ const requestData = copilotRequest . data ;
150+ createEvent ( emailEventType , {
151+ data : {
152+ opportunity_details_url : `${ copilotPortalUrl } /opportunity/${ opportunity . id } ` ,
153+ work_manager_url : config . get ( 'workManagerUrl' ) ,
154+ opportunity_type : getCopilotTypeLabel ( requestData . projectType ) ,
155+ opportunity_title : requestData . opportunityTitle ,
156+ start_date : moment . utc ( requestData . startDate ) . format ( 'DD-MM-YYYY' ) ,
157+ user_name : member ? member . handle : "" ,
158+ } ,
159+ sendgrid_template_id : TEMPLATE_IDS . COPILOT_ALREADY_PART_OF_PROJECT ,
160+ recipients : [ member . email ] ,
161+ version : 'v3' ,
162+ } , req . log ) ;
163+
164+ req . log . debug ( `Email sent` ) ;
165+ } ;
166+
167+ const existingMember = activeMembers . find ( item => item . userId === userId ) ;
168+ if ( existingMember ) {
169+ req . log . debug ( `User already part of project: ${ JSON . stringify ( existingMember ) } ` ) ;
170+ if ( [ 'copilot' , 'manager' ] . includes ( existingMember . role ) ) {
171+ req . log . debug ( `User is a copilot or manager` ) ;
172+ await updateCopilotOpportunity ( ) ;
173+ } else {
174+ req . log . debug ( `User has read/write role` ) ;
175+ await models . ProjectMember . update ( {
176+ role : 'copilot' ,
177+ } , {
178+ where : {
179+ id : existingMember . id ,
180+ } ,
181+ } ) ;
182+
183+ const projectMember = await models . ProjectMember . findOne ( {
184+ where : {
185+ id : existingMember . id ,
186+ } ,
187+ } ) ;
188+
189+ req . log . debug ( `Updated project member: ${ JSON . stringify ( projectMember . get ( { plain : true } ) ) } ` ) ;
190+
191+ util . sendResourceToKafkaBus (
192+ req ,
193+ EVENT . ROUTING_KEY . PROJECT_MEMBER_UPDATED ,
194+ RESOURCES . PROJECT_MEMBER ,
195+ projectMember . get ( { plain : true } ) ,
196+ existingMember ) ;
197+ req . log . debug ( `Member updated in kafka` ) ;
198+ await updateCopilotOpportunity ( ) ;
199+ }
200+ res . status ( 200 ) . send ( { id : applicationId } ) ;
201+ return ;
90202 }
91203
92- const invite = await models . ProjectMemberInvite . create ( {
93- status : INVITE_STATUS . PENDING ,
94- role : PROJECT_MEMBER_ROLE . COPILOT ,
95- userId,
204+ const member = {
96205 projectId,
97- applicationId : application . id ,
206+ role : USER_ROLE . TC_COPILOT ,
207+ userId,
98208 createdBy : req . authUser . userId ,
99- createdAt : new Date ( ) ,
100209 updatedBy : req . authUser . userId ,
101- updatedAt : new Date ( ) ,
210+ } ;
211+ req . context = req . context || { } ;
212+ req . context . currentProjectMembers = activeMembers ;
213+ await util . addUserToProject ( req , member , t )
214+
215+ await application . update ( {
216+ status : COPILOT_APPLICATION_STATUS . ACCEPTED ,
102217 } , {
103218 transaction : t ,
104- } )
219+ } ) ;
105220
106- util . sendResourceToKafkaBus (
107- req ,
108- EVENT . ROUTING_KEY . PROJECT_MEMBER_INVITE_CREATED ,
109- RESOURCES . PROJECT_MEMBER_INVITE ,
110- Object . assign ( { } , invite . toJSON ( ) , {
111- source : 'copilot_portal' ,
112- } ) ,
113- ) ;
221+ await opportunity . update ( {
222+ status : COPILOT_OPPORTUNITY_STATUS . COMPLETED ,
223+ } , {
224+ transaction : t ,
225+ } ) ;
114226
115- await application . update ( {
116- status : COPILOT_APPLICATION_STATUS . INVITED ,
227+
228+ await copilotRequest . update ( {
229+ status : COPILOT_REQUEST_STATUS . FULFILLED ,
117230 } , {
118231 transaction : t ,
119232 } ) ;
120233
234+ const sendEmailToCopilot = async ( ) => {
235+ const memberDetails = await util . getMemberDetailsByUserIds ( [ application . userId ] , req . log , req . id ) ;
236+ const member = memberDetails [ 0 ] ;
237+ req . log . debug ( `Sending email notification to accepted copilot` ) ;
238+ const emailEventType = CONNECT_NOTIFICATION_EVENT . EXTERNAL_ACTION_EMAIL ;
239+ const copilotPortalUrl = config . get ( 'copilotPortalUrl' ) ;
240+ const requestData = copilotRequest . data ;
241+ createEvent ( emailEventType , {
242+ data : {
243+ opportunity_details_url : `${ copilotPortalUrl } /opportunity/${ opportunity . id } ` ,
244+ opportunity_title : requestData . opportunityTitle ,
245+ start_date : moment . utc ( requestData . startDate ) . format ( 'DD-MM-YYYY' ) ,
246+ user_name : member ? member . handle : "" ,
247+ } ,
248+ sendgrid_template_id : TEMPLATE_IDS . COPILOT_APPLICATION_ACCEPTED ,
249+ recipients : [ member . email ] ,
250+ version : 'v3' ,
251+ } , req . log ) ;
252+
253+ req . log . debug ( `Email sent to copilot` ) ;
254+ } ;
255+
256+ await sendEmailToCopilot ( ) ;
257+
258+ // Cancel other applications
259+ const otherApplications = await models . CopilotApplication . findAll ( {
260+ where : {
261+ opportunityId : copilotOpportunityId ,
262+ id : {
263+ [ Op . notIn ] : [ applicationId ] ,
264+ } ,
265+ } ,
266+ transaction : t ,
267+ } ) ;
268+
269+ // Send email to all applicants about opportunity completion
270+ await sendEmailToAllApplicants ( copilotRequest , otherApplications ) ;
271+
272+ for ( const otherApplication of otherApplications ) {
273+ await otherApplication . update ( {
274+ status : COPILOT_APPLICATION_STATUS . CANCELED ,
275+ } , {
276+ transaction : t ,
277+ } ) ;
278+ }
279+
121280 res . status ( 200 ) . send ( { id : applicationId } ) ;
122281 } ) . catch ( err => next ( err ) ) ;
123282 } ,
0 commit comments