ReservationRestController.java
package sk.iway.iwcm.components.reservation.rest;
import java.math.BigDecimal;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.servlet.http.HttpServletRequest;
import org.json.JSONObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import sk.iway.iwcm.Identity;
import sk.iway.iwcm.Tools;
import sk.iway.iwcm.common.CloudToolsForCore;
import sk.iway.iwcm.components.reservation.jpa.ReservationEditorFields;
import sk.iway.iwcm.components.reservation.jpa.ReservationEntity;
import sk.iway.iwcm.components.reservation.jpa.ReservationObjectEntity;
import sk.iway.iwcm.components.reservation.jpa.ReservationObjectPriceRepository;
import sk.iway.iwcm.components.reservation.jpa.ReservationObjectRepository;
import sk.iway.iwcm.components.reservation.jpa.ReservationObjectTimesEntity;
import sk.iway.iwcm.components.reservation.jpa.ReservationObjectTimesRepository;
import sk.iway.iwcm.components.reservation.jpa.ReservationRepository;
import sk.iway.iwcm.database.SimpleQuery;
import sk.iway.iwcm.i18n.Prop;
import sk.iway.iwcm.system.datatable.Datatable;
import sk.iway.iwcm.system.datatable.DatatablePageImpl;
import sk.iway.iwcm.system.datatable.DatatableRequest;
import sk.iway.iwcm.system.datatable.DatatableRestControllerV2;
import sk.iway.iwcm.system.datatable.NotifyBean;
import sk.iway.iwcm.system.datatable.NotifyBean.NotifyType;
import sk.iway.iwcm.system.datatable.ProcessItemAction;
import sk.iway.iwcm.system.jpa.DefaultTimeValueConverter;
import sk.iway.iwcm.users.UsersDB;
@RestController
@RequestMapping("/admin/rest/reservation/reservations")
@PreAuthorize("@WebjetSecurityService.hasPermission('cmp_reservation')")
@Datatable
public class ReservationRestController extends DatatableRestControllerV2<ReservationEntity, Long> {
private final ReservationRepository reservationRepository;
private final ReservationObjectRepository ror;
private final ReservationObjectTimesRepository rotr;
private final ReservationObjectPriceRepository ropr;
private static final String SESSION_ATRIBUTE_NAME = "reservationDeletePasswords";
private static final String RESERVATION_OBJECT_ID = "reservationObjectId";
@Autowired
public ReservationRestController(ReservationRepository reservationRepository, ReservationObjectRepository ror, ReservationObjectTimesRepository rotr, ReservationObjectPriceRepository ropr) {
super(reservationRepository);
this.reservationRepository = reservationRepository;
this.ror = ror;
this.rotr = rotr;
this.ropr = ropr;
}
@Override
public Page<ReservationEntity> getAllItems(Pageable pageable) {
DatatablePageImpl<ReservationEntity> page = new DatatablePageImpl<>( reservationRepository.findAllByDomainId(CloudToolsForCore.getDomainId(), pageable) );
List<ReservationObjectEntity> reservationObjects = ror.findAllByDomainId(CloudToolsForCore.getDomainId());
page.addOptions(RESERVATION_OBJECT_ID, reservationObjects, "name", "id", false);
//
processFromEntity(page, ProcessItemAction.GETALL);
return page;
}
@Override
public ReservationEntity getOneItem(long id) {
ReservationEntity entity;
if(id == -1) {
entity = new ReservationEntity();
processFromEntity(entity, ProcessItemAction.CREATE);
} else {
entity = reservationRepository.findFirstByIdAndDomainId(id, CloudToolsForCore.getDomainId()).orElse(null);
processFromEntity(entity, ProcessItemAction.EDIT);
}
return entity;
}
@Override
public ReservationEntity processFromEntity(ReservationEntity entity, ProcessItemAction action) {
//If editorFields if null create new
ReservationEditorFields ref = entity.getEditorFields() == null ? new ReservationEditorFields() : entity.getEditorFields();
ref.fromReservationEntity(entity, action, getRequest());
return entity;
}
@Override
public ReservationEntity processToEntity(ReservationEntity entity, ProcessItemAction action) {
if(entity.getEditorFields() != null) {
//Check if reservation object ID is set
if(entity.getReservationObjectId() != null) {
entity.setReservationObjectForReservation( ror.findFirstByIdAndDomainId(entity.getReservationObjectId(), CloudToolsForCore.getDomainId()).orElse(null) );
entity.getEditorFields().toReservationEntity(entity, reservationRepository, getRequest(), false, isImporting(), action);
} else throwError("");
}
return entity;
}
@Override
public boolean processAction(ReservationEntity entity, String action) {
String unexpectedError = getProp().getText("html_area.insert_image.error_occured");
String errorTitle = getProp().getText("reservation.reservations.password_for_delete.error_title");
String acceptanceTitle = getProp().getText("reservation.reservations.acceptance_notification");
Identity loggedUser = UsersDB.getCurrentUser(getRequest());
Long objectId = entity.getReservationObjectId();
//We are doing new delete process so clean passwords from session
if(action.equals("prepareVerify")) {
getRequest().getSession().removeAttribute(SESSION_ATRIBUTE_NAME);
return true;
}
//Save combination password - reservationObjectId into session
if(action.equals("verify")) {
String customData = getRequest().getParameter("customData");
if(customData != null && !customData.isEmpty()) {
try {
@SuppressWarnings("unchecked")
Map<Long, String> deletePasswords = (Map<Long, String>)getRequest().getSession().getAttribute(SESSION_ATRIBUTE_NAME);
if(deletePasswords == null) deletePasswords = new HashMap<>();
JSONObject jsonObject = new JSONObject(customData);
deletePasswords.put(jsonObject.getLong(RESERVATION_OBJECT_ID), (String)jsonObject.get("password"));
getRequest().getSession().setAttribute(SESSION_ATRIBUTE_NAME, deletePasswords);
} catch (Exception err){
addNotify(new NotifyBean(errorTitle, unexpectedError, NotifyType.ERROR, 15000));
return true;
}
}
return true;
}
//Check id
if(objectId == null) {
addNotify(new NotifyBean(acceptanceTitle, unexpectedError, NotifyType.ERROR, 15000));
return true;
}
ReservationObjectEntity reservationObject = ror.findFirstByIdAndDomainId(entity.getReservationObjectId(), CloudToolsForCore.getDomainId()).orElse(null);
String objectAccepterEmail = reservationObject.getEmailAccepter();
//Check if reservation needs acceptation
if(Tools.isTrue(reservationObject.getMustAccepted())) {
//This reservation needs acceptation, check if accepter email is set
if(objectAccepterEmail == null || objectAccepterEmail.isEmpty()) {
//Error because there is no accepter email
addNotify(new NotifyBean(acceptanceTitle, unexpectedError, NotifyType.ERROR, 15000));
return true;
}
//Check if logged user have right to approve/reject reservation upon reservationObject
if(!loggedUser.getEmail().equals(reservationObject.getEmailAccepter())) {
//Error because logged user can't accept/reject reservation
addNotify(new NotifyBean(acceptanceTitle, getProp().getText("reservation.reservations.no_right") + " <strong>" + reservationObject.getName() + "</strong>", NotifyType.ERROR, 15000));
return true;
}
}
ReservationService reservationService = new ReservationService(getProp());
if("approve".equals(action)) {
//Is this status already set ?
if(Tools.isTrue(entity.getAccepted())) {
addNotify(new NotifyBean(acceptanceTitle, getProp().getText("reservation.reservations.reservation_accepted_succ"), NotifyType.SUCCESS, 15000));
return true;
}
//FIRST check if reservation is still valid !!
if(Tools.isFalse(reservationObject.getReservationForAllDay())) {
String error = reservationService.checkReservationTimeRangeValidity(entity, reservationObject);
if(error != null) {
//REJECT reservation auto
entity.setAccepted(Boolean.FALSE);
//Send email
reservationService.sendConfirmationEmail(entity, getRequest(), loggedUser);
//Save changes entity
reservationRepository.save(entity);
addNotify(new NotifyBean(acceptanceTitle, getProp().getText(error), NotifyType.ERROR, 15000));
return true;
}
}
String error2 = reservationService.checkReservationOverlappingValidity(entity, reservationObject, reservationRepository, false);
if(error2 != null) {
//REJECT entity auto
entity.setAccepted(Boolean.FALSE);
//Send email
reservationService.sendConfirmationEmail(entity, getRequest(), loggedUser);
//Save changes entity
reservationRepository.save(entity);
addNotify(new NotifyBean(acceptanceTitle, getProp().getText(error2), NotifyType.ERROR, 15000));
return true;
}
//Reservation was approved
entity.setAccepted(Boolean.TRUE);
//Send email
reservationService.sendConfirmationEmail(entity, getRequest(), loggedUser);
addNotify(new NotifyBean(acceptanceTitle, getProp().getText("reservation.reservations.reservation_accepted_succ"), NotifyType.SUCCESS, 15000));
} else if("reject".equals(action)) {
//Is this status already set ?
if(Tools.isFalse(entity.getAccepted())) {
addNotify(new NotifyBean(acceptanceTitle, getProp().getText("reservation.reservations.reservation_rejected_succ"), NotifyType.SUCCESS, 15000));
return true;
}
//Reservation was rejected
entity.setAccepted(Boolean.FALSE);
//Send email
reservationService.sendConfirmationEmail(entity, getRequest(), loggedUser);
addNotify(new NotifyBean(acceptanceTitle, getProp().getText("reservation.reservations.reservation_rejected_succ"), NotifyType.SUCCESS, 15000));
} else if("reset".equals(action)) {
//Is this status already set ?
if(entity.getAccepted() == null) {
addNotify(new NotifyBean(acceptanceTitle, getProp().getText("reservation.reservations.reservation_reset_succ"), NotifyType.SUCCESS, 15000));
return true;
}
//Reservation now will waiting for acceptation
entity.setAccepted(null);
//Send email
reservationService.sendConfirmationEmail(entity, getRequest(), loggedUser);
addNotify(new NotifyBean(acceptanceTitle, getProp().getText("reservation.reservations.reservation_reset_succ"), NotifyType.SUCCESS, 15000));
}
//Save changes entity
reservationRepository.save(entity);
return true;
}
@Override
public void beforeSave(ReservationEntity entity) {
//Email is NULL, because this is ADMIN section, so admin must be logged in
int userId = ReservationService.getUserToPay(null, entity.getId(), reservationRepository, getRequest());
//INSERT action
if(entity.getId() == null || entity.getId() == -1) {
entity.setDomainId(CloudToolsForCore.getDomainId());
entity.setUserId(userId);
entity.setId(-1L);
}
//Set price of reservation
entity.setPrice( ReservationService.calculateReservationPrice(entity, userId, ror, ropr) );
}
@Override
public void addSpecSearch(Map<String, String> params, List<Predicate> predicates, Root<ReservationEntity> root, CriteriaBuilder builder) {
//Search based on reservationObjectName, we find inside DB then in columns reservationObjectId
String searchReservationObjectName = params.get("searchEditorFields.selectedReservation");
if (searchReservationObjectName != null)
addSpecSearchReservationObjectName(searchReservationObjectName, RESERVATION_OBJECT_ID, predicates, root, builder);
super.addSpecSearch(params, predicates, root, builder);
}
/**
* Special search to filter reservations based on reservationObjectName (paramValue). Because ReservationEntity do not contain column with reservationObjectName (only reservationObjectId),
* we will use that reservationObjectName to get list of reservationObjectIds and than use jpaProperty.in() to filter only thus reservations with reservationObvejctId in this list.
* @param paramValue - searched reservation obejct name
* @param jpaProperty - name of JPA property, that must by inside returned reservation obejct ids list
* @param predicates
* @param root
* @param builder
*/
private static void addSpecSearchReservationObjectName(String paramValue, String jpaProperty, List<Predicate> predicates, Root<ReservationEntity> root, CriteriaBuilder builder) {
String valueClean = DatatableRestControllerV2.getCleanValue(paramValue);
String operator = "LIKE";
String prepend = "%";
String append = "%";
if (paramValue.startsWith("^") && paramValue.endsWith("$")) {
operator = "=";
prepend = "";
append = "";
} else if (paramValue.startsWith("^")) {
prepend = "";
} else if (paramValue.endsWith("$")) {
append = "";
}
List<Integer> reservationObejctIds = (new SimpleQuery()).forListInteger("SELECT DISTINCT reservation_object_id FROM reservation_object WHERE name " + operator + " ?", prepend + valueClean + append);
if(reservationObejctIds.isEmpty() == false) predicates.add(root.get(jpaProperty).in(reservationObejctIds));
else predicates.add(builder.equal(root.get(jpaProperty), Integer.MAX_VALUE));
}
@Override
public boolean deleteItem(ReservationEntity entity, long id) {
String unexpectedError = getProp().getText("html_area.insert_image.error_occured");
String errorTitle = getProp().getText("reservation.reservations.password_for_delete.error_title");
//Check if reservation object need password
if(Tools.isTrue(entity.getEditorFields().getNeedPasswordToDelete())) {
Prop prop = getProp();
Optional<ReservationObjectEntity> optReservationObject = ror.findFirstByIdAndDomainId(entity.getReservationObjectId(), CloudToolsForCore.getDomainId());
if(!optReservationObject.isPresent()) {
addNotify(new NotifyBean(errorTitle, getProp().getText("reservation.reservations.password_for_delete.error_entity_not_found"), NotifyType.ERROR, 15000));
return false;
}
@SuppressWarnings("unchecked")
Map<Long, String> deletePasswords = (Map<Long, String>)getRequest().getSession().getAttribute(SESSION_ATRIBUTE_NAME);
String password = deletePasswords.get(entity.getReservationObjectId());
if(password == null) {
addNotify(new NotifyBean(errorTitle, unexpectedError, NotifyType.ERROR, 15000));
return false;
}
//Verify password
if(optReservationObject.get().checkPasswordAndHashEquality(password, optReservationObject.get().getPassword())) reservationRepository.delete(entity);
else {
String errorText = prop.getText("reservation.reservations.password_for_delete.error_bad_password_1") + " <b>" + optReservationObject.get().getName() + " </b> ";
errorText += prop.getText("reservation.reservations.password_for_delete.error_bad_password_2") + " <b>" + id + " </b> ";
errorText += prop.getText("reservation.reservations.password_for_delete.error_bad_password_3");
addNotify(new NotifyBean(errorTitle, errorText, NotifyType.ERROR, 15000));
}
} else reservationRepository.delete(entity);
return true;
}
@Override
public void afterSave(ReservationEntity entity, ReservationEntity saved) {
if(!isImporting()) {
//Check if we must send Acceptation email
ReservationObjectEntity reservationObject = entity.getReservationObjectForReservation();
ReservationService reservationService = new ReservationService(getProp());
if(reservationObject != null && Tools.isTrue(reservationObject.getMustAccepted()) && entity.getAccepted() == null) {
//for some reason time part is lost even when in DB its saved good
entity.setDateFrom( DefaultTimeValueConverter.combineDateWithTime(entity.getDateFrom(), entity.getEditorFields().getReservationTimeFrom()) );
entity.setDateTo( DefaultTimeValueConverter.combineDateWithTime(entity.getDateTo(), entity.getEditorFields().getReservationTimeTo()) );
reservationService.sendAcceptationEmail(entity, getRequest());
}
//Send email only if reservation was created - new only
if(entity.getId() == -1)
reservationService.sendCreatedReservationEmail(saved, getRequest());
}
}
@Override
public void validateEditor(HttpServletRequest request, DatatableRequest<Long, ReservationEntity> target, Identity user, Errors errors, Long id, ReservationEntity entity) {
if(target.getAction().equals("create") || target.getAction().equals("edit") && entity.getEditorFields() != null) {
//Is object set as ALL DAY reservation ? - if yes, we do not need to check time
Boolean allDay = false;
if (entity.getReservationObjectId()!=null) allDay = ror.isReservationForAllDay(entity.getReservationObjectId());
if(Tools.isTrue(allDay)) {
if(entity.getEditorFields().getArrivingTime() == null) {
//errors.rejectValue("errorField.editorFields.arrivingTime", null, getProp().getText("javax.validation.constraints.NotBlank.message"));
if (entity.getEditorFields().getReservationTimeFrom() != null) entity.getEditorFields().setArrivingTime(entity.getEditorFields().getReservationTimeFrom());
else entity.getEditorFields().setArrivingTime(ReservationService.getArrivalTime(entity));
}
if(entity.getEditorFields().getDepartureTime() == null) {
//errors.rejectValue("errorField.editorFields.departureTime", null, getProp().getText("javax.validation.constraints.NotBlank.message"));
if (entity.getEditorFields().getReservationTimeTo() != null) entity.getEditorFields().setDepartureTime(entity.getEditorFields().getReservationTimeTo());
else entity.getEditorFields().setDepartureTime(ReservationService.getDepartureTime(entity));
}
} else {
if(entity.getEditorFields().getReservationTimeFrom() == null)
errors.rejectValue("errorField.editorFields.reservationTimeFrom", null, getProp().getText("javax.validation.constraints.NotBlank.message"));
if(entity.getEditorFields().getReservationTimeTo() == null)
errors.rejectValue("errorField.editorFields.reservationTimeTo", null, getProp().getText("javax.validation.constraints.NotBlank.message"));
}
if(Tools.isNotEmpty(entity.getEmail()) && Tools.isEmail(entity.getEmail())==false) {
errors.rejectValue("errorField.email", null, getProp().getText("javax.validation.constraints.Email.message"));
}
}
super.validateEditor(request, target, user, errors, id, entity);
}
@RequestMapping(
value="/compute-reservation-price",
params={"date-from", "date-to", "time-from", "time-to", "object-id"})
public BigDecimal computeReservationPrice(
@RequestParam("date-from") Long dateFrom,
@RequestParam("date-to") Long dateTo,
@RequestParam("time-from") Long timeFrom,
@RequestParam("time-to") Long timeTo,
@RequestParam("object-id") Long objectId,
@RequestParam("reservation-id") Long reservationId) {
//Email is NULL, because this is ADMIN section, so admin must be logged in
int userId = ReservationService.getUserToPay(null, reservationId, reservationRepository, getRequest());
return ReservationService.calculateReservationPrice(dateFrom, dateTo, timeFrom, timeTo, objectId, userId, ror, ropr);
}
@RequestMapping(path={"/reservation-object/{objectId}"})
public ReservationObjectEntity getReservationObject(@PathVariable Long objectId) {
if(objectId != null) {
//Get reservation object
ReservationObjectEntity reservationObject = ror.findFirstByIdAndDomainId(objectId, CloudToolsForCore.getDomainId()).orElse(null);
if(reservationObject == null) {
throwError("Reservation object was not found.");
return null;
}
//First set default values
String defaultTimeRangeString = getTimeStringRange(reservationObject.getReservationTimeFrom(), reservationObject.getReservationTimeTo());
HashMap<Integer, String> objectTimesInfo = new HashMap<>();
for(int day = 1; day <= 7; day++)
objectTimesInfo.put(day, defaultTimeRangeString);
for(ReservationObjectTimesEntity objectTime : reservationObject.getReservationObjectTimeEntities()) {
//Key (Integer) is day of week 1,2 ... 7
//Value (String) is combination timeFrom + "-" + timeTo (HH:mm format)
String timeRangeString = getTimeStringRange(objectTime.getTimeFrom(), objectTime.getTimeTo());
objectTimesInfo.put(objectTime.getDay(), timeRangeString);
}
reservationObject.setObjectTimesInfo(objectTimesInfo);
return reservationObject;
} else throwError("");
return null;
}
@RequestMapping(
value="/check-reservation-validity",
params={"date-from", "date-to", "time-from", "time-to", "object-id", "reservation-id", "allow-history-save", "allow-overbooking", "isDuplicate"})
public String checkReservationValidity(
@RequestParam("date-from") Long dateFrom,
@RequestParam("date-to") Long dateTo,
@RequestParam("time-from") Long timeFrom, //or aka arrivalTime if it's all day reservation
@RequestParam("time-to") Long timeTo, //or aka departureTime if it's all day reservation
@RequestParam("object-id") Long objectId,
@RequestParam("reservation-id") Long reservationId,
@RequestParam("allow-history-save") boolean allowHistorySave,
@RequestParam("allow-overbooking") boolean allowOverbooking,
@RequestParam("isDuplicate") boolean isDuplicate) {
String unexpectedError = getProp().getText("html_area.insert_image.error_occured");
if(dateFrom == null || dateTo == null || timeFrom == null || timeTo == null || objectId == null)
return unexpectedError;
//Get reservation object
ReservationObjectEntity reservationObject = ror.findFirstByIdAndDomainId(objectId, CloudToolsForCore.getDomainId()).orElse(null);
if(reservationObject == null) {
throwError("Reservation object was not found.");
return null;
}
//We need keep data as fresh for validation, so get joined object times from DB
reservationObject.setReservationObjectTimeEntities( rotr.findAllByObjectIdAndDomainId(reservationObject.getId(), CloudToolsForCore.getDomainId()) );
//Create reservation entity (just for test purpose)
ReservationEntity reservation = new ReservationEntity();
//Set reservation
reservation.setDateFrom(new Date(dateFrom));
reservation.setDateTo(new Date(dateTo));
if(isDuplicate) {
reservation.setId(Long.valueOf(-1));
} else {
reservation.setId(reservationId);
}
ReservationEditorFields ef = new ReservationEditorFields();
if(Tools.isTrue(reservationObject.getReservationForAllDay())) {
ef.setArrivingTime(DefaultTimeValueConverter.getValidTimeValue(new Date(timeFrom)));
ef.setDepartureTime(DefaultTimeValueConverter.getValidTimeValue(new Date(timeTo)));
} else {
ef.setReservationTimeFrom(DefaultTimeValueConverter.getValidTimeValue(new Date(timeFrom)));
ef.setReservationTimeTo(DefaultTimeValueConverter.getValidTimeValue(new Date(timeTo)));
}
ef.setAllowHistorySave(allowHistorySave);
ef.setAllowOverbooking(allowOverbooking);
reservation.setEditorFields(ef);
ReservationService reservationService = new ReservationService(getProp());
//Prepare entity
try{
reservationService.prepareReservationToValidation(reservation, Tools.isTrue(reservationObject.getReservationForAllDay()));
} catch(IllegalArgumentException e) {
return unexpectedError;
}
String error = null;
if(Tools.isFalse(reservationObject.getReservationForAllDay())) {
error = reservationService.checkReservationTimeRangeValidity(reservation, reservationObject);
if(error != null) return error;
}
error = reservationService.checkReservationOverlappingValidity(reservation, reservationObject, reservationRepository, false);
if(error != null) return error;
return null;
}
private String getTimeStringRange(Date start, Date end) {
if(start == null || end == null) return "";
return new SimpleDateFormat("HH:mm").format(start) + " - " + new SimpleDateFormat("HH:mm").format(end);
}
}