diff --git a/config/default.js b/config/default.js index 1ddb0594..1a012a6d 100644 --- a/config/default.js +++ b/config/default.js @@ -177,7 +177,7 @@ module.exports = { DEFAULT_TIMELINE_TEMPLATE_ID: process.env.DEFAULT_TIMELINE_TEMPLATE_ID || '53a307ce-b4b3-4d6f-b9a1-3741a58f77e6', DEFAULT_TRACK_ID: process.env.DEFAULT_TRACK_ID || '9b6fc876-f4d9-4ccb-9dfd-419247628825', // the minimum matching rate when searching roles by skills - ROLE_MATCHING_RATE: process.env.ROLE_MATCHING_RATE || 0.70, + ROLE_MATCHING_RATE: process.env.ROLE_MATCHING_RATE || 0.66, // member groups representing Wipro or TopCoder employee INTERNAL_MEMBER_GROUPS: process.env.INTERNAL_MEMBER_GROUPS || ['20000000', '20000001', '20000003', '20000010', '20000015'], // Topcoder skills cache time in minutes @@ -265,5 +265,8 @@ module.exports = { // The interview completed past time for fetching interviews INTERVIEW_COMPLETED_PAST_TIME: process.env.INTERVIEW_COMPLETED_PAST_TIME || 'PT4H', // The time before resource booking expiry when we should start sending notifications - RESOURCE_BOOKING_EXPIRY_TIME: process.env.RESOURCE_BOOKING_EXPIRY_TIME || 'P21D' + RESOURCE_BOOKING_EXPIRY_TIME: process.env.RESOURCE_BOOKING_EXPIRY_TIME || 'P21D', + // The Stripe + STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY, + CURRENCY: process.env.CURRENCY || 'usd' } diff --git a/docs/swagger.yaml b/docs/swagger.yaml index bfcf3abd..245f6abc 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -3538,7 +3538,56 @@ paths: content: application/json: schema: - $ref: "#/components/schemas/Error" + $ref: "#/components/schemas/Error" + /taas-teams/isExternalMember: + post: + tags: + - Teams + description: | + Finds whether member is internal or external + + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/IsExternalMemberRequestBody" + responses: + "200": + description: OK + content: + application/json: + schema: + $ref: "#/components/schemas/IsExternalMemberResponse" + "400": + description: Bad request + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "401": + description: Not authenticated + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "403": + description: Forbidden + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "409": + description: Conflict + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + "500": + description: Internal Server Error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" /taas-teams/members-suggest/{fragment}: get: tags: @@ -5754,12 +5803,9 @@ components: numberOfResources: type: number description: "No. of resources required." - rates: + rate: type: number description: "Weekly rates" - durationWeeks: - type: number - description: "No. of weeks" CalculateAmountResponse: properties: totalAmount: @@ -5775,6 +5821,16 @@ components: paymentIntentToken: type: string description: " Token required by stripe for completing payment." + IsExternalMemberRequestBody: + properties: + totalAmount: + type: number + description: "Member id" + IsExternalMemberResponse: + properties: + paymentIntentToken: + type: boolean + description: "Is the user external member" SubmitTeamRequestBody: properties: teamName: @@ -5786,6 +5842,9 @@ components: refCode: type: string description: "Optional referral code" + intakeSource: + type: string + description: "The source of the intake." positions: type: array description: "The array of positions" diff --git a/src/controllers/TeamController.js b/src/controllers/TeamController.js index e34fa943..addb01dd 100644 --- a/src/controllers/TeamController.js +++ b/src/controllers/TeamController.js @@ -173,6 +173,16 @@ async function createPayment(req, res) { res.send(await service.createPayment(req.body.totalAmount)); } +/** + * + * @param req the request + * @param res the response + */ + async function isExternalMember(req, res) { + res.send(await service.isExternalMember(req.body.memberId)); +} + + module.exports = { searchTeams, getTeam, @@ -189,5 +199,6 @@ module.exports = { searchSkills, suggestMembers, createPayment, - calculateAmount + calculateAmount, + isExternalMember } diff --git a/src/routes/TeamRoutes.js b/src/routes/TeamRoutes.js index 3c83d5f2..1412b9b9 100644 --- a/src/routes/TeamRoutes.js +++ b/src/routes/TeamRoutes.js @@ -112,12 +112,24 @@ module.exports = { post: { controller: "TeamController", method: "calculateAmount", + auth: 'jwt', + scopes: [constants.Scopes.CREATE_TAAS_TEAM] }, }, "/taas-teams/createPayment": { post: { controller: "TeamController", method: "createPayment", + auth: 'jwt', + scopes: [constants.Scopes.CREATE_TAAS_TEAM] + }, + }, + "/taas-teams/isExternalMember": { + post: { + controller: "TeamController", + method: "isExternalMember", + auth: 'jwt', + scopes: [] + } }, -} } diff --git a/src/services/EmailNotificationService.js b/src/services/EmailNotificationService.js index 5572933c..a561445e 100644 --- a/src/services/EmailNotificationService.js +++ b/src/services/EmailNotificationService.js @@ -33,7 +33,7 @@ async function getProjectWithId (projectId) { project = await helper.getProjectById(helper.getAuditM2Muser(), projectId) } catch (err) { localLogger.error( - `exception fetching project with id: ${projectId} Status Code: ${err.status} message: ${err.response.text}`, 'getProjectWithId') + `exception fetching project with id: ${projectId} Status Code: ${err.status} message: ${_.get(err, 'response.text', err.toString())}`, 'getProjectWithId') } return project @@ -65,7 +65,7 @@ async function getUserWithId (userId) { user = await helper.ensureUserById(userId) } catch (err) { localLogger.error( - `exception fetching user with id: ${userId} Status Code: ${err.status} message: ${err.response.text}`, 'getUserWithId') + `exception fetching user with id: ${userId} Status Code: ${err.status} message: ${_.get(err, 'response.text', err.toString())}`, 'getUserWithId') } return user @@ -120,7 +120,11 @@ async function sendCandidatesAvailableEmails () { const jobs = _.map(jobsDao, dao => dao.dataValues) const projectIds = _.uniq(_.map(jobs, job => job.projectId)) + + localLogger.debug(`[sendCandidatesAvailableEmails]: Found ${projectIds.length} projects with Job Candidates awaiting for review.`) + // for each unique project id, send an email + let sentCount = 0 for (const projectId of projectIds) { const project = await getProjectWithId(projectId) if (!project) { continue } @@ -172,7 +176,10 @@ async function sendCandidatesAvailableEmails () { description: 'Candidates are available for review' } }) + + sentCount++ } + localLogger.debug(`[sendCandidatesAvailableEmails]: Sent notifications for ${sentCount} of ${projectIds.length} projects with Job Candidates awaiting for review.`) } /** @@ -216,6 +223,10 @@ async function sendInterviewComingUpEmails () { raw: true }) + localLogger.debug(`[sendInterviewComingUpEmails]: Found ${interviews.length} interviews which are coming soon.`) + + let sentHostCount = 0 + let sentGuestCount = 0 for (const interview of interviews) { // send host email const data = await getDataForInterview(interview) @@ -233,6 +244,8 @@ async function sendInterviewComingUpEmails () { description: 'Interview Coming Up' } }) + + sentHostCount++ } else { localLogger.error(`Interview id: ${interview.id} host email not present`, 'sendInterviewComingUpEmails') } @@ -250,10 +263,14 @@ async function sendInterviewComingUpEmails () { description: 'Interview Coming Up' } }) + + sentGuestCount++ } else { localLogger.error(`Interview id: ${interview.id} guest emails not present`, 'sendInterviewComingUpEmails') } } + + localLogger.debug(`[sendInterviewComingUpEmails]: Sent notifications for ${sentHostCount} hosts and ${sentGuestCount} guest of ${interviews.length} interviews which are coming soon.`) } /** @@ -288,6 +305,9 @@ async function sendInterviewCompletedEmails () { raw: true }) + localLogger.debug(`[sendInterviewCompletedEmails]: Found ${interviews.length} interviews which must be ended by now.`) + + let sentCount = 0 for (const interview of interviews) { if (_.isEmpty(interview.hostEmail)) { localLogger.error(`Interview id: ${interview.id} host email not present`) @@ -308,7 +328,11 @@ async function sendInterviewCompletedEmails () { description: 'Interview Completed' } }) + + sentCount++ } + + localLogger.debug(`[sendInterviewCompletedEmails]: Sent notifications for ${sentCount} of ${interviews.length} interviews which must be ended by now.`) } /** @@ -341,6 +365,10 @@ async function sendPostInterviewActionEmails () { }) const projectIds = _.uniq(_.map(jobs, job => job.projectId)) + + localLogger.debug(`[sendPostInterviewActionEmails]: Found ${projectIds.length} projects with ${completedJobCandidates.length} Job Candidates with interview completed awaiting for an action.`) + + let sentCount = 0 for (const projectId of projectIds) { const project = await getProjectWithId(projectId) if (!project) { continue } @@ -374,7 +402,11 @@ async function sendPostInterviewActionEmails () { description: 'Post Interview Candidate Action Reminder' } }) + + sentCount++ } + + localLogger.debug(`[sendPostInterviewActionEmails]: Sent notifications for ${sentCount} of ${projectIds.length} projects with Job Candidates with interview completed awaiting for an action.`) } /** @@ -410,6 +442,9 @@ async function sendResourceBookingExpirationEmails () { }) const projectIds = _.uniq(_.map(expiringResourceBookings, rb => rb.projectId)) + localLogger.debug(`[sendResourceBookingExpirationEmails]: Found ${projectIds.length} projects with ${expiringResourceBookings.length} Resource Bookings expiring in less than 3 weeks.`) + + let sentCount = 0 for (const projectId of projectIds) { const project = await getProjectWithId(projectId) if (!project) { continue } @@ -447,7 +482,11 @@ async function sendResourceBookingExpirationEmails () { description: 'Upcoming Resource Booking Expiration' } }) + + sentCount++ } + + localLogger.debug(`[sendResourceBookingExpirationEmails]: Sent notifications for ${sentCount} of ${projectIds.length} projects with Resource Bookings expiring in less than 3 weeks.`) } /** diff --git a/src/services/PaymentService.js b/src/services/PaymentService.js index 93d04e41..b588c9ce 100644 --- a/src/services/PaymentService.js +++ b/src/services/PaymentService.js @@ -96,7 +96,7 @@ async function createChallenge (challenge, token) { return challengeId } catch (err) { localLogger.error({ context: 'createChallenge', message: `Status Code: ${err.status}` }) - localLogger.error({ context: 'createChallenge', message: err.response.text }) + localLogger.error({ context: 'createChallenge', message: _.get(err, 'response.text', err.toString()) }) throw err } } @@ -126,7 +126,7 @@ async function addResourceToChallenge (id, handle, token) { } } localLogger.error({ context: 'addResourceToChallenge', message: `Status Code: ${err.status}` }) - localLogger.error({ context: 'addResourceToChallenge', message: err.response.text }) + localLogger.error({ context: 'addResourceToChallenge', message: _.get(err, 'response.text', err.toString()) }) throw err } } @@ -153,7 +153,7 @@ async function activateChallenge (id, token) { } } localLogger.error({ context: 'activateChallenge', message: `Status Code: ${err.status}` }) - localLogger.error({ context: 'activateChallenge', message: err.response.text }) + localLogger.error({ context: 'activateChallenge', message: _.get(err, 'response.text', err.toString()) }) throw err } } @@ -189,7 +189,7 @@ async function closeChallenge (id, userId, userHandle, token) { } } localLogger.error({ context: 'closeChallenge', message: `Status Code: ${err.status}` }) - localLogger.error({ context: 'closeChallenge', message: err.response.text }) + localLogger.error({ context: 'closeChallenge', message: _.get(err, 'response.text', err.toString()) }) throw err } } diff --git a/src/services/TeamService.js b/src/services/TeamService.js index cfac33db..d6870bfe 100644 --- a/src/services/TeamService.js +++ b/src/services/TeamService.js @@ -19,7 +19,7 @@ const { getAuditM2Muser } = require('../common/helper') const { matchedSkills, unMatchedSkills } = require('../../scripts/emsi-mapping/esmi-skills-mapping') const Role = models.Role const RoleSearchRequest = models.RoleSearchRequest -const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY); +const stripe = require("stripe")(config.STRIPE_SECRET_KEY,{maxNetworkRetries: 5}); const emailTemplates = helper.getEmailTemplatesForKey('teamTemplates') @@ -741,6 +741,8 @@ async function roleSearchRequest (currentUser, data) { if (!_.isUndefined(data.roleId)) { role = await Role.findById(data.roleId) role = role.toJSON() + role.matchedSkills = role.listOfSkills + role.unMatchedSkills = [] role.skillsMatch = 1 // if skills is provided then use skills to find role } else if (!_.isUndefined(data.skills)) { @@ -791,7 +793,8 @@ async function getRoleBySkills (skills) { where: { listOfSkills: { [Op.overlap]: skills } }, raw: true } - const roles = await Role.findAll(queryCriteria) + let roles = await Role.findAll(queryCriteria) + roles = _.filter(roles, role => _.find(role.rates, r => r.global && r.rate20Global && r.rate30Global)) if (roles.length > 0) { let result = _.each(roles, role => { // role matched skills list @@ -810,7 +813,10 @@ async function getRoleBySkills (skills) { } } // if no matching role found then return Custom role or empty object - return await Role.findOne({ where: { name: { [Op.iLike]: 'Custom' } }, raw: true }) || {} + const customRole = await Role.findOne({ where: { name: { [Op.iLike]: 'Custom' } }, raw: true }) || {} + customRole.rates[0].rate30Global = customRole.rates[0].global * 0.75 + customRole.rates[0].rate20Global = customRole.rates[0].global * 0.5 + return customRole } getRoleBySkills.schema = Joi.object() @@ -1032,7 +1038,8 @@ async function createTeam (currentUser, data) { details: { positions: data.positions, utm: { - code: data.refCode + code: data.refCode, + intakeSource: data.intakeSource } } } @@ -1071,6 +1078,7 @@ createTeam.schema = Joi.object() teamName: Joi.string().required(), teamDescription: Joi.string(), refCode: Joi.string(), + intakeSource: Joi.string(), positions: Joi.array().items( Joi.object().keys({ roleName: Joi.string().required(), @@ -1158,11 +1166,12 @@ suggestMembers.schema = Joi.object().keys({ /** * Calculates total amount - * @param {Object} body + * @param {Object} amount * @returns {int} totalAmount */ - async function calculateAmount(body) { - const totalAmount = body.numberOfResources * body.rates * body.durationWeeks; + async function calculateAmount(amount) { + let totalAmount = 0; + _.forEach(amount, amt => totalAmount += amt.numberOfResources * amt.rate) return { totalAmount }; } @@ -1172,9 +1181,10 @@ suggestMembers.schema = Joi.object().keys({ * @returns {string} paymentIntentToken */ async function createPayment(totalAmount) { + const dollarToCents = (totalAmount*100); const paymentIntent = await stripe.paymentIntents.create({ - amount: totalAmount, - currency: process.env.CURRENCY, + amount: dollarToCents, + currency: config.CURRENCY, }); return { paymentIntentToken: paymentIntent.client_secret }; } diff --git a/src/services/WorkPeriodPaymentService.js b/src/services/WorkPeriodPaymentService.js index ac0fbe19..81ca79e9 100644 --- a/src/services/WorkPeriodPaymentService.js +++ b/src/services/WorkPeriodPaymentService.js @@ -69,8 +69,8 @@ async function _updateChallenge (challengeId, data) { await helper.updateChallenge(challengeId, body) logger.debug({ component: 'WorkPeriodPaymentService', context: 'updateChallenge', message: `Challenge with id ${challengeId} is updated` }) } catch (err) { - logger.error({ component: 'WorkPeriodPaymentService', context: 'updateChallenge', message: err.response.text }) - throw new errors.BadRequestError(`Cannot update the the challenge: ${err.response.text}`) + logger.error({ component: 'WorkPeriodPaymentService', context: 'updateChallenge', message: _.get(err, 'response.text', err.toString()) }) + throw new errors.BadRequestError(`Cannot update the the challenge: ${_.get(err, 'response.text', err.toString())}`) } } }