/// <summary> /// Create an order /// </summary> /// <param name="orderRequest">Order Request Dto</param> /// <returns>OrderResponseDto</returns> ///<remarks> /// Create orders steps : /// -We search availability to find a free rooms /// -We assign these rooms to each bookings /// -if PUSH channel and no availability we assign rooms of the same type anyway /// -if no PUSH channel and no availability we return null meaning no availability /// -We return the order reference /// </remarks> public OrderResponseDto CreateOrder(OrderRequestDto orderRequest) { OrderResponseDto responseDto = null; // Log request Log.LogInfo("CreateOrder request: {0}", null, orderRequest); Log.LogInfoAsXml(orderRequest); #region Precondition var business = businessManager.GetLimitedBusinessByShortname(orderRequest.BusinessShortName); if (business == null) { return null; } var channel = distributionManager.GetChannelByShortName(orderRequest.ChannelShortName); if (channel == null) { return null; } #endregion orderRequest.ChannelId = channel.Id; var roomIdsReserved = new List<int>(); var searchResults = new Dictionary<BookingRequestDto, AvailabilitySearchResult>(); var rooms = new Dictionary<BookingRequestDto, List<Room>>(); bool isAvailable = SearchAvailability(orderRequest, rooms, business, searchResults); if (isAvailable == true || orderRequest.AllowOverbooking == true) { bool isOverBooking = false; bool isOverLoaded = false; string reasonForOverbooking = null; var bookings = AllocateBookings(orderRequest, searchResults, roomIdsReserved, rooms, out isOverBooking, out isOverLoaded, out reasonForOverbooking); if ((isOverBooking == false || orderRequest.AllowOverbooking == true) && isOverLoaded == false) { if (isOverBooking) { log.LogWarn("There is an overbooking on the order {0} for the business with shortname {1}", orderRequest.ChannelReference, orderRequest.BusinessShortName); } //Abs does not manage the currency well so we default to the business working currency // Also set up order, then call create order first var order = new Order { CustomerCurrencyCode = string.IsNullOrWhiteSpace(orderRequest.CurrencyCode) ? business.WorkingCurrencyCode : orderRequest.CurrencyCode, ChannelId = channel.Id, Channel = channel, IntegrationType = channel.IntegrationType, OrderSourceCode = SourceType.Online.GetCode(), ChannelReference = orderRequest.ChannelReference, LeadGuest = Mapper.Map<Guest>(orderRequest.Guest), Bookings = bookings, IsIssue = !string.IsNullOrEmpty(reasonForOverbooking), ReasonForIssue = reasonForOverbooking }; order.LeadGuest.BusinessId = business.Id; var success = orderManager.CreateOrder(business.Id, order); if (success && order.Id.HasValue) { // Return the updated Order with Bookings (make sure to get the full object from database since the OrderReference is only generated in SQL code) responseDto = Mapper.Map<Order, OrderResponseDto>(orderManager.GetOrderWithBookingsByKey(order.Id.Value)); } } } Log.LogInfo("CreateOrder response: {0}", null, responseDto); Log.LogInfoAsXml(responseDto); return responseDto; }
private AvailabilitySearchResult FindRoomAndRatePlan(OrderRequestDto order, BookingRequestDto bookingRequest) { Model.Business.Business business = businessManager.GetLimitedBusinessByShortname(order.BusinessShortName); if (business == null) { return null; } var currency = business.WorkingCurrencyCode; AvailabilitySearchCriteria availabilitySearchCriteria = new AvailabilitySearchCriteria { BusinessIds = new List<long> { business.Id }, StartDate = CalendarDate.ConvertToCalendarDate( bookingRequest.StartDate), EndDate = CalendarDate.ConvertToCalendarDate( bookingRequest.EndDate), NumberOfAdults = bookingRequest.NumberOfAdults, NumberOfChildren = bookingRequest.NumberOfChildren, RatePlanId = bookingRequest.RatePlanId, RoomTypeId = bookingRequest.RoomTypeId, RequestedCurrency = currency, ChannelId = order.ChannelId }; // Get the results AvailabilitySearchResult result = availabilityManager.CheckAvailability(availabilitySearchCriteria); return result; }
/// <summary> /// Allocate the different rooms asked /// </summary> /// <param name="orderRequest">OrderRequestDto</param> /// <param name="searchResults">Availability search result Dictionnary indexed by request object</param> /// <param name="roomIdsReserved">List of reserved room ids</param> /// <param name="rooms">List of all the rooms for the room type asked</param> /// <param name="isOverBooking">Yes or not this allocation will result as an overbooking</param> /// <param name="isOverLoaded">Yes or not this allocation has been full filled completely</param> /// <param name="reasonForOverbooking">Indicates the reason for over booking</param> /// <remarks> /// Regarding isOverLoaded, a room can be overloaded in the case of Laterooms because /// they do not use our multi rooms search, as a consequence they construct a false /// inventory cache and can think that the hotel has more rooms that it has in reality. /// /// For push channel they already know perfectly the inventory of the hotel, so they would not /// try to do a multiple rooms booking with more rooms that the hotel has for a particulary /// room type /// </remarks> /// <returns>Return the list of booking allocated</returns> private static List<Booking> AllocateBookings( OrderRequestDto orderRequest, Dictionary<BookingRequestDto, AvailabilitySearchResult> searchResults, List<int> roomIdsReserved, Dictionary<BookingRequestDto, List<Room>> rooms, out bool isOverBooking, out bool isOverLoaded, out string reasonForOverbooking) { isOverBooking = false; isOverLoaded = false; StringBuilder unavailabilityReasons = new StringBuilder(); var bookings = new List<Booking>(); foreach (var searchResult in searchResults) { // Convert to Model var internalBooking = DataTransferObjectsConverter.ConvertBookingOnlineDtoToBooking(orderRequest, searchResult.Key); if (searchResult.Value.FirstRoomIdsAvailable != null) { foreach (int roomId in searchResult.Value.FirstRoomIdsAvailable) { if (!roomIdsReserved.Contains(roomId)) { AllocateBookingToRoom(internalBooking, searchResult.Value.FirstRoomTypeIdAvailable, searchResult.Value.FirstRoomRatePlanIdAvailable, roomId, bookings); roomIdsReserved.Add(roomId); // Reason for Unavailability if (searchResult.Value.UnavailabilityReasonCode.HasValue) { unavailabilityReasons.Append( searchResult.Value.UnavailabilityReasonCode.Value.ToString()); } else if (searchResult.Value.BusinessCandidates[0].UnavailabilityReasonCode.HasValue) { unavailabilityReasons.Append( searchResult.Value.BusinessCandidates[0].UnavailabilityReasonCode.Value.ToString()); } break; } } } //Force overbooking if all rooms reserved if (!bookings.Contains(internalBooking)) { foreach (var room in rooms[searchResult.Key]) { if (!roomIdsReserved.Contains(room.Id)) { AllocateBookingToRoom(internalBooking, room.RoomTypeId, internalBooking.RatePlanId.Value, room.Id, bookings); roomIdsReserved.Add(room.Id); isOverBooking = true; // Reason for Unavailability if (searchResult.Value.UnavailabilityReasonCode.HasValue) { unavailabilityReasons.Append( searchResult.Value.UnavailabilityReasonCode.Value.ToString()); } else if (searchResult.Value.BusinessCandidates[0].UnavailabilityReasonCode.HasValue) { unavailabilityReasons.Append( searchResult.Value.BusinessCandidates[0].UnavailabilityReasonCode.Value.ToString()); } break; } } } } //if the number of rooms allocated is less than //the number of rooms asked we declare the order over loaded isOverLoaded = (searchResults.Count() > bookings.Count); reasonForOverbooking = unavailabilityReasons.Length > 0 ? OVERBOOKING_PREFIX + unavailabilityReasons.ToString() : null; return bookings; }
private bool SearchAvailability( OrderRequestDto orderRequest, Dictionary<BookingRequestDto, List<Room>> rooms, Model.Business.Business business, Dictionary<BookingRequestDto, AvailabilitySearchResult> searchResults) { bool isAvailable = true; foreach (var booking in orderRequest.Bookings) { var roomList = roomManager.GetRoomsByBusinessRoomType(booking.RoomTypeId, business.Id); if (roomList.Count() == 0) { throw new ValidationException(ErrorFactory.CreateAndLogError(Errors.SRVEX30140, "BookingService.SearchAvailability", descriptionParameters: new object[] { booking.RoomTypeId, business.Id }, arguments: new object[] { booking.RoomTypeId, business.Id })); } rooms.Add(booking, roomList); var searchResult = FindRoomAndRatePlan(orderRequest, booking); searchResults.Add(booking, searchResult); if (searchResult == null || searchResult.IsOneRoomWithRatePlanMatched == false) { isAvailable = false; if (searchResult != null && searchResult.BusinessCandidates != null) { foreach (var businessCandidate in searchResult.BusinessCandidates) { if (businessCandidate.UnavailabilityReasonCode.HasValue) { Log.LogInfo("No Availability reason: {0}", null, businessCandidate.UnavailabilityReasonCode.Value.ToString()); Log.LogInfoAsXml(businessCandidate); } } } } } return isAvailable; }
/// <summary> /// Convert Booking Dto to Booking business domain /// </summary> /// <param name="order">OrderRequestDto</param> /// <param name="booking">BookingRequestDto</param> /// <returns>Booking</returns> public static Booking ConvertBookingOnlineDtoToBooking(OrderRequestDto order, BookingRequestDto booking) { var businessManager = new BusinessManager(); Model.Business.Business business = businessManager.GetLimitedBusinessByShortname(order.BusinessShortName); if (business != null) { var book = new Booking { BusinessId = business.Id, BookingReferenceNumber = order.BookingReferenceNumber, StartDate = booking.StartDate, EndDate = booking.EndDate, NumberOfAdults = booking.NumberOfAdults, NumberOfChildren = booking.NumberOfChildren, Cost = booking.Cost, RoomTypeId = booking.RoomTypeId, RoomId = booking.RoomId, RatePlanId = booking.RatePlanId, CheckinStatus = ConvertCodeAndNameToEnumEntity(booking.CheckinStatusCode), BookingScenarioType = (BookingScenarioTypeEnum)order.BookingScenarioType, Notes = booking.Notes, BookingStatus = ConvertCodeAndNameToEnumEntity(booking.BookingStatusCode), CreatedByUserId = order.CreatedByUserId, CreatedDateTime = order.CreatedDateTime, RateType = ConvertCodeAndNameToEnumEntity(RateType.BEST_AVAILABLE_RATE), ChannelCommissionOverride = booking.ChannelCommissionOverride, Extras = booking.Extras == null ? null : booking.Extras.ConvertAll(MapToOnlineBookingExtra) }; order.Guest.BusinessId = business.Id; return book; } return null; }