org.fenixedu.ulisboa.specifications.domain.evaluation.markSheet.CompetenceCourseMarkSheet.java Source code

Java tutorial

Introduction

Here is the source code for org.fenixedu.ulisboa.specifications.domain.evaluation.markSheet.CompetenceCourseMarkSheet.java

Source

/**
 * This file was created by Quorum Born IT <http://www.qub-it.com/> and its 
 * copyright terms are bind to the legal agreement regulating the FenixEdu@ULisboa 
 * software development project between Quorum Born IT and Servios Partilhados da
 * Universidade de Lisboa:
 *  - Copyright  2015 Quorum Born IT (until any Go-Live phase)
 *  - Copyright  2015 Universidade de Lisboa (after any Go-Live phase)
 *
 * Contributors: luis.egidio@qub-it.com
 *
 * 
 * This file is part of FenixEdu Specifications.
 *
 * FenixEdu Specifications is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * FenixEdu Specifications is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with FenixEdu Specifications.  If not, see <http://www.gnu.org/licenses/>.
 */

package org.fenixedu.ulisboa.specifications.domain.evaluation.markSheet;

import java.text.Collator;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.Optional;
import java.util.Set;
import java.util.SortedSet;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang.StringUtils;
import org.fenixedu.academic.domain.Attends;
import org.fenixedu.academic.domain.CompetenceCourse;
import org.fenixedu.academic.domain.CurricularCourse;
import org.fenixedu.academic.domain.Degree;
import org.fenixedu.academic.domain.DomainObjectUtil;
import org.fenixedu.academic.domain.Enrolment;
import org.fenixedu.academic.domain.EnrolmentEvaluation;
import org.fenixedu.academic.domain.EvaluationSeason;
import org.fenixedu.academic.domain.ExecutionCourse;
import org.fenixedu.academic.domain.ExecutionDegree;
import org.fenixedu.academic.domain.ExecutionSemester;
import org.fenixedu.academic.domain.ExecutionYear;
import org.fenixedu.academic.domain.GradeScale;
import org.fenixedu.academic.domain.Person;
import org.fenixedu.academic.domain.Professorship;
import org.fenixedu.academic.domain.Shift;
import org.fenixedu.academic.domain.degree.DegreeType;
import org.fenixedu.academic.domain.student.Registration;
import org.fenixedu.academic.domain.studentCurriculum.CurriculumModule;
import org.fenixedu.academic.util.EnrolmentEvaluationState;
import org.fenixedu.bennu.core.security.Authenticate;
import org.fenixedu.commons.i18n.I18N;
import org.fenixedu.ulisboa.specifications.domain.evaluation.EvaluationComparator;
import org.fenixedu.ulisboa.specifications.domain.evaluation.season.EvaluationSeasonPeriod;
import org.fenixedu.ulisboa.specifications.domain.evaluation.season.EvaluationSeasonPeriod.EvaluationSeasonPeriodType;
import org.fenixedu.ulisboa.specifications.domain.evaluation.season.EvaluationSeasonServices;
import org.fenixedu.ulisboa.specifications.domain.evaluation.season.rule.EvaluationSeasonRule;
import org.fenixedu.ulisboa.specifications.domain.evaluation.season.rule.GradeScaleValidator;
import org.fenixedu.ulisboa.specifications.domain.exceptions.ULisboaSpecificationsDomainException;
import org.fenixedu.ulisboa.specifications.domain.services.CurriculumLineServices;
import org.fenixedu.ulisboa.specifications.domain.services.enrollment.EnrolmentServices;
import org.fenixedu.ulisboa.specifications.domain.services.evaluation.EnrolmentEvaluationServices;
import org.fenixedu.ulisboa.specifications.domain.studentCurriculum.CurriculumAggregatorEntry;
import org.fenixedu.ulisboa.specifications.domain.studentCurriculum.CurriculumAggregatorServices;
import org.joda.time.DateTime;
import org.joda.time.LocalDate;
import org.joda.time.YearMonthDay;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.collect.Sets;

import pt.ist.fenixframework.Atomic;

public class CompetenceCourseMarkSheet extends CompetenceCourseMarkSheet_Base {

    static public Comparator<String> COMPARATOR_FOR_STUDENT_NAME = new Comparator<String>() {

        @Override
        public int compare(final String o1, final String o2) {
            return collator.get().compare(normalize(o1), normalize(o2));
        }

        final Supplier<Collator> collator = () -> {
            final Collator result = Collator.getInstance(I18N.getLocale());
            return result;
        };

        private String normalize(final String input) {
            return input == null ? "" : input.replaceAll("\\s+", "").toUpperCase();
        }
    };

