/// <summary> /// Check call cannot be reached /// </summary> /// <param name="status"></param> /// <param name="isSpecialist"></param> /// <returns>TRUE: call fails - FAILD: call completed</returns> private bool CannotBeReached(string callSid, CallDto call, string status, bool notifyForCustomer) { var result = false; bool isTalkNow = true; if (call.Caller.Id.Equals(call.Booking.Specialist.Id)) { isTalkNow = false; } // Case1: Call status == Canceled, Busy, Failed, NoAnswer > Call status of Twilio // Case2: CallerSid == null && Booking.Deferral == 0 && isTalkNow > TALKNOW > Specialist pick up but don't talk now or defer/re-schedule // Case3: call.CallerSid == null && !isTalkNow > CALL FROM BOOKING > Customer pick up but don't press key to talk with specialist if (status == CallStatus.Canceled || status == CallStatus.Busy || status == CallStatus.Failed || status == CallStatus.NoAnswer || (call.CallerSid == null && call.Booking.Deferral == 0 && isTalkNow) || (call.CallerSid == null && !isTalkNow)) // || call.ReceiverSid == null) // !Important { if (notifyForCustomer) // Notify for customer { // Show popup notify unreached CallHelper.ShowPopupNotifyInConference(call.Booking.Customer, ConferencePopupConst.ConsultantUnReachedTitle, string.Format(ConferencePopupConst.UnReachedContent, call.Booking.Specialist.Name)); if (!isTalkNow) { // Terminate call of customer Services.Call.Terminate(call.ReceiverSid); } // Update booking status Services.Booking.UpdateBookingStatus(call.Booking.Id, BookingStatus.Finish); } else // Notify for specialist { // Check call is talk now or call from book // Make call from book if (call.Receiver.Id.Equals(call.Booking.Customer.Id) && call.ReceiverSid == callSid) { // Update call fails Services.Call.UpdateCallFails(call.Booking.Id); if (call.Booking.CallFails < 2) { // Notify to customer AddBookingEvent(call, true); // Update booking status Services.Booking.UpdateBookingStatus(call.Booking.Id, BookingStatus.Confirmed); } if (call.Booking.CallFails == 2) { // Show popup notify for specialist CallHelper.ShowPopupNotifyInConference(call.Booking.Specialist, ConferencePopupConst.CallFailsTitle, string.Format(ConferencePopupConst.CallFailsContent, call.Booking.CallFails)); // Add to booking event Services.Booking.CreateBookingEvent(new BookingEventDto { Booking = call.Booking, SourceUser = call.Booking.Specialist, TargetUser = call.Booking.Customer, ShortDescription = AlertShortDescription.FailedAttemptsCancellation, Description = AlertDescription.FailedAttemptsCancellation, CreatedDate = DateTime.UtcNow, ModifiedDate = DateTime.UtcNow, IsRead = false, EmailAlertSubject = EmailAlertSubject.FailedAttemptsCancellation, EmailAlertDetail = EmailAlertDetail.FailedAttemptsCancellation, SMSAlertDetail = SMSAlertDetail.FailedAttemptsCancellation }); // Create minimum charge invoice when customer no answer three times try { PaymentHelper.CreateTransaction(call.Booking, call.Booking.CustomerMinCharge, true, Services); Services.Invoices.CreateConsultationInvoice(call.Booking.SpecialistMinCharge, call.Booking.Id, true, false, null); } catch (Exception e) { Log.Error("Create minimum charge invoice", e); throw; } // Update booking status Services.Booking.UpdateBookingStatus(call.Booking.Id, BookingStatus.Finish); } } else // Talk now > Specialist is receiver { // Terminate call of specialist Services.Call.Terminate(call.ReceiverSid); // Update booking status Services.Booking.UpdateBookingStatus(call.Booking.Id, BookingStatus.Finish); } // Show popup notify unreached CallHelper.ShowPopupNotifyInConference(call.Booking.Specialist, ConferencePopupConst.CustomerUnReachedTitle, string.Format(ConferencePopupConst.UnReachedContent, call.Booking.Customer.Name)); } // Update call status Services.Call.UpdateCallStatus(callSid, status); result = true; } return result; }
public void Update_CallDto() { // Setup dependence var settingMock = new Mock<ISettings>(); var componentMock = new Mock<IComponents>(); var repositoryMock = new Mock<IRepository>(); var uowMock = new Mock<IUnitOfWork>(); var serviceLocatorMock = new Mock<IServiceLocator>(); serviceLocatorMock.Setup(r => r.GetInstance<IRepository>()) .Returns(repositoryMock.Object); serviceLocatorMock.Setup(r => r.GetInstance<IUnitOfWork>()) .Returns(uowMock.Object); ServiceLocator.SetLocatorProvider(() => serviceLocatorMock.Object); repositoryMock.Setup(r => r.CreateUnitOfWork()).Returns(uowMock.Object); // Arrange data Guid id = Guid.NewGuid(); CallDto callDto = new CallDto { Id = id }; // Act CallService callService = new CallService(uowMock.Object, repositoryMock.Object, settingMock.Object, componentMock.Object); var currentCall = callService.Update(callDto); // Assert repositoryMock.Verify(r => r.Update<Call>( It.Is<Call>(b => b.Id == id))); uowMock.Verify(u => u.Save()); }
/// <summary> /// Add booking event for customer /// </summary> /// <param name="call"></param> /// <returns></returns> private void AddBookingEvent(CallDto call, bool notifyForCustomer) { var sourceUser = call.Booking.Specialist; var targetUser = call.Booking.Customer; if (!notifyForCustomer) { sourceUser = call.Booking.Customer; targetUser = call.Booking.Specialist; } string shortDescription = call.Booking.CallFails == 0 ? AlertShortDescription.FailedConferenceAttemptAtFirst : AlertShortDescription.FailedConferenceAttemptAtSecond; string description = call.Booking.CallFails == 0 ? AlertDescription.FailedConferenceAttemptAtFirst : AlertDescription.FailedConferenceAttemptAtSecond; string emailAlertSubject = call.Booking.CallFails == 0 ? EmailAlertSubject.FailedConferenceAttemptAtFirst : EmailAlertSubject.FailedConferenceAttemptAtSecond; string emailAlertDetail = call.Booking.CallFails == 0 ? EmailAlertDetail.FailedConferenceAttemptAtFirst : EmailAlertDetail.FailedConferenceAttemptAtSecond; string smsAlertDetail = call.Booking.CallFails == 0 ? SMSAlertDetail.FailedConferenceAttemptAtFirst : SMSAlertDetail.FailedConferenceAttemptAtSecond; // Add to booking event for customer Services.Booking.CreateBookingEvent(new BookingEventDto { Booking = call.Booking, SourceUser = sourceUser, TargetUser = targetUser, ShortDescription = shortDescription, Description = description, CreatedDate = DateTime.UtcNow, ModifiedDate = DateTime.UtcNow, IsRead = false, EmailAlertSubject = emailAlertSubject, EmailAlertDetail = emailAlertDetail, SMSAlertDetail = smsAlertDetail }); // Reload alert and booking list NotifyHelper.ReloadAlert(call.Booking.Specialist.UserName); NotifyHelper.ReloadAlert(call.Booking.Customer.UserName); }
/// <summary> /// Update price and twilio cost /// </summary> /// <param name="callId"></param> public void UpdateCallCost(CallDto call) { if (call.CallerSid != null && call.ReceiverSid != null) { // Twilio cost decimal callerCost = GetCallBySid(call.CallerSid).Price.HasValue ? GetCallBySid(call.CallerSid).Price.Value : 0; decimal receiverCost = GetCallBySid(call.ReceiverSid).Price.HasValue ? GetCallBySid(call.ReceiverSid).Price.Value : 0; call.TwilioCost = callerCost + receiverCost; Repository.Update<CallDomain>(call.ExposedAs<CallDomain>(Repository)); UnitOfWork.Save(); } }
public TwiMLResult ConsultantAfterCallResponse(CallContextModel model, string digits) { var response = new TwilioResponse(); var message = ConferenceConst.AcctionCompleted; var specialist = new UserDto(); var call = new CallDto(); bool canWaiveFee = true; int transcriptSuccess = 2; try { if (digits.Equals("2") || digits.Equals("3") || digits.Equals("4") || digits.Equals("5")) { call = Services.Call.GetByCallSid(Request["CallSid"]); if (string.IsNullOrWhiteSpace(model.RecordUrl)) { model.RecordSid = call.RecordSid; model.RecordDuration = call.RecordDuration; model.RecordUrl = call.RecordUrl; } // Get specialist if (model.IsCustomer) // Caller is customer { specialist = Services.Users.GetUserById(model.ReceiverId); } else { specialist = Services.Users.GetUserById(model.CallerId); } int startingTime = Convert.ToInt32(Services.SystemConfig.GetByKey(ParamatricBusinessRules.STARTING_TIME.ToString()).Value); if (call.Duration <= startingTime) { canWaiveFee = false; } } switch (digits) { case "1": // Dictate your follow up action var statusCallBack = Utilities.FormatLink(Url.Action("VoiceMail", "Conference", model)); Services.Call.RedirectToVoiceMail(Request["CallSid"], statusCallBack); break; case "2": // Transcript consultation // Send transcription request transcriptSuccess = SendTranscriptionRequest(specialist.Id.ToString(), specialist.UserName, specialist.Email, call.RecordUrl, call.RecordDuration, call.Booking); if (transcriptSuccess == 0) { message = ConferenceConst.BalanceNotEnoughForTranscript; } else if (transcriptSuccess == 1) { message = ConferenceConst.TranscriptError; } break; case "3": // Play consultation record response.Pause(5); response.Play(model.RecordUrl); break; case "4": // Play consultation record and transcription // Play consultation record response.Pause(5); response.Play(model.RecordUrl); // Send transcription request transcriptSuccess = SendTranscriptionRequest(specialist.Id.ToString(), specialist.UserName, specialist.Email, call.RecordUrl, call.RecordDuration, call.Booking); if (transcriptSuccess == 0) { message = ConferenceConst.BalanceNotEnoughForTranscript; } else if (transcriptSuccess == 1) { message = ConferenceConst.TranscriptError; } break; case "5": // Waive consultation fee if (canWaiveFee) { response.Redirect(Url.Action("WaiveFeeAction", model)); } else { response.Redirect(Url.Action("ConsultantAfterCall", model)); } break; default: response.Redirect(Url.Action("ConsultantAfterCall", model)); return new TwiMLResult(response); } } catch (Exception e) { // Log Log.Error("Consultant after call response. Error: ", e); // Error response.Say(ConferenceConst.ErrorMessage, new { voice = VoiceInConference, language = LanguageInConference }); response.Hangup(); } response.Say(message, new { voice = VoiceInConference, language = LanguageInConference }); response.Redirect(Url.Action("ConsultantAfterCall", model)); return new TwiMLResult(response); }
// ====================== .CREATE CALL ====================== //// ====================== UPDATE CALL ====================== /// <summary> /// Update a call /// </summary> /// <param name="call"></param> /// <returns></returns> public CallDto Update(CallDto call) { Repository.Update<CallDomain>(call.ExposedAs<CallDomain>(Repository)); UnitOfWork.Save(); return call; }
// ============================ INTERACTING WITH TWILIO ============================ /// <summary> /// Make call for consultantion /// </summary> /// <param name="booking"></param> /// <param name="twiMLUrl"></param> /// <param name="isTalkNow"></param> /// <returns></returns> public Call MakeCallForConsultantion(BookingDto booking, string twiMLUrl, bool isTalkNow) { try { string fullPhoneNo = booking.Specialist.MobileCountryCode + booking.Specialist.MobilePhone; if (!isTalkNow) // If call from confirm booking { fullPhoneNo = booking.Customer.MobileCountryCode + booking.Customer.MobilePhone; } var call = Dial(twiMLUrl, fullPhoneNo); // Update into database // Default make call for talk now var callDto = new CallDto { ReceiverSid = call.Sid, Booking = booking, ReceiverStatus = call.Status, Caller = booking.Customer, Receiver = booking.Specialist, StartTime = DateTime.UtcNow, EndTime = DateTime.UtcNow, CreatedDate = DateTime.UtcNow, ModifiedDate = DateTime.UtcNow }; // If make call for booking if (!isTalkNow) { callDto.Caller = booking.Specialist; callDto.Receiver = booking.Customer; } // Insert to database Create(callDto); return call; } catch (Exception e) { // Log Log.Error("CallService_MakeCallForTalkNow_Error making call", e); throw new Exception("Error making call. Error: " + e.Message); } }
// ====================== .GET CALL ====================== //// ====================== CREATE CALL ====================== /// <summary> /// Create call /// </summary> /// <param name="call"></param> /// <returns></returns> public CallDto Create(CallDto call) { Repository.Insert<CallDomain>(call.ExposedAs<CallDomain>(Repository)); UnitOfWork.Save(); return call; }
/// <summary> /// show popup follow upaction for customer /// </summary> /// <param name="callLog"></param> /// <param name="totalCost"></param> public static void ShowPopupFollowUpActionAfterCall(CallDto callDto, decimal totalCost, decimal minimumCost, string userName, bool IsFavourite, bool isSendForCustomer) { string urlDownload = string.Empty; string recordUrl = string.Empty; if (!string.IsNullOrWhiteSpace(callDto.RecordSid)) { string urlTwilio = string.Format(Settings.UrlCallRecord, Settings.AccountSid); urlDownload = string.Format("{0}{1}", urlTwilio, callDto.RecordSid); var twilioServer = ConfigurationManager.AppSettings["Twilio_ApiServer"]; // Check record url recordUrl = !string.IsNullOrWhiteSpace(callDto.RecordUrl) && callDto.RecordUrl.Contains(twilioServer) ? Utilities.ConvertLinkRecord(callDto.RecordSid) : S3ReaderHelper.CombineFileS3Root(callDto.RecordUrl); } TimeSpan duration = new TimeSpan(0, 0, callDto.Duration); var context = GlobalHost.ConnectionManager.GetHubContext<FollowUpActionHub>(); UserDto sendUser = isSendForCustomer ? callDto.Booking.Specialist : callDto.Booking.Customer; foreach (var connectionId in FollowUpActionHub.connections.GetConnections(userName)) { context.Clients.Client(connectionId).showPopupFollowUpAction(new { BookingId = callDto.Booking.Id, CallId = callDto.Id, TitleGender = !string.IsNullOrWhiteSpace(sendUser.Title) ? sendUser.Title : string.Empty, FirstName = sendUser.FirstName, LastName = sendUser.LastName, Name = sendUser.Name, Avatar = S3ReaderHelper.CombineS3Root(sendUser.Avatar), Url = urlDownload, Duration = duration.ToString(Constants.TimeSpanFormat), SpecialistId = callDto.Booking.Specialist.Id, CustomerId = callDto.Booking.Customer.Id, Cost = totalCost, RecordUrl = recordUrl, IsShowFeature = isSendForCustomer, PostNominal = isSendForCustomer && !string.IsNullOrWhiteSpace(sendUser.PostNominal) ? sendUser.PostNominal : string.Empty, IsFavourite = IsFavourite }); } }
/// <summary> /// Push start/stop for calltimer to client /// </summary> /// <param name="UserName"></param> /// <param name="callLog"></param> /// <param name="costPerMinute"></param> /// <param name="isBegin"></param> public static void CallDurationForUser(string UserName, UserDto userForLoadCallTimer, CallDto callDto, decimal costPerMinute, bool isBegin, int startingTime) { var context = GlobalHost.ConnectionManager.GetHubContext<CallTimerHub>(); foreach (var connectionId in CallTimerHub.connections.GetConnections(UserName)) { if (isBegin && callDto.StartTime.HasValue) { decimal minimumCost = Role.Specialist.Equals(userForLoadCallTimer.Role) ? callDto.Booking.CustomerMinCharge : callDto.Booking.SpecialistMinCharge; //load user information for calltimer context.Clients.Client(connectionId).loadCallTimer(userForLoadCallTimer.Id); //start call timer context.Clients.Client(connectionId).callDurationWeb( callDto.StartTime.Value.ToString(Code.Constants.DateTimeFormatTimeZone), DateTime.UtcNow.ToString(Code.Constants.DateTimeFormatTimeZone), costPerMinute, minimumCost, startingTime, callDto.Booking.IsApplyNoMinimumCharge); //start call timer for mobile context.Clients.Client(connectionId).callDuration(new { BookingId = callDto.Booking.Id, ReferenceNo = callDto.Booking.ReferenceNo, StartTime = callDto.StartTime.Value.ToString(Code.Constants.DateTimeFormatTimeZone), currentTimeServerSend = DateTime.UtcNow.ToString(Code.Constants.DateTimeFormatTimeZone), CostPerMinute = costPerMinute, UserTitleGender = !string.IsNullOrWhiteSpace(userForLoadCallTimer.Title) ? userForLoadCallTimer.Title : string.Empty, UserFirstName = userForLoadCallTimer.FirstName, UserLastName = userForLoadCallTimer.LastName, UserFullName = userForLoadCallTimer.Name, AvatarPath = S3ReaderHelper.CombineS3Root(userForLoadCallTimer.Avatar), MinimumCost = minimumCost, FreeTime = startingTime, UserPostNominal = Role.Specialist.Equals(userForLoadCallTimer.Role) && !string.IsNullOrWhiteSpace(userForLoadCallTimer.PostNominal) ? userForLoadCallTimer.PostNominal : string.Empty, IsApplyNoMinimumCharge = callDto.Booking.IsApplyNoMinimumCharge }); } else if (!isBegin && callDto.EndTime.HasValue) { //stop call timer context.Clients.Client(connectionId).stopCall( callDto.EndTime.Value.ToString(Code.Constants.DateTimeFormatTimeZone)); } } }