public async Task <IActionResult> CreateRequestAsync([FromBody] Request request) { // Look up the channel to notify var mapping = await this.channelMappingRepository.GetByCategoryAsync(request.Category); if (mapping == null) { this.logger.LogError("Failed to create consult request. No mapping exists for consult category {ConsultCategory}.", request.Category); return(this.BadRequest(new UnsuccessfulResponse { Reason = "Consult category is not valid" })); } // Lookup the Channel var channel = await this.channelRepository.GetByChannelIdAsync(mapping.ChannelId); if (channel == null) { this.logger.LogError("Failed to create consult request. Channel {ChannelId} does not exist, but a mapping to that channel exists.", mapping.ChannelId); return(this.BadRequest(new UnsuccessfulResponse { Reason = "New consult requests cannot be made for this category" })); } // Save the new request in the database request.Id = Guid.NewGuid(); request.FriendlyId = this.GetFriendlyId(); request.Status = RequestStatus.Unassigned; request.BookingsBusinessId = mapping.BookingsBusiness.Id; request.BookingsServiceId = mapping.BookingsService.Id; request.CreatedDateTime = DateTime.Now; await this.requestRepository.AddAsync(request); // Get the new request adaptive card var hostDomain = this.azureADOptions.Value.HostDomain; var consultAttachment = CardFactory.CreateConsultAttachment(request, $"https://{hostDomain}", this.localizer); // Send the proactive message to the channel var conversationParameter = await ProactiveUtil.SendChannelProactiveMessageAsync(consultAttachment, channel.ChannelId, channel.ServiceUrl, this.botOptions.Value); // rebuild local request with id from object request.ConversationId = conversationParameter.Id; request.ActivityId = conversationParameter.ActivityId; // Save the new request in the database await this.requestRepository.UpsertAsync(request); return(this.NoContent()); }
public async Task <IActionResult> AssignRequestAsync(string consultId, [FromBody] AssignConsultRequestBody requestBody, [FromHeader] string authorization) { string ssoToken = authorization.Substring("Bearer".Length + 1); // Extract user's object ID from claims var assignerObjectId = this.GetUserObjectId(); var assignerUserName = this.GetUserName(); if (string.IsNullOrWhiteSpace(assignerObjectId)) { this.logger.LogError("Failed to assign consult request. The assigner's object ID was null or empty."); return(this.Unauthorized()); } if (string.IsNullOrEmpty(consultId)) { return(this.BadRequest(new UnsuccessfulResponse { Reason = "Invalid consult ID provided" })); } // Handle assign to another agent var assigneeObjectId = assignerObjectId; if (requestBody.Agent != null && !string.IsNullOrEmpty(requestBody.Agent.Id) && !string.IsNullOrEmpty(requestBody.Agent.DisplayName)) { var canAssignOther = await this.CheckSupervisorAsync(consultId); if (!canAssignOther) { return(this.Unauthorized()); } assigneeObjectId = requestBody.Agent.Id; } // Lookup the agent var assigneeAgent = await this.agentRepository.GetByObjectIdAsync(assigneeObjectId); if (assigneeAgent == null) { this.logger.LogError("Failed to assign consult request. Agent with ID {UserObjectId} does not exist", assigneeObjectId); return(this.BadRequest(new UnsuccessfulResponse { Reason = "Invalid agent provided" })); } // Set assignee user name based on self-assign vs assign to other var assigneeUserName = assigneeObjectId == assignerObjectId ? assignerUserName : assigneeAgent.Name; // Load existing consult from DB var request = await this.requestRepository.GetAsync(new CosmosItemKey(consultId, consultId)); if (request == null) { this.logger.LogError("Failed to assign consult request {ConsultRequestId}. The consult request does not exist.", consultId); return(this.NotFound(new UnsuccessfulResponse { Reason = "Consult request not found" })); } if (request.Status != RequestStatus.Unassigned && request.Status != RequestStatus.ReassignRequested) { this.logger.LogWarning("Failed to assign consult request {ConsultRequestId}. The consult request is already assigned.", consultId); return(this.StatusCode(403, new UnsuccessfulResponse { Reason = "The consult is already assigned" })); } // Update consult request fields from request body request.AssignedTimeBlock = requestBody.SelectedTimeBlock; string assigneeStaffMemberId = assigneeAgent.BookingsStaffMemberId; GraphBeta.BookingAppointment bookingsResult = null; bool didBookingsSucceed = false; bool didRetrieveNewStaffMemberId = false; while (!didBookingsSucceed && !didRetrieveNewStaffMemberId) { // If we don't have the agent's staff member ID, try getting it from Graph if (string.IsNullOrEmpty(assigneeStaffMemberId)) { try { assigneeStaffMemberId = await GraphUtil.GetBookingsStaffMemberId(ssoToken, request.BookingsBusinessId, assigneeAgent.UserPrincipalName, this.azureADOptions.Value); } catch (Graph.ServiceException) { // This failure could also be because the caller doesn't have permission this.logger.LogError("Failed to assign consult request {ConsultRequestId}. Bookings staff member ID for agent {AgentUPN} could not be retrieved.", consultId, assigneeAgent.UserPrincipalName); return(this.BadRequest(new UnsuccessfulResponse { Reason = "The assignee is not a valid staff member in Bookings." })); } if (string.IsNullOrEmpty(assigneeStaffMemberId)) { this.logger.LogError("Failed to assign consult request {ConsultRequestId}. Bookings staff member ID for agent {AgentUPN} could not be retrieved.", consultId, assigneeAgent.UserPrincipalName); return(this.BadRequest(new UnsuccessfulResponse { Reason = "The assignee is not a valid staff member in Bookings." })); } didRetrieveNewStaffMemberId = true; } // Create/update Bookings appointment try { if (request.Status == RequestStatus.Unassigned) { bookingsResult = await GraphUtil.CreateBookingsAppointment(ssoToken, request, assigneeStaffMemberId, this.azureADOptions.Value); } else { await GraphUtil.UpdateBookingsAppointment(ssoToken, request, assigneeStaffMemberId, this.azureADOptions.Value); } didBookingsSucceed = true; } catch (Graph.ServiceException) { // Will try retrieving a fresh staff member ID assigneeStaffMemberId = null; } } // Ensure appointment creation succeeded if (!didBookingsSucceed) { this.logger.LogError("Failed to assign consult request {ConsultRequestId}. The Bookings appointment could not be created/updated for agent {AgentId}.", consultId, assigneeObjectId); return(this.BadRequest(new UnsuccessfulResponse { Reason = "Unable to create/update the Bookings appointment." })); } // Update DB with agent's staff member ID, if needed if (assigneeAgent.BookingsStaffMemberId != assigneeStaffMemberId) { assigneeAgent.BookingsStaffMemberId = assigneeStaffMemberId; await this.agentRepository.UpsertAsync(assigneeAgent); } // Update consult request fields with Bookings info, if needed if (request.Status == RequestStatus.Unassigned) { request.BookingsAppointmentId = bookingsResult.Id; request.JoinUri = bookingsResult.OnlineMeetingUrl; } // Update other consult request fields var currentTime = DateTime.UtcNow; request.Status = RequestStatus.Assigned; request.AssignedToId = assigneeObjectId; request.AssignedToName = assigneeUserName; request.Activities = request.Activities ?? new List <Activity>(); request.Activities.Add(new Activity { Id = Guid.NewGuid(), Type = ActivityType.Assigned, ActivityForUserId = assigneeObjectId, ActivityForUserName = assigneeUserName, Comment = !string.IsNullOrWhiteSpace(requestBody.Comments) ? requestBody.Comments : null, CreatedByName = assignerUserName, CreatedById = new Guid(assignerObjectId), CreatedDateTime = currentTime, }); // Update consult request in DB await this.requestRepository.UpsertAsync(request); // Look up the channel to notify var mapping = await this.channelMappingRepository.GetByCategoryAsync(request.Category); if (mapping == null) { this.logger.LogError("Failed to send channel message for assignment of consult {ConsultRequestId}. Channel mapping does not exist for request category {RequestCategory}.", consultId, request.Category); return(this.BadRequest(new UnsuccessfulResponse { Reason = "Consult requests in this category cannot be assigned" })); } // Lookup the Channel var channel = await this.channelRepository.GetByChannelIdAsync(mapping.ChannelId); if (channel == null) { this.logger.LogError("Failed to send channel message for assignment of consult {ConsultRequestId}. Channel {ChannelId} does not exist, but a mapping to that channel exists.", consultId, mapping.ChannelId); return(this.BadRequest(new UnsuccessfulResponse { Reason = "Consult requests in this category cannot be reassigned" })); } // Get the assigned consult adaptive card for channel var baseUrl = $"https://{this.azureADOptions.Value.HostDomain}"; var consultChannelCard = CardFactory.CreateAssignedConsultAttachment(request, baseUrl, assigneeUserName, false, this.localizer); // Update the proactive message to the channel await ProactiveUtil.UpdateChannelProactiveMessageAsync(consultChannelCard, channel.ServiceUrl, request.ConversationId, request.ActivityId, this.botOptions.Value); // Temporarily set agent's locale as current culture before using localizer Microsoft.Bot.Schema.Attachment consultPersonalCard; using (new CultureSwitcher(assigneeAgent.Locale, assigneeAgent.Locale)) { // Get the assigned consult adaptive card for 1:1 chat consultPersonalCard = CardFactory.CreateAssignedConsultAttachment(request, baseUrl, assigneeUserName, true, this.localizer); } // Send the proactive message to the agent await ProactiveUtil.SendChatProactiveMessageAsync(consultPersonalCard, assigneeAgent.TeamsId, this.azureADOptions.Value.TenantId, assigneeAgent.ServiceUrl, this.botOptions.Value); return(this.Ok(request)); }
public async Task <IActionResult> ReassignRequestAsync([FromHeader] string authorization, string consultId, [FromBody] ReassignConsultRequestBody body) { if (string.IsNullOrEmpty(consultId)) { return(this.BadRequest(new UnsuccessfulResponse { Reason = "Invalid consult ID provided" })); } // Extract user's object ID from claims var userObjectId = this.GetUserObjectId(); var userName = this.GetUserName(); var currentTime = DateTime.UtcNow; var userObjectIdGuid = new Guid(userObjectId); // Load existing consult from DB var request = await this.requestRepository.GetAsync(new CosmosItemKey(consultId, consultId)); if (request == null) { this.logger.LogError("Failed to reassign consult request {ConsultRequestId}. The consult request does not exist.", consultId); return(this.NotFound(new UnsuccessfulResponse { Reason = "Consult request not found" })); } if (request.Status != RequestStatus.Assigned) { this.logger.LogWarning("Failed to reassign consult request {ConsultRequestId}. The consult request is not in the 'Assigned' state.", consultId); return(this.StatusCode(403, new UnsuccessfulResponse { Reason = "The consult cannot be reassigned." })); } request.Status = RequestStatus.ReassignRequested; // Add activity to consult request request.Activities = request.Activities ?? new List <Activity>(); request.Activities.Add(new Activity { Id = Guid.NewGuid(), Type = ActivityType.ReassignRequested, ActivityForUserId = request.AssignedToId, ActivityForUserName = request.AssignedToName, CreatedByName = userName, CreatedById = userObjectIdGuid, CreatedDateTime = currentTime, }); // Store updated consult request await this.requestRepository.UpsertAsync(request); // Look up the channel to notify var mapping = await this.channelMappingRepository.GetByCategoryAsync(request.Category); if (mapping == null) { this.logger.LogError("Failed to send channel message for reassignment of consult {ConsultRequestId}. Channel mapping does not exist for request category {RequestCategory}", consultId, request.Category); return(this.BadRequest(new UnsuccessfulResponse { Reason = "Consult requests in this category cannot be reassigned" })); } // Lookup the Channel var channel = await this.channelRepository.GetByChannelIdAsync(mapping.ChannelId); if (channel == null) { // throw error...Category does not exists. this.logger.LogError("Failed to send channel message for reassignment of consult {ConsultRequestId}. Channel {ChannelId} does not exist, but a mapping to that channel exists", consultId, mapping.ChannelId); return(this.BadRequest(new UnsuccessfulResponse { Reason = "Consult requests in this category cannot be reassigned" })); } // Get the assigned consult adaptive card for channel var baseUrl = $"https://{this.azureADOptions.Value.HostDomain}"; List <object> mentionedAgents = new List <object>(); string photo = null; string ssoToken = authorization.Substring("Bearer".Length + 1); var displayPicGraphResponse = await GraphUtil.GetUserDisplayPhotoAsync(ssoToken, userObjectId, this.azureADOptions.Value); if (displayPicGraphResponse.FailureReason == string.Empty) { photo = "data:image/jpeg;base64," + Convert.ToBase64String(displayPicGraphResponse.Result); } var comment = body.Comments == null ? string.Empty : body.Comments; foreach (var agentIdName in body.Agents) { var agent = await this.agentRepository.GetByObjectIdAsync(agentIdName.Id); mentionedAgents.Add(new { agentUserId = agent.TeamsId, agentName = agentIdName.DisplayName, }); } var reassignConsultChannelCard = CardFactory.CreateReassignConsultAttachment(request, mentionedAgents, baseUrl, comment, userName, photo, this.localizer); // Update the channel message, and reply to it so that it gets pushed to the bottom var replyString = this.localizer.GetString("ConsultNeedsReassignment", userName); await ProactiveUtil.UpdateChannelProactiveMessageAsync(reassignConsultChannelCard, channel.ServiceUrl, request.ConversationId, request.ActivityId, this.botOptions.Value); await ProactiveUtil.ReplyToChannelMessageAsync(replyString, channel.ServiceUrl, request.ConversationId, request.ActivityId, this.botOptions.Value); return(this.Ok(request)); }