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));
        }
Пример #2
0
        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}");
        }
Пример #3
0
        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")
            });
Пример #4
0
        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);
Пример #5
0
        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);
        }