public async Task <Result> Cancel(Booking booking, ApiCaller apiCaller, BookingChangeEvents eventType) { if (booking.Status == BookingStatuses.Cancelled) { _logger.LogBookingAlreadyCancelled(booking.ReferenceCode); return(Result.Success()); } return(await CheckBookingCanBeCancelled() .Bind(SendCancellationRequest) .Bind(ProcessCancellation) .Finally(WriteLog)); Result CheckBookingCanBeCancelled() { if (booking.Status != BookingStatuses.Confirmed) { return(Result.Failure("Only confirmed bookings can be cancelled")); } if (booking.CheckOutDate <= _dateTimeProvider.UtcToday()) { return(Result.Failure("Cannot cancel booking after check out date")); } return(Result.Success()); } async Task <Result <Booking> > SendCancellationRequest() { var(_, isCancelFailure, _, cancelError) = await _supplierConnectorManager.Get((Suppliers)booking.Supplier).CancelBooking(booking.ReferenceCode); return(isCancelFailure ? Result.Failure <Booking>(cancelError.Detail) : Result.Success(booking)); } async Task <Result> ProcessCancellation(Booking b) { var changeReason = new BookingChangeReason { Event = eventType, Source = BookingChangeSources.System }; await _bookingRecordsUpdater.ChangeStatus(b, BookingStatuses.PendingCancellation, _dateTimeProvider.UtcNow(), apiCaller, changeReason); return(b.UpdateMode == BookingUpdateModes.Synchronous ? await RefreshStatus(b, apiCaller, eventType) : Result.Success()); } Result WriteLog(Result result) => LoggerUtils.WriteLogByResult(result, () => _logger.LogBookingCancelSuccess(booking.ReferenceCode), () => _logger.LogBookingCancelFailure(booking.ReferenceCode, result.Error)); }
public async Task ProcessResponse(Booking bookingResponse, ApiCaller apiCaller, BookingChangeEvents eventType) { var(_, isFailure, booking, error) = await _bookingRecordManager.Get(bookingResponse.ReferenceCode); if (isFailure) { _logger.LogBookingResponseProcessFailure(error); return; } _logger.LogBookingResponseProcessStarted(bookingResponse.ReferenceCode, booking.Status); await _bookingAuditLogService.Add(bookingResponse, booking); if (bookingResponse.Status == BookingStatusCodes.NotFound) { await ProcessBookingNotFound(booking, bookingResponse, eventType); return; } await _recordsUpdater.UpdateWithSupplierData(booking, bookingResponse.SupplierReferenceCode, bookingResponse.BookingUpdateMode, bookingResponse.Rooms); if (bookingResponse.Status.ToInternalStatus() == booking.Status) { _logger.LogBookingResponseProcessSuccess(bookingResponse.ReferenceCode, "No status changes applied"); return; } var changeReason = new BookingChangeReason { Event = eventType, Source = BookingChangeSources.Supplier }; var(_, isUpdateFailure, updateError) = await _recordsUpdater.ChangeStatus(booking, bookingResponse.Status.ToInternalStatus(), _dateTimeProvider.UtcNow(), apiCaller, changeReason); if (isUpdateFailure) { _logger.LogBookingResponseProcessFailure(updateError); return; } _logger.LogBookingResponseProcessSuccess(bookingResponse.ReferenceCode, $"New status: {bookingResponse.Status}"); }
public async Task <Result> ChangeStatus(Booking booking, BookingStatuses status, DateTime date, ApiCaller apiCaller, BookingChangeReason reason) { if (booking.Status == status) { return(Result.Success()); } await SetStatus(booking, status); var message = new BookingStatusChangeInfo { BookingId = booking.Id, ReferenceCode = booking.ReferenceCode, Status = EnumFormatters.FromDescription(status), ChangeTime = _dateTimeProvider.UtcNow(), AccommodationName = booking.AccommodationName, AccommodationPhoto = booking.AccommodationInfo?.Photo, CheckInDate = booking.CheckInDate.DateTime, CheckOutDate = booking.CheckOutDate.DateTime }; await _notificationsService.Send(apiCaller, JsonDocument.Parse(JsonSerializer.SerializeToUtf8Bytes(message, new JsonSerializerOptions(JsonSerializerDefaults.Web))), Notifications.Enums.NotificationTypes.BookingStatusChanged); // Temporary hot-fix for notifying admins about bookings statuses changed to "Pending" or "Waiting for response" // TODO: remove when we have appropriate admin panel booking monitoring if (status == BookingStatuses.Pending || status == BookingStatuses.WaitingForResponse) { var(isSuccess, _, bookingInfo, _) = await GetBookingInfo(booking.ReferenceCode); if (isSuccess) { await _bookingNotificationService.NotifyAdminsStatusChanged(bookingInfo); } } await _bookingChangeLogService.Write(booking, status, date, apiCaller, reason); return(status switch { BookingStatuses.Confirmed => await ProcessConfirmation(booking, date), BookingStatuses.Cancelled => await ProcessCancellation(booking, date, apiCaller), BookingStatuses.Rejected => await ProcessDiscarding(booking, apiCaller), BookingStatuses.Invalid => await ProcessDiscarding(booking, apiCaller), BookingStatuses.Discarded => await ProcessDiscarding(booking, apiCaller), BookingStatuses.ManualCorrectionNeeded => await ProcessManualCorrectionNeeding(booking, apiCaller), BookingStatuses.PendingCancellation => Result.Success(), BookingStatuses.WaitingForResponse => Result.Success(), BookingStatuses.Pending => Result.Success(), _ => throw new ArgumentOutOfRangeException(nameof(status), status, "Invalid status value") });
public async Task Write(Booking booking, BookingStatuses status, DateTime date, ApiCaller apiCaller, BookingChangeReason reason) { var bookingStatusHistoryEntry = new BookingStatusHistoryEntry { BookingId = booking.Id, UserId = apiCaller.Id, ApiCallerType = apiCaller.Type, CreatedAt = date, Status = status, Initiator = GetInitiatorType(apiCaller), Source = reason.Source, Event = reason.Event, Reason = reason.Reason }; if (apiCaller.Type == ApiCallerTypes.Agent) { bookingStatusHistoryEntry.AgencyId = booking.AgencyId; } var entry = _context.BookingStatusHistory.Add(bookingStatusHistoryEntry); await _context.SaveChangesAsync(); _context.Detach(entry.Entity);
public async Task <Result <Booking> > Register(AccommodationBookingRequest bookingRequest, BookingAvailabilityInfo availabilityInfo, PaymentTypes paymentMethod, AgentContext agentContext, string languageCode) { var(_, isFailure, booking, error) = await CheckRooms() .Map(GetTags) .Map(Create) .Tap(SaveRequestInfo) .Tap(LogBookingStatus) .Tap(SaveMarkups) .Tap(CreateSupplierOrder); if (isFailure) { return(Result.Failure <Booking>(error)); } _logger.LogBookingRegistrationSuccess(booking.ReferenceCode); return(booking); Result CheckRooms() { if (bookingRequest.RoomDetails.Count != availabilityInfo.AvailabilityRequest.RoomDetails.Count) { return(Result.Failure("Rooms does not correspond to search rooms")); } for (var i = 0; i < bookingRequest.RoomDetails.Count; i++) { var adultsCount = bookingRequest.RoomDetails[i].Passengers.Count(p => p.Age == null); var childrenCount = bookingRequest.RoomDetails[i].Passengers.Count(p => p.Age != null); if (availabilityInfo.AvailabilityRequest.RoomDetails[i].AdultsNumber != adultsCount || availabilityInfo.AvailabilityRequest.RoomDetails[i].ChildrenAges.Count != childrenCount) { return(Result.Failure("Rooms does not correspond to search rooms")); } } return(Result.Success()); } async Task <(string itn, string referenceCode)> GetTags() { string itn; if (string.IsNullOrWhiteSpace(bookingRequest.ItineraryNumber)) { itn = await _tagProcessor.GenerateItn(); } else { // User can send reference code instead of itn if (!_tagProcessor.TryGetItnFromReferenceCode(bookingRequest.ItineraryNumber, out itn)) { itn = bookingRequest.ItineraryNumber; } if (!await AreExistBookingsForItn(itn, agentContext.AgentId)) { itn = await _tagProcessor.GenerateItn(); } } var referenceCode = await _tagProcessor.GenerateReferenceCode( ServiceTypes.HTL, availabilityInfo.CountryCode, itn); return(itn, referenceCode); } async Task <Booking> Create((string itn, string referenceCode) tags) { var createdBooking = await CreateBooking( created : _dateTimeProvider.UtcNow(), agentContext : agentContext, itineraryNumber : tags.itn, referenceCode : tags.referenceCode, clientReferenceCode : bookingRequest.ClientReferenceCode, availabilityInfo : availabilityInfo, paymentMethod : paymentMethod, bookingRequest : bookingRequest, languageCode : languageCode); _context.Bookings.Add(createdBooking); await _context.SaveChangesAsync(); _context.Entry(createdBooking).State = EntityState.Detached; return(createdBooking); } Task SaveRequestInfo(Booking booking) => _requestStorage.Set(booking.ReferenceCode, bookingRequest, availabilityInfo); Task LogBookingStatus(Booking booking) { var changeReason = new BookingChangeReason { Event = BookingChangeEvents.Create, Source = BookingChangeSources.System }; return(_changeLogService.Write(booking, BookingStatuses.Created, booking.Created.DateTime, agentContext.ToApiCaller(), changeReason)); } Task SaveMarkups(Booking booking) => _appliedBookingMarkupRecordsManager.Create(booking.ReferenceCode, availabilityInfo.AppliedMarkups); Task CreateSupplierOrder(Booking booking) => _supplierOrderService.Add(referenceCode: booking.ReferenceCode, serviceType: ServiceTypes.HTL, convertedPrice: availabilityInfo.ConvertedSupplierPrice, supplierPrice: availabilityInfo.OriginalSupplierPrice, deadline: availabilityInfo.SupplierDeadline, supplier: (int)booking.Supplier, paymentType: availabilityInfo.CardRequirement is not null ? SupplierPaymentType.CreditCard : SupplierPaymentType.DirectPayment, paymentDate: availabilityInfo.RoomContractSet.IsAdvancePurchaseRate ? booking.Created.DateTime : booking.CheckOutDate.DateTime); }