    static final private Logger logger = LoggerFactory.getLogger(CompetenceCourseMarkSheet.class);

    protected CompetenceCourseMarkSheet() {
        super();
    }

    protected void init(final ExecutionSemester executionSemester, final CompetenceCourse competenceCourse,
            final ExecutionCourse executionCourse, final EvaluationSeason evaluationSeason,
            final LocalDate evaluationDate, GradeScale gradeScale, final Person certifier, final Set<Shift> shifts,
            final LocalDate expireDate) {

        setExecutionSemester(executionSemester);
        setCompetenceCourse(competenceCourse);
        setExecutionCourse(executionCourse);
        setEvaluationSeason(evaluationSeason);
        setEvaluationDate(evaluationDate);
        setGradeScale(gradeScale);
        setCertifier(certifier);
        getShiftSet().addAll(shifts);
        setExpireDate(expireDate);
        checkRules();
    }

    private void checkRules() {

        if (getExecutionSemester() == null) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.executionSemester.required");
        }

        if (getCompetenceCourse() == null) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.competenceCourse.required");
        }

        if (getExecutionCourse() == null) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.executionCourse.required");
        }

        if (getEvaluationSeason() == null) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.evaluationSeason.required");
        }

        if (getEvaluationDate() == null) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.evaluationDate.required");
        }

        if (getCertifier() == null) {
            throw new ULisboaSpecificationsDomainException("error.CompetenceCourseMarkSheet.certifier.required");
        }

        if (getGradeScale() == null) {
            throw new ULisboaSpecificationsDomainException("error.CompetenceCourseMarkSheet.gradeScale.required");
        }

        for (final EnrolmentEvaluation enrolmentEvaluation : getEnrolmentEvaluationSet()) {
            if (enrolmentEvaluation.getGradeScale() != getGradeScale()) {
                throw new ULisboaSpecificationsDomainException(
                        "error.CompetenceCourseMarkSheet.marksheet.already.contains.evaluations.with.another.grade.scale");
            }
        }

        if (getEnrolmentEvaluationSet().isEmpty() && getExecutionCourseEnrolmentsNotInAnyMarkSheet().isEmpty()) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.no.enrolments.found.for.grade.submission");
        }

        checkIfEvaluationDateIsInExamsPeriod();
        checkIfEvaluationsDateIsEqualToMarkSheetEvaluationDate();
    }

    private void checkIfEvaluationDateIsInExamsPeriod() {
        final Set<EvaluationSeasonPeriod> periods = getExamsPeriods();

        if (periods.isEmpty()) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.evaluationDateNotInExamsPeriod.undefined",
                    EvaluationSeasonServices.getDescriptionI18N(getEvaluationSeason()).getContent(),
                    getExecutionSemester().getQualifiedName());
        }

        for (final EvaluationSeasonPeriod iter : periods) {
            if (iter.isContainingDate(getEvaluationDate())) {
                return;
            }
        }

        throw new ULisboaSpecificationsDomainException(
                "error.CompetenceCourseMarkSheet.evaluationDateNotInExamsPeriod", getEvaluationDate().toString(),
                EvaluationSeasonPeriod.getIntervalsDescription(periods));
    }

    protected void checkIfIsGradeSubmissionAvailable() {

        if (getExpireDate() != null) {

            if (getExpireDate().isBefore(new LocalDate())) {
                throw new ULisboaSpecificationsDomainException(
                        "error.CompetenceCourseMarkSheet.notInGradeSubmissionPeriod.expired");
            }

            return;
        }

        final Set<EvaluationSeasonPeriod> periods = getGradeSubmissionPeriods();

        if (periods.isEmpty()) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.notInGradeSubmissionPeriod.undefined",
                    EvaluationSeasonServices.getDescriptionI18N(getEvaluationSeason()).getContent(),
                    getExecutionSemester().getQualifiedName());
        }

        for (final EvaluationSeasonPeriod iter : periods) {

            if (iter.isContainingDate(new LocalDate())) {
                return;
            }
        }

        throw new ULisboaSpecificationsDomainException("error.CompetenceCourseMarkSheet.notInGradeSubmissionPeriod",
                EvaluationSeasonPeriod.getIntervalsDescription(periods));
    }

    private void checkIfEvaluationsDateIsEqualToMarkSheetEvaluationDate() {
        for (final EnrolmentEvaluation iter : getEnrolmentEvaluationSet()) {
            if (!iter.getExamDateYearMonthDay().toLocalDate().isEqual(getEvaluationDate())) {
                throw new ULisboaSpecificationsDomainException(
                        "error.CompetenceCourseMarkSheet.evaluations.examDate.must.be.equal.marksheet.evaluationDate");
            }
        }
    }

    @Atomic
    public void edit(final LocalDate evaluationDate, final GradeScale gradeScale, final Person certifier,
            final LocalDate expireDate) {

        if (!isEdition()) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.markSheet.can.only.be.updated.in.edition.state");
        }

        getEnrolmentEvaluationSet().forEach(e -> {
            e.setExamDateYearMonthDay(
                    evaluationDate == null ? null : evaluationDate.toDateTimeAtStartOfDay().toYearMonthDay());
            e.setPersonResponsibleForGrade(certifier);
        });

        init(getExecutionSemester(), getCompetenceCourse(), getExecutionCourse(), getEvaluationSeason(),
                evaluationDate, gradeScale, certifier, getShiftSet(), expireDate);

        checkRules();
    }

    void editExpireDate(LocalDate expireDate) {
        super.setExpireDate(expireDate);
        checkRules();
    }

    @Override
    protected void checkForDeletionBlockers(Collection<String> blockers) {
        super.checkForDeletionBlockers(blockers);
    }

    @Atomic
    public void delete() {

        if (!isEdition()) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.markSheet.can.only.be.deleted.in.edition.state");
        }

        setExecutionSemester(null);
        setCompetenceCourse(null);
        setExecutionCourse(null);
        setEvaluationSeason(null);
        setCertifier(null);
        getShiftSet().clear();
        getEnrolmentEvaluationSet().clear();

        final Iterator<CompetenceCourseMarkSheetStateChange> stateIterator = getStateChangeSet().iterator();
        while (stateIterator.hasNext()) {
            final CompetenceCourseMarkSheetStateChange stateChange = stateIterator.next();
            stateIterator.remove();
            stateChange.delete();
        }

        final Iterator<CompetenceCourseMarkSheetChangeRequest> changeRequestIterator = getChangeRequestsSet()
                .iterator();
        while (changeRequestIterator.hasNext()) {
            final CompetenceCourseMarkSheetChangeRequest changeRequest = changeRequestIterator.next();
            changeRequestIterator.remove();
            changeRequest.delete();
        }

        ULisboaSpecificationsDomainException.throwWhenDeleteBlocked(getDeletionBlockers());
        deleteDomainObject();
    }

    public Set<EvaluationSeasonPeriod> getGradeSubmissionPeriods() {
        return getEvaluationSeasonPeriods(EvaluationSeasonPeriodType.GRADE_SUBMISSION);
    }

    public Set<EvaluationSeasonPeriod> getExamsPeriods() {
        return getEvaluationSeasonPeriods(EvaluationSeasonPeriodType.EXAMS);
    }

    private Set<EvaluationSeasonPeriod> getEvaluationSeasonPeriods(final EvaluationSeasonPeriodType periodType) {
        final Set<EvaluationSeasonPeriod> result = Sets.<EvaluationSeasonPeriod>newHashSet();

        for (final EvaluationSeasonPeriod iter : getExecutionSemester().getEvaluationSeasonPeriodSet()) {
            if (iter.getPeriodType() == periodType && iter.getSeason() == getEvaluationSeason()
                    && !Sets.intersection(iter.getExecutionDegrees(), getExecutionDegrees()).isEmpty()) {
                result.add(iter);
            }
        }

        return result;
    }

    private Set<ExecutionDegree> getExecutionDegrees() {
        return getExecutionCourse().getAssociatedCurricularCoursesSet().stream()
                .map(i -> i.getExecutionDegreeFor(getExecutionSemester().getAcademicInterval()))
                .collect(Collectors.toSet());
    }

    // @formatter: off
    /************
     * SERVICES
     ************/
    // @formatter: on

    @Atomic
    public static CompetenceCourseMarkSheet create(final ExecutionSemester executionSemester,
            final CompetenceCourse competenceCourse, final ExecutionCourse executionCourse,
            final EvaluationSeason evaluationSeason, final LocalDate evaluationDate, final Person certifier,
            final Set<Shift> shifts, final boolean byTeacher) {

        final CompetenceCourseMarkSheet result = new CompetenceCourseMarkSheet();
        result.init(executionSemester, competenceCourse, executionCourse, evaluationSeason, evaluationDate,
                GradeScale.TYPE20, certifier, shifts, null);
        CompetenceCourseMarkSheetStateChange.createEditionState(result, byTeacher, null);
        return result;
    }

    public static Stream<CompetenceCourseMarkSheet> findBy(final ExecutionCourse executionCourse) {

        final Set<CompetenceCourseMarkSheet> result = Sets.newHashSet();

        if (executionCourse != null) {

            for (final CurricularCourse curricularCourse : executionCourse.getAssociatedCurricularCoursesSet()) {
                result.addAll(curricularCourse.getCompetenceCourse().getCompetenceCourseMarkSheetSet());
            }
        }

        return result.stream().filter(c -> c.getExecutionSemester() == executionCourse.getExecutionPeriod());
    }

    public static Stream<CompetenceCourseMarkSheet> findBy(final ExecutionSemester executionSemester,
            final CompetenceCourse competenceCourse, final CompetenceCourseMarkSheetStateEnum markSheetState,
            final EvaluationSeason evaluationSeason,
            final CompetenceCourseMarkSheetChangeRequestStateEnum changeRequestState) {

        final Set<CompetenceCourseMarkSheet> result = Sets.newHashSet();
        if (executionSemester != null) {
            result.addAll(executionSemester.getCompetenceCourseMarkSheetSet());
        }

        return result.stream()

                .filter(c -> competenceCourse == null || c.getCompetenceCourse() == competenceCourse)

                .filter(c -> markSheetState == null || c.isInState(markSheetState))

                .filter(c -> evaluationSeason == null || c.getEvaluationSeason() == evaluationSeason)

                .filter(c -> changeRequestState == null
                        || c.getChangeRequestsSet().stream().anyMatch(r -> r.getState() == changeRequestState));
    }

    private CompetenceCourseMarkSheetStateChange getFirstStateChange() {
        return getStateChangeSet().stream().min(CompetenceCourseMarkSheetStateChange::compareTo).get();
    }

    private CompetenceCourseMarkSheetStateChange getStateChange() {
        return getStateChangeSet().stream().max(CompetenceCourseMarkSheetStateChange::compareTo).get();
    }

    public boolean isEdition() {
        return getStateChange().isEdition();
    }

    public boolean isSubmitted() {
        return getStateChange().isSubmitted();
    }

    public boolean isConfirmed() {
        return getStateChange().isConfirmed();
    }

    public DateTime getCreationDate() {
        return getFirstStateChange().getDate();
    }

    public Person getCreator() {
        return getFirstStateChange().getResponsible();
    }

    public String getState() {
        return getStateChange().getState().getDescriptionI18N().getContent();
    }

    public DateTime getStateDate() {
        return getStateChange().getDate();
    }

    @Atomic
    public void markAsPrinted() {
        super.setPrinted(true);
    }

    public ExecutionYear getExecutionYear() {
        return getExecutionSemester().getExecutionYear();
    }

    public String getShiftsDescription() {
        return getShiftSet().stream().map(i -> i.getNome()).collect(Collectors.joining(", "));
    }

    /**
     * Does one final test for final vs temporary evaluations
     * Possibly redundant with isEnrolmentCandidateForEvaluation
     * 
     * @see {@link com.qubit.qubEdu.module.academicOffice.presentation.actions.teacher.MarkSheetTeacherManagementDispatchAction.getEnrolmentsNotInAnyMarkSheet}
     */
    static private Set<Enrolment> getEnrolmentsNotInAnyMarkSheet(final ExecutionSemester semester,
            final CompetenceCourse competence, final ExecutionCourse execution,
            final EvaluationSeason evaluationSeason, final LocalDate evaluationDate, final Set<Shift> shifts) {

        final Set<Enrolment> result = Sets.newHashSet();

        for (final Enrolment enrolment : filterEnrolmentsForGradeSubmission(
                collectEnrolmentsForGradeSubmission(semester, competence, execution), semester, evaluationSeason,
                evaluationDate, shifts)) {

            final Optional<EnrolmentEvaluation> finalEvaluation = enrolment.getEnrolmentEvaluation(evaluationSeason,
                    semester, true);
            if (finalEvaluation.isPresent()) {
                continue;
            }

            final Optional<EnrolmentEvaluation> temporaryEvaluation = enrolment
                    .getEnrolmentEvaluation(evaluationSeason, semester, false);
            if (temporaryEvaluation.isPresent()
                    && temporaryEvaluation.get().getCompetenceCourseMarkSheet() != null) {
                continue;
            }

            result.add(enrolment);
        }

        return result;
    }

    /**
     * Collects enrolments based on mark sheet parameters (competence, semester, execution course)
     * Does not deal with anual competence courses, this is done in getExecutionCourseEnrolmentsNotInAnyMarkSheet
     */
    static private Set<Enrolment> collectEnrolmentsForGradeSubmission(final ExecutionSemester semester,
            final CompetenceCourse competence, final ExecutionCourse execution) {

        final Set<Enrolment> result = Sets.newHashSet();

        for (final ExecutionCourse iter : competence.getExecutionCoursesByExecutionPeriod(semester)) {

            if (execution == null || iter == execution) {

                for (final CurricularCourse curricularCourse : iter.getAssociatedCurricularCoursesSet()) {

                    for (final CurriculumModule curriculumModule : curricularCourse.getCurriculumModulesSet()) {

                        if (!curriculumModule.isEnrolment()) {
                            continue;
                        }

                        result.add((Enrolment) curriculumModule);
                    }
                }

                for (final Attends attends : iter.getAttendsSet()) {
                    final Enrolment enrolment = attends.getEnrolment();
                    if (enrolment != null) {
                        result.add(enrolment);
                    }
                }
            }
        }

        return result;
    }

    /**
     * Algorithm entry point
     * Deals with anual competence courses
     */
    public Set<Enrolment> getExecutionCourseEnrolmentsNotInAnyMarkSheet() {
        return getExecutionCourseEnrolmentsNotInAnyMarkSheet(getExecutionSemester(), getCompetenceCourse(),
                getExecutionCourse(), getEvaluationSeason(), getEvaluationDate(), getShiftSet());
    }

    /**
     * We need static services for report purposes
     */
    static public Set<Enrolment> getExecutionCourseEnrolmentsNotInAnyMarkSheet(final ExecutionSemester semester,
            final CompetenceCourse competence, final ExecutionCourse execution, final EvaluationSeason season,
            final LocalDate evaluationDate, final Set<Shift> shifts) {

        final Set<Enrolment> result = Sets.newHashSet();

        for (final Enrolment enrolment : getEnrolmentsNotInAnyMarkSheet(semester, competence, execution, season,
                evaluationDate, shifts)) {

            if (competence.isAnual() && semester == semester.getExecutionYear().getLastExecutionPeriod()) {

                final ExecutionCourse otherExecutionCourse = enrolment
                        .getExecutionCourseFor(semester.getExecutionYear().getFirstExecutionPeriod());
                if (otherExecutionCourse != null && otherExecutionCourse.getAssociatedCurricularCoursesSet()
                        .containsAll(execution.getAssociatedCurricularCoursesSet())) {

                    if (enrolment.getAttendsByExecutionCourse(otherExecutionCourse) != null) {
                        result.add(enrolment);
                    }
                }

            } else {
                if (enrolment.getAttendsByExecutionCourse(execution) != null) {
                    result.add(enrolment);
                }
            }
        }

        return result;
    }

    /**
     * @see {@link com.qubit.qubEdu.module.academicOffice.domain.evaluation.EvaluationSeason.getEnrolmentsForGradeSubmission}
     */
    static private Set<Enrolment> filterEnrolmentsForGradeSubmission(final Set<Enrolment> enrolments,
            final ExecutionSemester semester, final EvaluationSeason season, final LocalDate evaluationDate,
            final Set<Shift> shifts) {

        final Set<Enrolment> result = Sets.newHashSet();

        for (final Enrolment enrolment : enrolments) {

            if (enrolment.isAnnulled()) {
                continue;
            }

            if (!EvaluationSeasonServices.isDifferentEvaluationSemesterAccepted(season)
                    && !enrolment.isValid(semester)) {
                continue;
            }

            if (!isEnrolmentCandidateForEvaluation(enrolment, evaluationDate, semester, season)) {
                continue;
            }

            if (!shifts.isEmpty() && !EnrolmentServices.containsAnyShift(enrolment, semester, shifts)) {
                continue;
            }

            final Optional<EnrolmentEvaluation> evaluation = enrolment.getEnrolmentEvaluation(season, semester,
                    false);
            if (evaluation.isPresent()
                    && (evaluation.get().getCompetenceCourseMarkSheet() != null || evaluation.get().isAnnuled())) {
                continue;
            }

            result.add(enrolment);
        }

        return result;
    }

    /**
     * Filters according to evaluation season
     */
    static private boolean isEnrolmentCandidateForEvaluation(final Enrolment enrolment,
            final LocalDate evaluationDate, final ExecutionSemester semester, final EvaluationSeason season) {

        if (enrolment.isEvaluatedInSeason(season, semester)) {
            return false;
        }

        // only evaluations before the input evaluation date should be investigated
        final Collection<EnrolmentEvaluation> evaluations = getAllFinalEnrolmentEvaluations(enrolment,
                evaluationDate);
        final EnrolmentEvaluation latestEvaluation = getLatestEnrolmentEvaluation(evaluations);
        final boolean isApproved = latestEvaluation != null && latestEvaluation.isApproved();

        // this evaluation season is for not approved enrolments
        if (!EvaluationSeasonServices.isForApprovedEnrolments(season) && isApproved) {
            return false;
        }

        // this evaluation season is for approved enrolments
        if (EvaluationSeasonServices.isForApprovedEnrolments(season) && !isApproved) {
            return false;
        }

        if (EvaluationSeasonServices.isBlockingTreasuryEventInDebt(season, enrolment, semester)) {
            return false;
        }

        if (EvaluationSeasonServices.hasPreviousSeasonBlockingGrade(season, latestEvaluation)) {
            return false;
        }

        if (!EvaluationSeasonServices.hasRequiredPreviousSeasonMinimumGrade(season, evaluations)) {
            return false;
        }

        if (EvaluationSeasonServices.isRequiredPreviousSeasonEvaluation(season)) {
            if (latestEvaluation == null) {
                return false;
            }

            boolean exclude = true;

            final EvaluationSeason previousSeason = EvaluationSeasonServices.getPreviousSeason(season);
            if (previousSeason != null) {

                // WARNING: we should be using EnrolmentEvaluation.find API, but for simplicity reasons we're dealing with this "manually" 
                for (final EnrolmentEvaluation iter : evaluations) {
                    if (iter.getEvaluationSeason() == previousSeason) {
                        exclude = false;
                        break;
                    }
                }
            }

            if (exclude) {
                return false;
            }
        }

        if (!CurriculumAggregatorServices.isCandidateForEvaluation(season, enrolment)) {
            return false;
        }

        final Optional<EnrolmentEvaluation> temporaryEvaluation = enrolment.getEnrolmentEvaluation(season, semester,
                false);

        if (EvaluationSeasonServices.isDifferentEvaluationSemesterAccepted(season)
                && temporaryEvaluation.isPresent()) {
            return temporaryEvaluation.get().getExecutionPeriod() == semester;
        }

        return !EvaluationSeasonServices.isRequiredEnrolmentEvaluation(season) || temporaryEvaluation.isPresent();
    }

    /**
     * Returns final evaluations that took place before the evaluation date
     */
    static private Collection<EnrolmentEvaluation> getAllFinalEnrolmentEvaluations(final Enrolment enrolment,
            final LocalDate evaluationDate) {

        final Collection<EnrolmentEvaluation> evaluations = enrolment.getAllFinalEnrolmentEvaluations();

        if (evaluationDate != null) {
            for (final Iterator<EnrolmentEvaluation> iterator = evaluations.iterator(); iterator.hasNext();) {
                final EnrolmentEvaluation enrolmentEvaluation = iterator.next();

                final YearMonthDay examDate = enrolmentEvaluation.getExamDateYearMonthDay();
                if (examDate != null && !examDate.isBefore(evaluationDate)) {
                    iterator.remove();
                }
            }
        }

        return evaluations;
    }

    static private EnrolmentEvaluation getLatestEnrolmentEvaluation(
            final Collection<EnrolmentEvaluation> evaluations) {
        return ((evaluations == null || evaluations.isEmpty()) ? null
                : Collections.<EnrolmentEvaluation>max(evaluations, new EvaluationComparator()));
    }

    public boolean isGradeValueAccepted(final String gradeValue) {

        if (StringUtils.isNotBlank(gradeValue)) {

            final GradeScaleValidator validator = getGradeScaleValidator();
            if (validator == null) {
                return getGradeScale().belongsTo(gradeValue);
            } else {
                return validator.isGradeValueAccepted(gradeValue);
            }
        }

        return false;
    }

    public String getGradeScaleDescription() {
        final GradeScaleValidator validator = getGradeScaleValidator();
        return validator == null ? getGradeScale().getDescription() : validator.getRuleDescription().getContent();
    }

    public GradeScaleValidator getGradeScaleValidator() {
        final SortedSet<GradeScaleValidator> result = Sets.newTreeSet(DomainObjectUtil.COMPARATOR_BY_ID);

        for (final GradeScaleValidator validator : EvaluationSeasonRule.find(getEvaluationSeason(),
                GradeScaleValidator.class)) {

            if (validator.getGradeScale() != getGradeScale()) {
                continue;
            }

            final Set<DegreeType> markSheetDegreeTypes = getExecutionCourse().getAssociatedCurricularCoursesSet()
                    .stream().map(c -> c.getDegree().getDegreeType()).collect(Collectors.toSet());
            if (Sets.intersection(markSheetDegreeTypes, validator.getDegreeTypeSet()).isEmpty()) {
                continue;
            }

            if (!validator.getAppliesToCurriculumAggregatorEntry()
                    || !isCurriculumAggregatorEntryScaleConsistent()) {
                continue;
            }

            result.add(validator);
        }

        if (result.size() > 1) {
            logger.warn("Mark sheet {} has more than one GradeScaleValidator configured, returning the oldest",
                    this);
        }

        return result.isEmpty() ? null : result.first();
    }

    private boolean isCurriculumAggregatorEntryScaleConsistent() {
        return CurriculumAggregatorServices.isAggregationsActive(getExecutionYear())
                && getCurriculumAggregatorEntryScale() != null;
    }

    private Integer getCurriculumAggregatorEntryScale() {
        Integer result = null;

        final Set<CurriculumAggregatorEntry> entries = getExecutionCourse().getAssociatedCurricularCoursesSet()
                .stream().map(i -> CurriculumAggregatorServices.getContext(i, getExecutionYear()))
                .map(y -> y.getCurriculumAggregatorEntry()).collect(Collectors.toSet());

        if (!entries.isEmpty()) {
            final int temp = entries.iterator().next().getGradeValueScale();

            if (entries.stream().allMatch(i -> i.getGradeValueScale() == temp)) {
                result = temp;
            } else {
                logger.warn(
                        "Mark sheet {} has more than one GradeValueScale configured, cannot filter GradeScaleValidator",
                        this);
            }
        }

        return result;
    }

    @Atomic
    public void confirm(boolean byTeacher) {

        if (!isSubmitted()) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.must.be.submitted.to.confirm");
        }

        if (getEnrolmentEvaluationSet().isEmpty()) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.enrolmentEvaluations.required.to.confirm.markSheet");
        }

        if (!EvaluationSeasonServices.isSupportsTeacherConfirmation(getEvaluationSeason()) && byTeacher) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.unauthorized.teacher.confirmation",
                    getEvaluationSeason().getName().getContent());
        }

        for (final EnrolmentEvaluation evaluation : getEnrolmentEvaluationSet()) {
            //TODO: force evaluation checksum generation
            evaluation.setEnrolmentEvaluationState(EnrolmentEvaluationState.FINAL_OBJ);
            evaluation.setPerson(Authenticate.getUser().getPerson());
            evaluation.setWhenDateTime(new DateTime());
            evaluation.setGradeAvailableDateYearMonthDay(new YearMonthDay());
            EnrolmentServices.updateState(evaluation.getEnrolment());
            CurriculumLineServices.updateAggregatorEvaluation(evaluation.getEnrolment());
            EnrolmentEvaluationServices.onStateChange(evaluation);
        }

        CompetenceCourseMarkSheetStateChange.createConfirmedState(this, byTeacher, null);
    }

    @Atomic
    public void submit(boolean byTeacher) {

        if (!isEdition()) {
            throw new ULisboaSpecificationsDomainException(
                    "error.CompetenceCourseMarkSheet.must.be.edition.to.confirm");
        }

        final CompetenceCourseMarkSheetStateChange stateChange = CompetenceCourseMarkSheetStateChange
                .createSubmitedState(this, byTeacher, null);

        final CompetenceCourseMarkSheetSnapshot snapshot = CompetenceCourseMarkSheetSnapshot.create(stateChange,
                getCompetenceCourse().getCode(), getCompetenceCourse().getNameI18N().toLocalizedString(),
                getExecutionSemester().getQualifiedName(), getEvaluationSeason().getName(),
                getCertifier().getName(), getEvaluationDate());

        for (final EnrolmentEvaluation evaluation : getSortedEnrolmentEvaluations()) {
            final Registration registration = evaluation.getRegistration();
            final Degree degree = evaluation.getStudentCurricularPlan().getDegree();
            snapshot.addEntry(registration.getNumber(), registration.getName(), evaluation.getGrade(),
                    degree.getCode(), degree.getNameI18N().toLocalizedString(), EnrolmentServices
                            .getShiftsDescription(evaluation.getEnrolment(), evaluation.getExecutionPeriod()));
        }

        snapshot.finalize();
    }

    @Atomic
    public void revertToEdition(boolean byTeacher, String reason) {

        if (isEdition()) {
            throw new ULisboaSpecificationsDomainException("error.CompetenceCourseMarkSheet.already.in.edition");
        }

        for (final EnrolmentEvaluation evaluation : getEnrolmentEvaluationSet()) {
            evaluation.setEnrolmentEvaluationState(EnrolmentEvaluationState.TEMPORARY_OBJ);
            evaluation.setGradeAvailableDateYearMonthDay((YearMonthDay) null);
            EnrolmentServices.updateState(evaluation.getEnrolment());
            CurriculumLineServices.updateAggregatorEvaluation(evaluation.getEnrolment());
            EnrolmentEvaluationServices.onStateChange(evaluation);
        }

        CompetenceCourseMarkSheetStateChange.createEditionState(this, byTeacher, reason);
    }

    public String getCheckSum() {
        if (isEdition()) {
            return null;
        }

        return getLastSnapshot().get().getCheckSum();
    }

    public String getFormattedCheckSum() {
        if (isEdition()) {
            return null;
        }

        return getLastSnapshot().get().getFormattedCheckSum();
    }

    public SortedSet<EnrolmentEvaluation> getSortedEnrolmentEvaluations() {

        final Comparator<EnrolmentEvaluation> byStudentName = (x,
                y) -> CompetenceCourseMarkSheet.COMPARATOR_FOR_STUDENT_NAME.compare(
                        x.getRegistration().getStudent().getName(), y.getRegistration().getStudent().getName());

        final SortedSet<EnrolmentEvaluation> result = Sets
                .newTreeSet(byStudentName.thenComparing(DomainObjectUtil.COMPARATOR_BY_ID));

        result.addAll(getEnrolmentEvaluationSet());

        return result;

    }

    private Optional<CompetenceCourseMarkSheetStateChange> getLastStateBy(CompetenceCourseMarkSheetStateEnum type) {
        return getStateChangeSet().stream().filter(s -> s.getState() == type)
                .max(CompetenceCourseMarkSheetStateChange::compareTo);

    }

    public Optional<CompetenceCourseMarkSheetSnapshot> getLastSnapshot() {
        final Optional<CompetenceCourseMarkSheetStateChange> lastStateChange = getLastStateBy(
                CompetenceCourseMarkSheetStateEnum.findSubmited());

        return Optional.of(lastStateChange.isPresent() ? lastStateChange.get().getSnapshot() : null);
    }

    public Collection<CompetenceCourseMarkSheetSnapshot> getSnapshots() {
        return getStateChangeSet().stream()
                .filter(s -> s.getState() == CompetenceCourseMarkSheetStateEnum.findSubmited())
                .map(s -> s.getSnapshot()).collect(Collectors.toSet());

    }

    public Collection<CompetenceCourseMarkSheetSnapshot> getPreviousSnapshots() {

        final Collection<CompetenceCourseMarkSheetSnapshot> snapshots = getSnapshots();
        if (snapshots.isEmpty()) {
            return Collections.emptySet();
        }

        final CompetenceCourseMarkSheetSnapshot lastSnapshot = getLastSnapshot().get();
        return snapshots.stream().filter(s -> s != lastSnapshot).collect(Collectors.toSet());

    }

    public boolean isInState(CompetenceCourseMarkSheetStateEnum markSheetState) {
        return getStateChange().getState() == markSheetState;
    }

    public boolean isCertifierExecutionCourseResponsible() {
        final Professorship professorship = getExecutionCourse().getProfessorship(getCertifier());
        return professorship != null && professorship.isResponsibleFor();
    }

    public CompetenceCourseMarkSheetChangeRequest getLastChangeRequest() {
        final Optional<CompetenceCourseMarkSheetChangeRequest> result = getChangeRequestsSet().stream()
                .max(CompetenceCourseMarkSheetChangeRequest.COMPARATOR_BY_REQUEST_DATE);

        return result.isPresent() ? result.get() : null;
    }

    public CompetenceCourseMarkSheetChangeRequest getLastPendingChangeRequest() {
        final Optional<CompetenceCourseMarkSheetChangeRequest> result = getChangeRequestsSet().stream()
                .filter(r -> r.isPending()).max(CompetenceCourseMarkSheetChangeRequest.COMPARATOR_BY_REQUEST_DATE);

        return result.isPresent() ? result.get() : null;

    }

    public SortedSet<CompetenceCourseMarkSheetChangeRequest> getSortedChangeRequests() {

        final SortedSet<CompetenceCourseMarkSheetChangeRequest> result = Sets
                .newTreeSet(CompetenceCourseMarkSheetChangeRequest.COMPARATOR_BY_REQUEST_DATE.reversed());

        result.addAll(getChangeRequestsSet());

        return result;

    }

}