ReservationStatService.java
package sk.iway.iwcm.components.reservation.rest;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Service;
import lombok.Getter;
import lombok.Setter;
import sk.iway.iwcm.DateTools;
import sk.iway.iwcm.Tools;
import sk.iway.iwcm.calendar.Month;
import sk.iway.iwcm.common.CloudToolsForCore;
import sk.iway.iwcm.components.reservation.jpa.ReservationEntity;
import sk.iway.iwcm.components.reservation.jpa.ReservationRepository;
import sk.iway.iwcm.components.reservation.jpa.ReservationStatDTO;
@Service
public class ReservationStatService {
private ReservationStatService() {}
private static final BigDecimal HOUR_IN_MILLIS = new BigDecimal(1000 * 60 * 60);
public enum ReservationType {
HOURS,
DAYS;
public static ReservationType getReservationType(String reservationType) {
if("typeDays".equals(reservationType))
return ReservationType.DAYS;
return ReservationType.HOURS;
}
}
@Getter
@Setter
public static class DoublePieChartData {
private Integer valueA;
private BigDecimal valueB;
@SuppressWarnings("unused")
private String category;
public DoublePieChartData(Integer valueA, BigDecimal valueB, String category) {
this.valueA = valueA;
this.valueB = valueB;
this.category = category;
}
public void incrementA() { this.valueA++; }
public void addToB(BigDecimal value) { this.valueB = this.valueB.add(value); }
}
@Getter
@Setter
public static class LineChartData {
BigDecimal value;
Date dayDate;
public LineChartData(BigDecimal value, Date dayDate) {
this.value = value;
this.dayDate = dayDate;
}
public void addToValue(BigDecimal addValue) { this.value = this.value.add(addValue); }
}
/**
* Return table data for the given month and reservation type -> for reservation stat page
* @param serachDate - text in format yyyy-MM
* @param reservationTypeSting - supported values -> typeDays, typeHours
* @param reservationRepository
* @return
*/
public static List<ReservationStatDTO> getTableData(String serachDate, String reservationTypeSting, ReservationRepository reservationRepository) {
ReservationType reservationType = ReservationType.getReservationType(reservationTypeSting);
Date[] dateRange = getDateRange(serachDate);
//Get all reservations for the given month, domain and they MUST be accepted
List<ReservationEntity> filteredReservations = reservationRepository.findByDateAndType(dateRange[1], dateRange[0], ReservationType.DAYS.equals(reservationType), CloudToolsForCore.getDomainId());
//Group reservations by reservation object id AND creatorId
// !! if creatorId is null or -1 (no logged user) -> group by reservation object id AND email
Map<String, ReservationStatDTO> reservationsStatMap = new HashMap<>();
for(ReservationEntity re : filteredReservations) {
String key = "";
if(re.getUserId() != null && re.getUserId() > 0) {
key = re.getReservationObjectId() + "_" + re.getUserId();
} else {
key = re.getReservationObjectId() + "_" + re.getEmail();
}
ReservationStatDTO rs = reservationsStatMap.get(key);
if(rs == null) {
//Combination not in map yet
rs = new ReservationStatDTO();
rs.setUserName(re.getName() + " " + re.getSurname());
rs.setReservationObjectName( re.getReservationObjectForReservation().getName() );
rs.setTotalPrice( BigDecimal.ZERO );
rs.setTotalReservedHours( BigDecimal.ZERO );
rs.setNumberOfReservedDays(0);
rs.setNumberOfReservations(0);
}
//Combination already in map
rs.setTotalPrice( rs.getTotalPrice().add( re.getPrice() ) );
//Number of reservations -> count +1 every time
rs.setNumberOfReservations( rs.getNumberOfReservations() + 1 );
//Number of reserved days -> one reservation can be for multiple days
int daysInterval = getDayDiff(re.getDateFrom(), re.getDateTo());
rs.setNumberOfReservedDays( rs.getNumberOfReservedDays() + daysInterval );
if(ReservationType.HOURS.equals(reservationType)) {
//Number of total reserved hours -> it's time range * reserved days
rs.setTotalReservedHours( rs.getTotalReservedHours().add( computeHoursInterval(re, daysInterval) ) );
}
reservationsStatMap.put(key, rs);
}
for(ReservationStatDTO rs : reservationsStatMap.values()) {
if(ReservationType.HOURS.equals(reservationType))
rs.setAverageTimePerDay( divide(rs.getTotalReservedHours(), rs.getNumberOfReservedDays(), 2) );
rs.setAverageIntervalInDays( divide(rs.getNumberOfReservedDays(), rs.getNumberOfReservations(), 2) );
}
return new ArrayList<>(reservationsStatMap.values());
}
/**
* Return pie chart data for the given month and reservation type -> for reservation stat page
* @param serachDate - text in format yyyy-MM
* @param reservationTypeSting - supported values -> typeDays, typeHours
* @param wantedValue - supported values -> users (count reservations by user), objects (count reservations by reservation objects)
* @param reservationRepository
* @return
*/
public static List<DoublePieChartData> getPieChartData(String serachDate, String reservationTypeSting, String wantedValue, ReservationRepository reservationRepository) {
ReservationType reservationType = ReservationType.getReservationType(reservationTypeSting);
Date[] dateRange = getDateRange(serachDate);
//Get all reservations for the given month, domain and they MUST be accepted
List<ReservationEntity> filteredReservations = reservationRepository.findByDateAndType(dateRange[1], dateRange[0], ReservationType.DAYS.equals(reservationType), CloudToolsForCore.getDomainId());
//Supported values for wantedValue -> users, objects
if("users".equals(wantedValue) == false && "objects".equals(wantedValue) == false) return new ArrayList<>();
Map<String, DoublePieChartData> map = new HashMap<>();
for(ReservationEntity re : filteredReservations) {
String key = "";
DoublePieChartData cd = new DoublePieChartData(0, BigDecimal.ZERO, "");
if("users".equals(wantedValue)) {
key = re.getUserId() > 0 ? re.getUserId().toString() : re.getEmail();
cd = map.get(key);
if(cd == null) {
String label = re.getUserId() > 0 ? re.getName() + " " + re.getSurname() + " (id:" + re.getUserId() + ")" : re.getName() + " " + re.getSurname() + " (email:" + re.getEmail() + ")";
cd = new DoublePieChartData(1, BigDecimal.ZERO, label);
} else cd.incrementA();
} else if("objects".equals(wantedValue)) {
key = re.getReservationObjectId().toString();
cd = map.get(key);
if(cd == null) {
cd = new DoublePieChartData(1, BigDecimal.ZERO, re.getReservationObjectForReservation().getName());
} else cd.incrementA();
}
//Set value B -> HOURS
int daysInterval = getDayDiff(re.getDateFrom(), re.getDateTo());
if(ReservationType.HOURS.equals(reservationType))
cd.addToB( computeHoursInterval(re, daysInterval) );
else cd.addToB( new BigDecimal(daysInterval) );
map.put(key, cd);
}
return new ArrayList<>(map.values());
}
/**
* Return line chart data for the given month and reservation type -> for reservation stat page
* @param serachDate - text in format yyyy-MM
* @param reservationTypeSting - supported values -> typeDays, typeHours
* @param reservationRepository
* @return
*/
public static Map<String, List<LineChartData>> getLineChartData(String serachDate, String reservationTypeSting, ReservationRepository reservationRepository) {
ReservationType reservationType = ReservationType.getReservationType(reservationTypeSting);
Date[] dateRange = getDateRange(serachDate);
int dayDiff = getDayDiff(dateRange[0], dateRange[1]);
//Get all reservations for the given month, domain and they MUST be accepted
List<ReservationEntity> filteredReservations = reservationRepository.findByDateAndType(dateRange[1], dateRange[0], ReservationType.DAYS.equals(reservationType), CloudToolsForCore.getDomainId());
//Prepare empty map for each reservation object
Map<String, List<LineChartData>> map = new HashMap<>();
for(ReservationEntity re : filteredReservations) {
String key = re.getReservationObjectName();
if(map.get(key) == null) {
map.put(key, getInitializeList(dateRange[0], dayDiff));
}
}
//Fill map with data
for(ReservationEntity re : filteredReservations) {
String key = re.getReservationObjectName();
List<LineChartData> reservationData = map.get(key);
long timeDiff = DateTools.timePartDiff(re.getDateFrom(), re.getDateTo());
BigDecimal hours = divide(timeDiff, HOUR_IN_MILLIS, 2);
int firstReservationDay = getDayOfMonth(re.getDateFrom());
int reservationRangeDiff = getDayDiff(re.getDateFrom(), re.getDateTo());
for(int i = firstReservationDay; i < (firstReservationDay + reservationRangeDiff); i++) {
if(ReservationType.HOURS.equals(reservationType))
reservationData.get(i-1).addToValue(hours);
else reservationData.get(i-1).addToValue(BigDecimal.ONE);
}
map.put(key, reservationData);
}
return map;
}
/**
* Return total hours interval for the given reservation and days interval
* First compute time diff in milliseconds for ONE DAY and then multiply by days interval
* @param re
* @param daysInterval
* @return
*/
private static BigDecimal computeHoursInterval(ReservationEntity re, int daysInterval) {
//Time diff in milliseconds for ONE DAY
long timeDiff = DateTools.timePartDiff(re.getDateFrom(), re.getDateTo());
//Time diff in milliseconds for WHOLE reservation -> timeDiff * daysInterval
long wholeDiff = timeDiff * daysInterval;
//Divide by 1000 * 60 * 60 to get hours -> round to 2 decimal places
return divide(wholeDiff, HOUR_IN_MILLIS, 2);
}
/**
* Return BigDecimal result of division of two numbers with given scale and rounding mode set to HALF_EVEN
* @param dividend
* @param divisor
* @param scale
* @return
*/
private static BigDecimal divide(Number dividend, Number divisor, int scale) {
return new BigDecimal(dividend.toString()).divide(new BigDecimal(divisor.toString()), scale, RoundingMode.HALF_EVEN);
}
/**
* Return date range for whole MONTH from the given string of format yyyy-MM.
* If text or format is not correct, return range for current month
* @param searchDate
* @return
*/
private static Date[] getDateRange(String searchDate) {
int year;
int month;
if(Tools.isNotEmpty(searchDate) && searchDate.matches(ReservationService.REGEX_YYYY_MM)) {
String[] dateParts = searchDate.split("-");
year = Integer.parseInt(dateParts[0]);
month = Integer.parseInt(dateParts[1]) - 1; //-1 because Calendar month starts with 0
} else {
Calendar cal = Calendar.getInstance();
cal.setTime(new Date());
year = cal.get(Calendar.YEAR);
month = cal.get(Calendar.MONTH);
}
//Get datetime range for the given month and year
Month monthInstance = new Month(year, month);
return new Date[]{monthInstance.getStartDate(), monthInstance.getEndDate()};
}
/**
* Return number of days between two dates
* @param starDate
* @param endDate
* @return
*/
private static int getDayDiff(Date starDate, Date endDate) {
Calendar cal1 = Calendar.getInstance();
cal1.setTime(starDate);
Calendar cal2 = Calendar.getInstance();
cal2.setTime(endDate);
return cal2.get(Calendar.DAY_OF_MONTH) - cal1.get(Calendar.DAY_OF_MONTH) + 1;
}
/**
* Return day of month for the given date
* @param date
* @return
*/
private static int getDayOfMonth(Date date) {
Calendar cal = Calendar.getInstance();
cal.setTime(date);
return cal.get(Calendar.DAY_OF_MONTH);
}
private static List<LineChartData> getInitializeList(Date startDate, int dayDiff) {
List<LineChartData> lineChartData = new ArrayList<>();
Calendar cal = Calendar.getInstance();
cal.setTime(startDate);
for(int i = 0; i < dayDiff; i++) {
lineChartData.add(new LineChartData(BigDecimal.ZERO, cal.getTime()));
cal.add(Calendar.DAY_OF_MONTH, 1);
}
return lineChartData;
}
}