Esempio n. 1
0
        public async Task Start(Guid searchId, AvailabilityRequest request, Suppliers supplier, AgentContext agent, string languageCode,
                                AccommodationBookingSettings searchSettings)
        {
            var supplierConnector = _supplierConnectorManager.Get(supplier);

            try
            {
                _logger.LogProviderAvailabilitySearchStarted($"Availability search with id '{searchId}' on supplier '{supplier}' started");

                await GetAvailability(request, languageCode)
                .Bind(ConvertCurrencies)
                .Map(ApplyMarkups)
                .Map(Convert)
                .Tap(SaveResult)
                .Finally(SaveState);
            }
            catch (Exception ex)
            {
                // TODO: Add sentry error notification
                _logger.LogProviderAvailabilitySearchException(ex);
                var result = ProblemDetailsBuilder.Fail <List <AccommodationAvailabilityResult> >("Server error", HttpStatusCode.InternalServerError);
                await SaveState(result);
            }


            async Task <Result <EdoContracts.Accommodations.Availability, ProblemDetails> > GetAvailability(AvailabilityRequest request,
                                                                                                            string languageCode)
            {
                var saveToStorageTask   = _storage.SaveState(searchId, SupplierAvailabilitySearchState.Pending(searchId), supplier);
                var getAvailabilityTask = supplierConnector.GetAvailability(request, languageCode);
                await Task.WhenAll(saveToStorageTask, getAvailabilityTask);

                return(getAvailabilityTask.Result);
            }

            async Task <Result <EdoContracts.Accommodations.Availability, ProblemDetails> > ConvertCurrencies(EdoContracts.Accommodations.Availability availabilityDetails)
            {
                var convertedResults = new List <SlimAccommodationAvailability>(availabilityDetails.Results.Count);

                foreach (var slimAccommodationAvailability in availabilityDetails.Results)
                {
                    // Currency can differ in different results
                    var(_, isFailure, convertedAccommodationAvailability, error) = await _priceProcessor.ConvertCurrencies(agent, slimAccommodationAvailability, AvailabilityResultsExtensions.ProcessPrices,
                                                                                                                           AvailabilityResultsExtensions.GetCurrency);

                    if (isFailure)
                    {
                        return(Result.Failure <EdoContracts.Accommodations.Availability, ProblemDetails>(error));
                    }

                    convertedResults.Add(convertedAccommodationAvailability);
                }

                return(new EdoContracts.Accommodations.Availability(availabilityDetails.AvailabilityId, availabilityDetails.NumberOfNights,
                                                                    availabilityDetails.CheckInDate, availabilityDetails.CheckOutDate, convertedResults, availabilityDetails.NumberOfProcessedAccommodations));
            }

            async Task <EdoContracts.Accommodations.Availability> ApplyMarkups(EdoContracts.Accommodations.Availability response)
            {
                var markup = await _priceProcessor.ApplyMarkups(agent, response, AvailabilityResultsExtensions.ProcessPrices);

                return(markup.Data);
            }

            async Task <List <AccommodationAvailabilityResult> > Convert(EdoContracts.Accommodations.Availability details)
            {
                var supplierAccommodationIds = details.Results
                                               .Select(r => new SupplierAccommodationId(supplier, r.Accommodation.Id))
                                               .Distinct()
                                               .ToList();

                var duplicates = await _duplicatesService.GetDuplicateReports(supplierAccommodationIds);

                var timestamp = _dateTimeProvider.UtcNow().Ticks;

                return(details
                       .Results
                       .Select(accommodationAvailability =>
                {
                    var minPrice = accommodationAvailability.RoomContractSets.Min(r => r.Rate.FinalPrice.Amount);
                    var maxPrice = accommodationAvailability.RoomContractSets.Max(r => r.Rate.FinalPrice.Amount);
                    var accommodationId = new SupplierAccommodationId(supplier, accommodationAvailability.Accommodation.Id);
                    var resultId = Guid.NewGuid();
                    var duplicateReportId = duplicates.TryGetValue(accommodationId, out var reportId)
                            ? reportId
                            : string.Empty;
                    var roomContractSets = accommodationAvailability.RoomContractSets
                                           .ToRoomContractSetList()
                                           .ApplySearchFilters(searchSettings, _dateTimeProvider, request.CheckInDate);

                    return new AccommodationAvailabilityResult(resultId,
                                                               timestamp,
                                                               details.AvailabilityId,
                                                               accommodationAvailability.Accommodation,
                                                               roomContractSets,
                                                               duplicateReportId,
                                                               minPrice,
                                                               maxPrice,
                                                               request.CheckInDate,
                                                               request.CheckOutDate);
                })
                       .Where(a => a.RoomContractSets.Any())
                       .ToList());
            }

            Task SaveResult(List <AccommodationAvailabilityResult> results) => _storage.SaveResults(searchId, supplier, results);


            Task SaveState(Result <List <AccommodationAvailabilityResult>, ProblemDetails> result)
            {
                var state = result.IsSuccess
                    ? SupplierAvailabilitySearchState.Completed(searchId, result.Value.Select(r => r.DuplicateReportId).ToList(), result.Value.Count)
                    : SupplierAvailabilitySearchState.Failed(searchId, result.Error.Detail);

                if (state.TaskState == AvailabilitySearchTaskState.Completed)
                {
                    _logger.LogProviderAvailabilitySearchSuccess(
                        $"Availability search with id '{searchId}' on supplier '{supplier}' finished successfully with '{state.ResultCount}' results");
                }
                else
                {
                    _logger.LogProviderAvailabilitySearchFailure(
                        $"Availability search with id '{searchId}' on supplier '{supplier}' finished with state '{state.TaskState}', error '{state.Error}'");
                }

                return(_storage.SaveState(searchId, state, supplier));
            }
        }
