package org.synyx.urlaubsverwaltung.web.application;
import org.joda.time.DateMidnight;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import org.synyx.urlaubsverwaltung.core.account.domain.Account;
import org.synyx.urlaubsverwaltung.core.account.service.AccountService;
import org.synyx.urlaubsverwaltung.core.account.service.VacationDaysService;
import org.synyx.urlaubsverwaltung.core.application.domain.Application;
import org.synyx.urlaubsverwaltung.core.application.domain.ApplicationComment;
import org.synyx.urlaubsverwaltung.core.application.domain.ApplicationStatus;
import org.synyx.urlaubsverwaltung.core.application.service.ApplicationCommentService;
import org.synyx.urlaubsverwaltung.core.application.service.ApplicationInteractionService;
import org.synyx.urlaubsverwaltung.core.application.service.ApplicationService;
import org.synyx.urlaubsverwaltung.core.application.service.exception.ImpatientAboutApplicationForLeaveProcessException;
import org.synyx.urlaubsverwaltung.core.application.service.exception.RemindAlreadySentException;
import org.synyx.urlaubsverwaltung.core.department.DepartmentService;
import org.synyx.urlaubsverwaltung.core.person.Person;
import org.synyx.urlaubsverwaltung.core.person.PersonService;
import org.synyx.urlaubsverwaltung.core.person.Role;
import org.synyx.urlaubsverwaltung.core.util.DateUtil;
import org.synyx.urlaubsverwaltung.core.workingtime.WorkDaysService;
import org.synyx.urlaubsverwaltung.core.workingtime.WorkingTime;
import org.synyx.urlaubsverwaltung.core.workingtime.WorkingTimeService;
import org.synyx.urlaubsverwaltung.security.SecurityRules;
import org.synyx.urlaubsverwaltung.security.SessionService;
import org.synyx.urlaubsverwaltung.web.ControllerConstants;
import org.synyx.urlaubsverwaltung.web.person.PersonConstants;
import org.synyx.urlaubsverwaltung.web.person.UnknownPersonException;
import java.util.List;
import java.util.Optional;
/**
* Controller to manage applications for leave.
*
* @author Aljona Murygina - murygina@synyx.de
*/
@RequestMapping("/web/application")
@Controller
public class ApplicationForLeaveDetailsController {
@Autowired
private SessionService sessionService;
@Autowired
private PersonService personService;
@Autowired
private AccountService accountService;
@Autowired
private ApplicationService applicationService;
@Autowired
private ApplicationInteractionService applicationInteractionService;
@Autowired
private VacationDaysService vacationDaysService;
@Autowired
private ApplicationCommentService commentService;
@Autowired
private WorkDaysService workDaysService;
@Autowired
private ApplicationCommentValidator commentValidator;
@Autowired
private DepartmentService departmentService;
@Autowired
private WorkingTimeService workingTimeService;
@RequestMapping(value = "/{applicationId}", method = RequestMethod.GET)
public String showApplicationDetail(@PathVariable("applicationId") Integer applicationId,
@RequestParam(value = ControllerConstants.YEAR_ATTRIBUTE, required = false) Integer requestedYear,
@RequestParam(value = "action", required = false) String action,
@RequestParam(value = "shortcut", required = false) boolean shortcut, Model model)
throws UnknownApplicationForLeaveException, AccessDeniedException {
Application application = applicationService.getApplicationById(applicationId).orElseThrow(() ->
new UnknownApplicationForLeaveException(applicationId));
Person signedInUser = sessionService.getSignedInUser();
Person person = application.getPerson();
if (!sessionService.isSignedInUserAllowedToAccessPersonData(signedInUser, person)) {
throw new AccessDeniedException(String.format(
"User '%s' has not the correct permissions to see application for leave of user '%s'",
signedInUser.getLoginName(), person.getLoginName()));
}
Integer year = requestedYear == null ? application.getEndDate().getYear() : requestedYear;
prepareDetailView(application, year, action, shortcut, model);
return "application/app_detail";
}
private void prepareDetailView(Application application, int year, String action, boolean shortcut, Model model) {
// COMMENTS
List<ApplicationComment> comments = commentService.getCommentsByApplication(application);
model.addAttribute("comment", new ApplicationCommentForm());
model.addAttribute("comments", comments);
model.addAttribute("lastComment", comments.get(comments.size() - 1));
// SPECIAL ATTRIBUTES FOR BOSSES / DEPARTMENT HEADS
Person signedInUser = sessionService.getSignedInUser();
boolean isNotYetAllowed = application.hasStatus(ApplicationStatus.WAITING)
|| application.hasStatus(ApplicationStatus.TEMPORARY_ALLOWED);
boolean isPrivilegedUser = signedInUser.hasRole(Role.BOSS) || signedInUser.hasRole(Role.DEPARTMENT_HEAD)
|| signedInUser.hasRole(Role.SECOND_STAGE_AUTHORITY);
if (isNotYetAllowed && isPrivilegedUser) {
model.addAttribute("bosses", personService.getPersonsByRole(Role.BOSS));
model.addAttribute("referredPerson", new ReferredPerson());
}
// APPLICATION FOR LEAVE
model.addAttribute("application", new ApplicationForLeave(application, workDaysService));
// WORKING TIME FOR VACATION PERIOD
Optional<WorkingTime> optionalWorkingTime = workingTimeService.getByPersonAndValidityDateEqualsOrMinorDate(
application.getPerson(), application.getStartDate());
if (optionalWorkingTime.isPresent()) {
model.addAttribute("workingTime", optionalWorkingTime.get());
}
// DEPARTMENT APPLICATIONS FOR LEAVE
List<Application> departmentApplications =
departmentService.getApplicationsForLeaveOfMembersInDepartmentsOfPerson(application.getPerson(),
application.getStartDate(), application.getEndDate());
model.addAttribute("departmentApplications", departmentApplications);
// HOLIDAY ACCOUNT
Optional<Account> account = accountService.getHolidaysAccount(year, application.getPerson());
if (account.isPresent()) {
model.addAttribute("vacationDaysLeft", vacationDaysService.getVacationDaysLeft(account.get()));
model.addAttribute("account", account.get());
model.addAttribute(PersonConstants.BEFORE_APRIL_ATTRIBUTE, DateUtil.isBeforeApril(DateMidnight.now()));
}
// UNSPECIFIC ATTRIBUTES
model.addAttribute(ControllerConstants.YEAR_ATTRIBUTE, year);
model.addAttribute("action", action);
model.addAttribute("shortcut", shortcut);
}
/**
* Allow a not yet allowed application for leave (Privileged user only!).
*/
@PreAuthorize(SecurityRules.IS_BOSS_OR_DEPARTMENT_HEAD_OR_SECOND_STAGE_AUTHORITY)
@RequestMapping(value = "/{applicationId}/allow", method = RequestMethod.POST)
public String allowApplication(@PathVariable("applicationId") Integer applicationId,
@ModelAttribute("comment") ApplicationCommentForm comment,
@RequestParam(value = "redirect", required = false) String redirectUrl, Errors errors,
RedirectAttributes redirectAttributes) throws UnknownApplicationForLeaveException, AccessDeniedException {
Application application = applicationService.getApplicationById(applicationId).orElseThrow(() ->
new UnknownApplicationForLeaveException(applicationId));
Person signedInUser = sessionService.getSignedInUser();
Person person = application.getPerson();
boolean isBoss = signedInUser.hasRole(Role.BOSS);
boolean isDepartmentHead = signedInUser.hasRole(Role.DEPARTMENT_HEAD)
&& departmentService.isDepartmentHeadOfPerson(signedInUser, person);
boolean isSecondStageAuthority = signedInUser.hasRole(Role.SECOND_STAGE_AUTHORITY)
&& departmentService.isSecondStageAuthorityOfPerson(signedInUser, person);
if (!isBoss && !isDepartmentHead && !isSecondStageAuthority) {
throw new AccessDeniedException(String.format(
"User '%s' has not the correct permissions to allow application for leave of user '%s'",
signedInUser.getLoginName(), person.getLoginName()));
}
comment.setMandatory(false);
commentValidator.validate(comment, errors);
if (errors.hasErrors()) {
redirectAttributes.addFlashAttribute(ControllerConstants.ERRORS_ATTRIBUTE, errors);
return "redirect:/web/application/" + applicationId + "?action=allow";
}
Application allowedApplicationForLeave = applicationInteractionService.allow(application, signedInUser,
Optional.ofNullable(comment.getText()));
if (allowedApplicationForLeave.hasStatus(ApplicationStatus.ALLOWED)) {
redirectAttributes.addFlashAttribute("allowSuccess", true);
} else if (allowedApplicationForLeave.hasStatus(ApplicationStatus.TEMPORARY_ALLOWED)) {
redirectAttributes.addFlashAttribute("temporaryAllowSuccess", true);
}
if (redirectUrl != null) {
return "redirect:" + redirectUrl;
}
return "redirect:/web/application/" + applicationId;
}
/**
* If a boss is not sure about the decision if an application should be allowed or rejected, he can ask another boss
* to decide about this application (an email is sent).
*/
@PreAuthorize(SecurityRules.IS_BOSS_OR_DEPARTMENT_HEAD)
@RequestMapping(value = "/{applicationId}/refer", method = RequestMethod.POST)
public String referApplication(@PathVariable("applicationId") Integer applicationId,
@ModelAttribute("referredPerson") ReferredPerson referredPerson, RedirectAttributes redirectAttributes)
throws UnknownApplicationForLeaveException, UnknownPersonException, AccessDeniedException {
Application application = applicationService.getApplicationById(applicationId).orElseThrow(() ->
new UnknownApplicationForLeaveException(applicationId));
String referLoginName = referredPerson.getLoginName();
Person recipient = personService.getPersonByLogin(referLoginName).orElseThrow(() ->
new UnknownPersonException(referLoginName));
Person sender = sessionService.getSignedInUser();
boolean isBoss = sender.hasRole(Role.BOSS);
boolean isDepartmentHead = departmentService.isDepartmentHeadOfPerson(sender, application.getPerson());
if (isBoss || isDepartmentHead) {
applicationInteractionService.refer(application, recipient, sender);
redirectAttributes.addFlashAttribute("referSuccess", true);
return "redirect:/web/application/" + applicationId;
}
throw new AccessDeniedException(String.format(
"User '%s' has not the correct permissions to refer application for leave to user '%s'",
sender.getLoginName(), referLoginName));
}
/**
* Reject an application for leave (Boss only!).
*/
@PreAuthorize(SecurityRules.IS_BOSS_OR_DEPARTMENT_HEAD_OR_SECOND_STAGE_AUTHORITY)
@RequestMapping(value = "/{applicationId}/reject", method = RequestMethod.POST)
public String rejectApplication(@PathVariable("applicationId") Integer applicationId,
@ModelAttribute("comment") ApplicationCommentForm comment,
@RequestParam(value = "redirect", required = false) String redirectUrl, Errors errors,
RedirectAttributes redirectAttributes) throws UnknownApplicationForLeaveException, AccessDeniedException {
Application application = applicationService.getApplicationById(applicationId).orElseThrow(() ->
new UnknownApplicationForLeaveException(applicationId));
Person person = application.getPerson();
Person signedInUser = sessionService.getSignedInUser();
boolean isBoss = signedInUser.hasRole(Role.BOSS);
boolean isDepartmentHead = departmentService.isDepartmentHeadOfPerson(signedInUser, person);
boolean isSecondStageAuthority = departmentService.isSecondStageAuthorityOfPerson(signedInUser, person);
if (isBoss || isDepartmentHead || isSecondStageAuthority) {
comment.setMandatory(true);
commentValidator.validate(comment, errors);
if (errors.hasErrors()) {
redirectAttributes.addFlashAttribute(ControllerConstants.ERRORS_ATTRIBUTE, errors);
if (redirectUrl != null) {
return "redirect:/web/application/" + applicationId + "?action=reject&shortcut=true";
}
return "redirect:/web/application/" + applicationId + "?action=reject";
}
applicationInteractionService.reject(application, signedInUser, Optional.ofNullable(comment.getText()));
redirectAttributes.addFlashAttribute("rejectSuccess", true);
if (redirectUrl != null) {
return "redirect:" + redirectUrl;
}
return "redirect:/web/application/" + applicationId;
}
throw new AccessDeniedException(String.format(
"User '%s' has not the correct permissions to reject application for leave of user '%s'",
signedInUser.getLoginName(), person.getLoginName()));
}
/**
* Cancel an application for leave. Cancelling an application for leave on behalf for someone is allowed only for
* Office.
*/
@RequestMapping(value = "/{applicationId}/cancel", method = RequestMethod.POST)
public String cancelApplication(@PathVariable("applicationId") Integer applicationId,
@ModelAttribute("comment") ApplicationCommentForm comment, Errors errors, RedirectAttributes redirectAttributes)
throws UnknownApplicationForLeaveException, AccessDeniedException {
Application application = applicationService.getApplicationById(applicationId).orElseThrow(() ->
new UnknownApplicationForLeaveException(applicationId));
Person signedInUser = sessionService.getSignedInUser();
boolean isWaiting = application.hasStatus(ApplicationStatus.WAITING);
boolean isAllowed = application.hasStatus(ApplicationStatus.ALLOWED);
boolean isTemporaryAllowed = application.hasStatus((ApplicationStatus.TEMPORARY_ALLOWED));
// security check: only two cases where cancelling is possible
// 1: user can cancel her own applications for leave if it has not been allowed yet
// 2: user can request cancellation if the application is already allowed.
// 3: office can cancel all applications for leave that has the state waiting or allowed, even for other persons
if (signedInUser.equals(application.getPerson())) {
// user can cancel only her own waiting applications, so the comment is NOT mandatory
comment.setMandatory(false);
} else if (signedInUser.hasRole(Role.OFFICE) && (isWaiting || isAllowed || isTemporaryAllowed)) {
// office cancels application of other users, state can be waiting or allowed, so the comment is mandatory
comment.setMandatory(true);
} else {
throw new AccessDeniedException(String.format(
"User '%s' has not the correct permissions to cancel application for leave of user '%s'",
signedInUser.getLoginName(), application.getPerson().getLoginName()));
}
commentValidator.validate(comment, errors);
if (errors.hasErrors()) {
redirectAttributes.addFlashAttribute(ControllerConstants.ERRORS_ATTRIBUTE, errors);
return "redirect:/web/application/" + applicationId + "?action=cancel";
}
applicationInteractionService.cancel(application, signedInUser, Optional.ofNullable(comment.getText()));
return "redirect:/web/application/" + applicationId;
}
/**
* Remind the bosses about the decision of an application for leave.
*/
@RequestMapping(value = "/{applicationId}/remind", method = RequestMethod.POST)
public String remindBoss(@PathVariable("applicationId") Integer applicationId,
RedirectAttributes redirectAttributes) throws UnknownApplicationForLeaveException {
Application application = applicationService.getApplicationById(applicationId).orElseThrow(() ->
new UnknownApplicationForLeaveException(applicationId));
try {
applicationInteractionService.remind(application);
redirectAttributes.addFlashAttribute("remindIsSent", true);
} catch (RemindAlreadySentException ex) {
redirectAttributes.addFlashAttribute("remindAlreadySent", true);
} catch (ImpatientAboutApplicationForLeaveProcessException ex) {
redirectAttributes.addFlashAttribute("remindNoWay", true);
}
return "redirect:/web/application/" + applicationId;
}
}