package org.synyx.urlaubsverwaltung.web.sicknote;
import org.joda.time.DateMidnight;
import org.joda.time.Interval;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.validation.Errors;
import org.springframework.validation.Validator;
import org.synyx.urlaubsverwaltung.core.period.DayLength;
import org.synyx.urlaubsverwaltung.core.sicknote.SickNote;
import org.synyx.urlaubsverwaltung.core.sicknote.SickNoteComment;
import org.synyx.urlaubsverwaltung.core.workingtime.OverlapCase;
import org.synyx.urlaubsverwaltung.core.workingtime.OverlapService;
import org.synyx.urlaubsverwaltung.core.workingtime.WorkingTime;
import org.synyx.urlaubsverwaltung.core.workingtime.WorkingTimeService;
import java.util.Optional;
/**
* Class for validating {@link SickNote} object.
*
* @author Aljona Murygina - murygina@synyx.de
*/
@Component
public class SickNoteValidator implements Validator {
private static final String ERROR_MANDATORY_FIELD = "error.entry.mandatory";
private static final String ERROR_PERIOD = "error.entry.invalidPeriod";
private static final String ERROR_LENGTH = "error.entry.tooManyChars";
private static final String ERROR_PERIOD_SICK_NOTE = "sicknote.error.aubInvalidPeriod";
private static final String ERROR_HALF_DAY_PERIOD_SICK_NOTE = "sicknote.error.halfDayPeriod";
private static final String ERROR_OVERLAP = "application.error.overlap";
private static final String ERROR_WORKING_TIME = "sicknote.error.noValidWorkingTime";
private static final String ATTRIBUTE_DAY_LENGTH = "dayLength";
private static final String ATTRIBUTE_START_DATE = "startDate";
private static final String ATTRIBUTE_END_DATE = "endDate";
private static final String ATTRIBUTE_AUB_START_DATE = "aubStartDate";
private static final String ATTRIBUTE_AUB_END_DATE = "aubEndDate";
private static final String ATTRIBUTE_COMMENT = "text";
private static final int MAX_CHARS = 200;
private final OverlapService overlapService;
private final WorkingTimeService workingTimeService;
@Autowired
public SickNoteValidator(OverlapService overlapService, WorkingTimeService workingTimeService) {
this.overlapService = overlapService;
this.workingTimeService = workingTimeService;
}
@Override
public boolean supports(Class<?> clazz) {
return SickNote.class.equals(clazz);
}
@Override
public void validate(Object target, Errors errors) {
SickNote sickNote = (SickNote) target;
validateSickNotePeriod(sickNote, errors);
if (sickNote.isAubPresent()) {
validateAUPeriod(sickNote, errors);
}
}
private void validateSickNotePeriod(SickNote sickNote, Errors errors) {
DayLength dayLength = sickNote.getDayLength();
DateMidnight startDate = sickNote.getStartDate();
DateMidnight endDate = sickNote.getEndDate();
validateNotNull(startDate, ATTRIBUTE_START_DATE, errors);
validateNotNull(endDate, ATTRIBUTE_END_DATE, errors);
if (dayLength == null) {
errors.rejectValue(ATTRIBUTE_DAY_LENGTH, ERROR_MANDATORY_FIELD);
}
if (dayLength != null && startDate != null && endDate != null) {
validatePeriod(startDate, endDate, dayLength, ATTRIBUTE_END_DATE, errors);
validateNoOverlapping(sickNote, errors);
}
}
private void validateAUPeriod(SickNote sickNote, Errors errors) {
DayLength dayLength = sickNote.getDayLength();
DateMidnight aubStartDate = sickNote.getAubStartDate();
DateMidnight aubEndDate = sickNote.getAubEndDate();
validateNotNull(aubStartDate, ATTRIBUTE_AUB_START_DATE, errors);
validateNotNull(aubEndDate, ATTRIBUTE_AUB_END_DATE, errors);
if (aubStartDate != null && aubEndDate != null) {
validatePeriod(aubStartDate, aubEndDate, dayLength, ATTRIBUTE_AUB_END_DATE, errors);
DateMidnight startDate = sickNote.getStartDate();
DateMidnight endDate = sickNote.getEndDate();
if (startDate != null && endDate != null && endDate.isAfter(startDate)) {
// Intervals are inclusive of the start instant and exclusive of the end, i.e. add one day at the end
Interval interval = new Interval(startDate, endDate.plusDays(1));
if (!interval.contains(aubStartDate)) {
errors.rejectValue(ATTRIBUTE_AUB_START_DATE, ERROR_PERIOD_SICK_NOTE);
}
if (!interval.contains(aubEndDate)) {
errors.rejectValue(ATTRIBUTE_AUB_END_DATE, ERROR_PERIOD_SICK_NOTE);
}
}
}
}
private void validateNotNull(DateMidnight date, String field, Errors errors) {
// may be that date field is null because of cast exception, than there is already a field error
if (date == null && errors.getFieldErrors(field).isEmpty()) {
errors.rejectValue(field, ERROR_MANDATORY_FIELD);
}
}
/**
* Validate that the given start date is not after the given end date.
*
* @param startDate
* @param endDate
* @param field
* @param errors
*/
private void validatePeriod(DateMidnight startDate, DateMidnight endDate, DayLength dayLength, String field,
Errors errors) {
if (startDate.isAfter(endDate)) {
errors.rejectValue(field, ERROR_PERIOD);
} else {
boolean isHalfDay = dayLength == DayLength.MORNING || dayLength == DayLength.NOON;
if (isHalfDay && !startDate.isEqual(endDate)) {
errors.rejectValue(field, ERROR_HALF_DAY_PERIOD_SICK_NOTE);
}
}
}
private void validateNoOverlapping(SickNote sickNote, Errors errors) {
/**
* Ensure the person has a working time for the period of the sick note
*/
Optional<WorkingTime> workingTime = workingTimeService.getByPersonAndValidityDateEqualsOrMinorDate(
sickNote.getPerson(), sickNote.getStartDate());
if (!workingTime.isPresent()) {
errors.reject(ERROR_WORKING_TIME);
return;
}
/**
* Ensure that there is no application for leave and no sick note in the same period
*/
OverlapCase overlap = overlapService.checkOverlap(sickNote);
boolean isOverlapping = overlap == OverlapCase.FULLY_OVERLAPPING || overlap == OverlapCase.PARTLY_OVERLAPPING;
if (isOverlapping) {
errors.reject(ERROR_OVERLAP);
}
}
public void validateComment(SickNoteComment comment, Errors errors) {
String text = comment.getText();
if (StringUtils.hasText(text)) {
if (text.length() > MAX_CHARS) {
errors.rejectValue(ATTRIBUTE_COMMENT, ERROR_LENGTH);
}
} else {
errors.rejectValue(ATTRIBUTE_COMMENT, ERROR_MANDATORY_FIELD);
}
}
}