Esempio n. 2
0
        public async Task <Result <AccommodationBookingInfo, ProblemDetails> > Finalize(string referenceCode, AgentContext agentContext, string languageCode)
        {
            var(_, isGetBookingFailure, booking, getBookingError) = await GetAgentsBooking()
                                                                    .Ensure(b => agentContext.AgencyId == b.AgencyId, ProblemDetailsBuilder.Build("The booking does not belong to your current agency"))
                                                                    .Bind(CheckBookingIsPaid)
                                                                    .OnFailure(WriteLogFailure);

            if (isGetBookingFailure)
            {
                return(Result.Failure <AccommodationBookingInfo, ProblemDetails>(getBookingError));
            }

            return(await BookOnProvider(booking, referenceCode, languageCode)
                   .Tap(ProcessResponse)
                   .Bind(CaptureMoneyIfDeadlinePassed)
                   .OnFailure(VoidMoneyAndCancelBooking)
                   .Bind(GenerateInvoice)
                   .Tap(NotifyOnCreditCardPayment)
                   .Bind(GetAccommodationBookingInfo)
                   .Finally(WriteLog));


            Task <Result <Data.Booking.Booking, ProblemDetails> > GetAgentsBooking()
            => _bookingRecordsManager.GetAgentsBooking(referenceCode, agentContext).ToResultWithProblemDetails();


            Result <Data.Booking.Booking, ProblemDetails> CheckBookingIsPaid(Data.Booking.Booking bookingFromPipe)
            {
                if (bookingFromPipe.PaymentStatus == BookingPaymentStatuses.NotPaid)
                {
                    _logger.LogBookingFinalizationPaymentFailure($"The booking with reference code: '{referenceCode}' hasn't been paid");
                    return(ProblemDetailsBuilder.Fail <Data.Booking.Booking>("The booking hasn't been paid"));
                }

                return(bookingFromPipe);
            }

            Task ProcessResponse(Booking bookingResponse) => _bookingResponseProcessor.ProcessResponse(bookingResponse, booking);


            async Task <Result <EdoContracts.Accommodations.Booking, ProblemDetails> > CaptureMoneyIfDeadlinePassed(EdoContracts.Accommodations.Booking bookingInPipeline)
            {
                var daysBeforeDeadline = Infrastructure.Constants.Common.DaysBeforeDeadlineWhenPayForBooking;
                var now = _dateTimeProvider.UtcNow();

                var deadlinePassed = booking.CheckInDate <= now.AddDays(daysBeforeDeadline) ||
                                     (booking.DeadlineDate.HasValue && booking.DeadlineDate.Value.Date <= now.AddDays(daysBeforeDeadline));

                if (!deadlinePassed)
                {
                    return(bookingInPipeline);
                }

                var(_, isPaymentFailure, _, paymentError) = await _bookingPaymentService.Capture(booking, agentContext.ToUserInfo());

                if (isPaymentFailure)
                {
                    return(ProblemDetailsBuilder.Fail <EdoContracts.Accommodations.Booking>(paymentError));
                }

                return(bookingInPipeline);
            }

            Task VoidMoneyAndCancelBooking(ProblemDetails problemDetails) => this.VoidMoneyAndCancelBooking(booking, agentContext);


            async Task <Result <Booking, ProblemDetails> > NotifyOnCreditCardPayment(Booking details)
            {
                await _bookingMailingService.SendCreditCardPaymentNotifications(details.ReferenceCode);

                return(details);
            }

            Task <Result <Booking, ProblemDetails> > GenerateInvoice(Booking details) => this.GenerateInvoice(details, referenceCode, agentContext);


            Task <Result <AccommodationBookingInfo, ProblemDetails> > GetAccommodationBookingInfo(Booking details)
            => _bookingRecordsManager.GetAccommodationBookingInfo(details.ReferenceCode, languageCode)
            .ToResultWithProblemDetails();


            void WriteLogFailure(ProblemDetails problemDetails)
            => _logger.LogBookingByAccountFailure($"Failed to finalize a booking with reference code: '{referenceCode}'. Error: {problemDetails.Detail}");


            Result <T, ProblemDetails> WriteLog <T>(Result <T, ProblemDetails> result)
            => LoggerUtils.WriteLogByResult(result,
                                            () => _logger.LogBookingFinalizationSuccess($"Successfully finalized a booking with reference code: '{referenceCode}'"),
                                            () => _logger.LogBookingFinalizationFailure(
                                                $"Failed to finalize a booking with reference code: '{referenceCode}'. Error: {result.Error.Detail}"));
        }
        public async Task <Result <RoomContractSetAvailability?, ProblemDetails> > GetExactAvailability(
            Guid searchId, string htId, Guid roomContractSetId, AgentContext agent, string languageCode)
        {
            Baggage.AddSearchId(searchId);
            var settings = await _accommodationBookingSettingsService.Get(agent);

            var(_, isFailure, result, error) = await GetSelectedRoomSet(searchId, htId, roomContractSetId);

            if (isFailure)
            {
                return(ProblemDetailsBuilder.Fail <RoomContractSetAvailability?>(error));
            }

            var availabilityRequest = await _availabilityRequestStorage.Get(searchId);

            if (availabilityRequest.IsFailure)
            {
                return(ProblemDetailsBuilder.Fail <RoomContractSetAvailability?>(availabilityRequest.Error));
            }

            var connectorEvaluationResult = await EvaluateOnConnector(result);

            if (connectorEvaluationResult.IsFailure)
            {
                _logger.LogBookingEvaluationFailure(connectorEvaluationResult.Error.Status, connectorEvaluationResult.Error.Detail);
                return((RoomContractSetAvailability?)null);
            }

            if (connectorEvaluationResult.Value is null)
            {
                return((RoomContractSetAvailability?)null);
            }

            var originalSupplierPrice = connectorEvaluationResult.Value.Value.RoomContractSet.Rate.FinalPrice;

            var(_, isContractFailure, contractKind, contractError) = await _adminAgencyManagementService.GetContractKind(agent.AgencyId);

            if (isContractFailure)
            {
                return(ProblemDetailsBuilder.Fail <RoomContractSetAvailability?>(contractError));
            }

            var accommodationResult = await GetAccommodation(result.htId, languageCode);

            if (accommodationResult.IsFailure)
            {
                _logger.LogGetAccommodationByHtIdFailed(result.htId, accommodationResult.Error.Detail);
                return((RoomContractSetAvailability?)null);
            }

            var slimAccommodation = GetSlimAccommodation(accommodationResult.Value);

            return(await Convert(connectorEvaluationResult.Value.Value)
                   .Bind(ConvertCurrencies)
                   .Map(ProcessPolicies)
                   .Map(ApplyMarkups)
                   .Map(AlignPrices)
                   .Tap(SaveToCache)
                   .Map(e => e.Data)
                   .Check(CheckAgainstSettings)
                   .Check(CheckCancellationPolicies)
                   .Map(ApplySearchSettings));


            async Task <Result <(Suppliers Supplier, RoomContractSet RoomContractSet, string AvailabilityId, string htId, string CountryHtId, string LocalityHtId)> >
            GetSelectedRoomSet(Guid searchId, string htId, Guid roomContractSetId)
            {
                var result = (await _roomSelectionStorage.GetResult(searchId, htId, settings.EnabledConnectors))
                             .SelectMany(r =>
                {
                    return(r.Result.RoomContractSets
                           .Select(rs => (Source: r.Supplier, RoomContractSet: rs, r.Result.AvailabilityId, r.Result.HtId,
                                          CountryHtId: r.Result.CountryHtId, LocalityHtId: r.Result.LocalityHtId)));
                })
                             .SingleOrDefault(r => r.RoomContractSet.Id == roomContractSetId);

                if (result.Equals(default))