private boolean overlaps(TimeSlot timeSlot, List<TimeSlot> timeSlots) { for (TimeSlot slot : timeSlots) { if (timeSlot.getStartTime().equals(slot.getStartTime())) { return configMatches(timeSlot, slot); } else if (timeSlot.getStartTime().isBefore(slot.getStartTime())) { // make sure timeSlot ends before slot starts if (timeSlot.getEndTime().isAfter(slot.getStartTime())) { return configMatches(timeSlot, slot); } } else { // disabled, as we do want overlapping time slots from different calendar configurations // //only test if these time slots come from different configurations as we // DO want overlapping time slots (when min interval is smaller than min duration) // if (!timeSlot.getConfig().equals(slot.getConfig())){ // // //if timeSlot starts after slot, make sure it also starts before slot // ends // if (timeSlot.getStartTime().isBefore(slot.getEndTime())){ // return true; // } // } } } return false; }
public Long getBookingSlotsLeft(TimeSlot timeSlot, Offer offer, List<Booking> confirmedBookings) { checkForBookedCourts(timeSlot, confirmedBookings, true); Long bookingSlotsLeft = offer.getMaxConcurrentBookings(); for (Booking existingBooking : timeSlot.getBookings()) { if (existingBooking.getOffer().equals(offer)) { bookingSlotsLeft--; } } return bookingSlotsLeft; }
public void checkForBookedCourts( TimeSlot timeSlot, List<Booking> confirmedBookings, Boolean preventOverlapping) { LocalTime startTime = timeSlot.getStartTime(); LocalTime endTime = timeSlot.getEndTime(); for (Booking booking : confirmedBookings) { if (timeSlot.getDate().equals(booking.getBookingDate())) { LocalTime bookingStartTime = booking.getBookingTime(); LocalTime bookingEndTime = bookingStartTime.plusMinutes(booking.getDuration().intValue()); Boolean addBooking = false; if (preventOverlapping) { if (startTime.isBefore(bookingEndTime)) { if (endTime.isAfter(bookingStartTime)) { addBooking = true; } } } else { // for displaying allocations if (startTime.compareTo(bookingStartTime) >= 0) { if (endTime.compareTo(bookingEndTime) <= 0) { addBooking = true; } } } if (addBooking) { Offer offer = booking.getOffer(); for (Offer timeSlotOffer : timeSlot.getConfig().getOffers()) { if (offer.equals(timeSlotOffer)) { timeSlot.addBooking(booking); break; } } } } } }
private void addTimeSlot( List<TimeSlot> timeSlots, LocalDateTime time, CalendarConfig config, BigDecimal pricePerMinDuration) { TimeSlot timeSlot = new TimeSlot(); timeSlot.setDate(time.toLocalDate()); timeSlot.setStartTime(time.toLocalTime()); timeSlot.setEndTime(time.toLocalTime().plusMinutes(config.getMinDuration())); timeSlot.setConfig(config); timeSlot.setPricePerMinDuration(pricePerMinDuration); // only add timeSlot if timeSlots does not already contain an entry that overlaps if (!overlaps(timeSlot, timeSlots)) { timeSlots.add(timeSlot); } }
public OfferDurationPrice getOfferDurationPrice( LocalDate selectedDate, LocalTime selectedTime, Offer selectedOffer) throws CalendarConfigException { List<CalendarConfig> configs = calendarConfigDAO.findFor(selectedDate); List<Booking> confirmedBookings = bookingDAO.findBlockedBookingsForDate(selectedDate); // convert to required data structure Map<Offer, List<CalendarConfig>> offerConfigMap = new HashMap<>(); for (CalendarConfig config : configs) { for (Offer offer : config.getOffers()) { if (offer.equals(selectedOffer)) { List<CalendarConfig> list = offerConfigMap.get(offer); if (list == null) { list = new ArrayList<>(); } list.add(config); // sort by start time Collections.sort(list); offerConfigMap.put(offer, list); } } } OfferDurationPrice offerDurationPrices = null; Iterator<Map.Entry<Offer, List<CalendarConfig>>> iterator = offerConfigMap.entrySet().iterator(); // for every offer while (iterator.hasNext()) { Map.Entry<Offer, List<CalendarConfig>> entry = iterator.next(); Offer offer = entry.getKey(); List<CalendarConfig> configsForOffer = entry.getValue(); // make sure the first configuration starts before the requested booking time if (selectedTime.compareTo(configsForOffer.get(0).getStartTime()) < 0) { continue; } LocalDateTime endTime = null; Integer duration = configsForOffer.get(0).getMinDuration(); BigDecimal pricePerMinute; BigDecimal price = null; CalendarConfig previousConfig = null; Map<Integer, BigDecimal> durationPriceMap = new TreeMap<>(); Boolean isContiguous = true; for (CalendarConfig config : configsForOffer) { // make sure there is no gap between calendar configurations if (endTime == null) { // first run endTime = getLocalDateTime(selectedDate, selectedTime).plusMinutes(config.getMinDuration()); } else { // break if there are durations available and calendar configs are not contiguous if (!durationPriceMap.isEmpty()) { // we substract min interval before the comparison as it has been added during the last // iteration LocalDateTime configStartDateTime = getLocalDateTime(selectedDate, config.getStartTime()); if (!endTime.minusMinutes(config.getMinInterval()).equals(configStartDateTime)) { break; } } } Integer interval = config.getMinInterval(); pricePerMinute = getPricePerMinute(config); // as long as the endTime is before the end time configured in the calendar LocalDateTime configEndDateTime = getLocalDateTime(selectedDate, config.getEndTime()); while (endTime.compareTo(configEndDateTime) <= 0) { TimeSlot timeSlot = new TimeSlot(); timeSlot.setDate(selectedDate); timeSlot.setStartTime(selectedTime); timeSlot.setEndTime(endTime.toLocalTime()); timeSlot.setConfig(config); Long bookingSlotsLeft = getBookingSlotsLeft(timeSlot, offer, confirmedBookings); // we only allow contiguous bookings for any given offer if (bookingSlotsLeft < 1) { isContiguous = false; break; } if (price == null) { // see if previousConfig endTime - minInterval matches the selected time. if so, take // half of the previous config price as a basis if (previousConfig != null && previousConfig .getEndTime() .minusMinutes(previousConfig.getMinInterval()) .equals(selectedTime)) { BigDecimal previousConfigPricePerMinute = getPricePerMinute(previousConfig); price = previousConfigPricePerMinute.multiply( new BigDecimal(previousConfig.getMinInterval()), MathContext.DECIMAL128); price = price.add( pricePerMinute.multiply( new BigDecimal(duration - previousConfig.getMinInterval()), MathContext.DECIMAL128)); } else { price = pricePerMinute.multiply( new BigDecimal(duration.toString()), MathContext.DECIMAL128); } } else { // add price for additional interval price = price.add( pricePerMinute.multiply( new BigDecimal(interval.toString()), MathContext.DECIMAL128)); } price = price.setScale(2, RoundingMode.HALF_EVEN); durationPriceMap.put(duration, price); // increase the duration by the configured minimum interval and determine the new end time // for the next iteration duration += interval; endTime = endTime.plusMinutes(interval); } if (!durationPriceMap.isEmpty()) { OfferDurationPrice odp = new OfferDurationPrice(); odp.setOffer(offer); odp.setDurationPriceMap(durationPriceMap); odp.setConfig(config); offerDurationPrices = odp; } if (!isContiguous) { // we only allow coniguous bookings for one offer. process next offer // previousConfig = null; break; } previousConfig = config; } } return offerDurationPrices; }
public void addWeekView( LocalDate selectedDate, List<Facility> selectedFacilities, ModelAndView mav, Boolean preventOverlapping) throws JsonProcessingException { // calculate date configuration for datepicker LocalDate today = new LocalDate(DEFAULT_TIMEZONE); LocalDate firstDay = today.dayOfMonth().withMinimumValue(); LocalDate lastDay = today.plusDays(Constants.CALENDAR_MAX_DATE).dayOfMonth().withMaximumValue(); List<CalendarConfig> calendarConfigs = calendarConfigDAO.findBetween(firstDay, lastDay); Collections.sort(calendarConfigs); Map<String, DatePickerDayConfig> dayConfigs = getDayConfigMap(firstDay, lastDay, calendarConfigs); List<Booking> confirmedBookings = bookingDAO.findBlockedBookingsBetween(firstDay, lastDay); // calculate available time slots List<TimeSlot> timeSlots = new ArrayList<>(); List<LocalDate> weekDays = new ArrayList<>(); for (int i = 1; i <= CalendarWeekDay.values().length; i++) { LocalDate date = selectedDate.withDayOfWeek(i); weekDays.add(date); if (!date.isBefore(new LocalDate())) { try { // generate list of bookable time slots timeSlots.addAll( getTimeSlotsForDate( date, calendarConfigs, confirmedBookings, true, preventOverlapping)); } catch (CalendarConfigException e) { // safe to ignore } } } SortedSet<Offer> offers = new TreeSet<>(); List<TimeRange> rangeList = new ArrayList<>(); // Map<TimeRange, List<TimeSlot>> rangeList = new TreeMap<>(); for (TimeSlot slot : timeSlots) { Set<Offer> slotOffers = slot.getConfig().getOffers(); offers.addAll(slotOffers); TimeRange range = new TimeRange(); range.setStartTime(slot.getStartTime()); range.setEndTime(slot.getEndTime()); if (rangeList.contains(range)) { range = rangeList.get(rangeList.indexOf(range)); } else { rangeList.add(range); } List<TimeSlot> slotis = range.getTimeSlots(); slotis.add(slot); range.setTimeSlots(slotis); } Collections.sort(rangeList); List<Offer> selectedOffers = new ArrayList<>(); if (selectedFacilities.isEmpty()) { selectedOffers = offerDAO.findAll(); } else { for (Facility facility : selectedFacilities) { selectedOffers.addAll(facility.getOffers()); } } Collections.sort(selectedOffers); mav.addObject("dayConfigs", objectMapper.writeValueAsString(dayConfigs)); mav.addObject("maxDate", lastDay.toString()); mav.addObject("Day", selectedDate); mav.addObject("NextMonday", selectedDate.plusDays(8 - selectedDate.getDayOfWeek())); mav.addObject("PrevSunday", selectedDate.minusDays(selectedDate.getDayOfWeek())); mav.addObject("WeekDays", weekDays); mav.addObject("RangeMap", rangeList); mav.addObject("Offers", offers); mav.addObject("SelectedOffers", selectedOffers); mav.addObject("SelectedFacilities", selectedFacilities); mav.addObject("Facilities", facilityDAO.findAll()); }
private boolean configMatches(TimeSlot timeSlot, TimeSlot slot) { return timeSlot.getConfig().equals(slot.getConfig()); }