public Map<String, DatePickerDayConfig> getDayConfigMap( LocalDate firstDay, LocalDate lastDay, List<CalendarConfig> calendarConfigs) { Map<String, DatePickerDayConfig> dayConfigs = new HashMap<>(); for (LocalDate date = firstDay; date.isBefore(lastDay); date = date.plusDays(1)) { DatePickerDayConfig dayConfig = new DatePickerDayConfig(); dayConfig.setSelectable(Boolean.FALSE); for (CalendarConfig calendarConfig : calendarConfigs) { for (CalendarWeekDay weekDay : calendarConfig.getCalendarWeekDays()) { if (weekDay.ordinal() + 1 == date.getDayOfWeek()) { if (!isHoliday(date, calendarConfig)) { if (calendarConfig.getStartDate().compareTo(date) <= 0 && calendarConfig.getEndDate().compareTo(date) >= 0) { dayConfig.setSelectable(Boolean.TRUE); } } } } } dayConfigs.put(date.toString(), dayConfig); } return dayConfigs; }
public boolean isHoliday(LocalDate date, CalendarConfig calendarConfig) { String holidayKey = calendarConfig.getHolidayKey(); boolean isHoliday = false; if (!holidayKey.equals(NO_HOLIDAY_KEY)) { String[] holidayKeySplit = holidayKey.split("-"); String country = holidayKeySplit[0]; String region = holidayKeySplit[1]; HolidayManager countryHolidays = HolidayManager.getInstance(ManagerParameters.create(HolidayCalendar.valueOf(country))); isHoliday = countryHolidays.isHoliday(date, region); } return isHoliday; }
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 List<TimeSlot> getTimeSlotsForDate( LocalDate selectedDate, List<CalendarConfig> allCalendarConfigs, List<Booking> existingBookings, Boolean onlyFutureTimeSlots, Boolean preventOverlapping) throws CalendarConfigException { List<CalendarConfig> calendarConfigs = calendarConfigUtil.getCalendarConfigsMatchingDate(allCalendarConfigs, selectedDate); Iterator<CalendarConfig> iterator = calendarConfigs.iterator(); while (iterator.hasNext()) { CalendarConfig calendarConfig = iterator.next(); if (isHoliday(selectedDate, calendarConfig)) { iterator.remove(); } } List<TimeSlot> timeSlots = new ArrayList<>(); if (calendarConfigs.size() > 0) { LocalDate today = new LocalDate(DEFAULT_TIMEZONE); // sort all calendar configurations for selected date by start time Collections.sort(calendarConfigs); CalendarConfig previousConfig = null; LocalDateTime time = null; LocalDateTime now = new LocalDateTime(DEFAULT_TIMEZONE); // generate list of bookable time slots for (CalendarConfig config : calendarConfigs) { LocalDateTime startDateTime = getLocalDateTime(selectedDate, config.getStartTime()); if (time == null) { // on first iteration time = startDateTime; } else { if (time.plusMinutes(previousConfig.getMinInterval()).equals(startDateTime)) { // contiguous bookings possible // time = time; } else { // reset basePriceLastConfig as this is a non contiguous offer previousConfig = null; time = startDateTime; } } LocalDateTime endDateTime = getLocalDateTime(selectedDate, config.getEndTime()); while (time.plusMinutes(config.getMinDuration()).compareTo(endDateTime) <= 0) { BigDecimal pricePerMinDuration; if (previousConfig == null) { pricePerMinDuration = config.getBasePrice(); } else { BigDecimal previousConfigBasePricePerMinute = getPricePerMinute(previousConfig); pricePerMinDuration = previousConfigBasePricePerMinute.multiply( new BigDecimal(previousConfig.getMinInterval()), MathContext.DECIMAL128); BigDecimal basePricePerMinute = getPricePerMinute(config); pricePerMinDuration = pricePerMinDuration.add( basePricePerMinute.multiply( new BigDecimal(config.getMinDuration() - previousConfig.getMinInterval()), MathContext.DECIMAL128)); previousConfig = null; } pricePerMinDuration = pricePerMinDuration.setScale(2, RoundingMode.HALF_EVEN); if (onlyFutureTimeSlots) { if (selectedDate.isAfter(today) || time.isAfter(now)) { addTimeSlot(timeSlots, time, config, pricePerMinDuration); } } else { addTimeSlot(timeSlots, time, config, pricePerMinDuration); } time = time.plusMinutes(config.getMinInterval()); } previousConfig = config; } // sort time slots by time Collections.sort(timeSlots); // decrease court count for every blocking booking for (TimeSlot timeSlot : timeSlots) { checkForBookedCourts(timeSlot, existingBookings, preventOverlapping); } } return timeSlots; }
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 BigDecimal getPricePerMinute(CalendarConfig config) { return config .getBasePrice() .divide(new BigDecimal(config.getMinDuration().toString()), MathContext.DECIMAL128); }