/** * Copyright © 2002 Instituto Superior Técnico * * This file is part of FenixEdu Academic. * * FenixEdu Academic 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 Academic 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 Academic. If not, see <http://www.gnu.org/licenses/>. */ package org.fenixedu.academic.domain.studentCurriculum; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.concurrent.ConcurrentLinkedQueue; import java.util.function.Supplier; import java.util.stream.Collectors; import org.fenixedu.academic.domain.Degree; import org.fenixedu.academic.domain.DegreeCurricularPlan; import org.fenixedu.academic.domain.ExecutionSemester; import org.fenixedu.academic.domain.ExecutionYear; import org.fenixedu.academic.domain.Person; import org.fenixedu.academic.domain.StudentCurricularPlan; import org.fenixedu.academic.domain.accessControl.academicAdministration.AcademicAccessRule; import org.fenixedu.academic.domain.accessControl.academicAdministration.AcademicOperationType; import org.fenixedu.academic.domain.curricularRules.ICurricularRule; import org.fenixedu.academic.domain.curricularRules.executors.RuleResult; import org.fenixedu.academic.domain.curricularRules.executors.ruleExecutors.CurricularRuleLevel; import org.fenixedu.academic.domain.curricularRules.executors.ruleExecutors.EnrolmentResultType; import org.fenixedu.academic.domain.enrolment.EnrolmentContext; import org.fenixedu.academic.domain.enrolment.IDegreeModuleToEvaluate; import org.fenixedu.academic.domain.exceptions.DomainException; import org.fenixedu.academic.domain.exceptions.EnrollmentDomainException; import org.fenixedu.academic.domain.person.RoleType; import org.fenixedu.academic.domain.phd.enrolments.PhdStudentCurricularPlanEnrolmentManager; import org.fenixedu.academic.domain.student.Registration; import org.fenixedu.academic.domain.student.Student; import org.fenixedu.academic.domain.studentCurriculum.StudentCurricularPlanEnrolmentPreConditions.EnrolmentPreConditionResult; import org.fenixedu.bennu.core.groups.Group; import org.slf4j.Logger; import org.slf4j.LoggerFactory; abstract public class StudentCurricularPlanEnrolment { private static Logger logger = LoggerFactory.getLogger(StudentCurricularPlanEnrolment.class); protected EnrolmentContext enrolmentContext; private static ConcurrentLinkedQueue<CurricularCourseEnrollmentCondition> conditions = new ConcurrentLinkedQueue<>(); protected StudentCurricularPlanEnrolment(final EnrolmentContext enrolmentContext) { checkParameters(enrolmentContext); this.enrolmentContext = enrolmentContext; } private void checkParameters(final EnrolmentContext enrolmentContext) { if (enrolmentContext == null) { throw new DomainException("error.StudentCurricularPlanEnrolment.invalid.enrolmentContext"); } if (enrolmentContext.getStudentCurricularPlan() == null) { throw new DomainException("error.StudentCurricularPlanEnrolment.invalid.studentCurricularPlan"); } if (!enrolmentContext.hasResponsiblePerson()) { throw new DomainException("error.StudentCurricularPlanEnrolment.enrolmentContext.invalid.person"); } } final public RuleResult manage() { assertEnrolmentPreConditions(); unEnrol(); addEnroled(); final Map<EnrolmentResultType, List<IDegreeModuleToEvaluate>> degreeModulesToEnrolMap = new HashMap<EnrolmentResultType, List<IDegreeModuleToEvaluate>>(); final RuleResult result = evaluateDegreeModules(degreeModulesToEnrolMap); performEnrolments(degreeModulesToEnrolMap); return result; } protected void assertEnrolmentPreConditions() { checkDebts(); if (isResponsiblePersonAllowedToEnrolStudents() || isResponsibleInternationalRelationOffice()) { assertAcademicAdminOfficePreConditions(); } else if (isResponsiblePersonStudent()) { assertStudentEnrolmentPreConditions(); } else { assertOtherRolesPreConditions(); } for (CurricularCourseEnrollmentCondition condition : conditions) { condition.verify(getStudentCurricularPlan()); } } @FunctionalInterface public static interface CurricularCourseEnrollmentCondition { public void verify(StudentCurricularPlan scp) throws DomainException; } public static void registerCondition(CurricularCourseEnrollmentCondition condition) { conditions.add(condition); } protected void checkDebts() { final EnrolmentPreConditionResult result = StudentCurricularPlanEnrolmentPreConditions.checkDebts(getStudentCurricularPlan()); if (!result.isValid()) { throw new DomainException(result.message(), result.args()); } } protected Person getPerson() { return getStudent().getPerson(); } protected void assertAcademicAdminOfficePreConditions() { checkEnrolmentWithoutRules(); if (updateRegistrationAfterConclusionProcessPermissionEvaluated()) { return; } if (!getRegistration().hasActiveLastState(getExecutionSemester())) { throw new DomainException("error.StudentCurricularPlan.registration.is.not.active.for.semester", getExecutionSemester().getQualifiedName()); } } protected boolean updateRegistrationAfterConclusionProcessPermissionEvaluated() { if (areModifiedCyclesConcluded() || isStudentCurricularPlanConcluded()) { checkUpdateRegistrationAfterConclusion(); return true; } else { return false; } } protected boolean areModifiedCyclesConcluded() { for (final CycleCurriculumGroup curriculumGroup : getModifiedCycles()) { if (curriculumGroup.isConclusionProcessed()) { return true; } } return false; } protected boolean isStudentCurricularPlanConcluded() { return !getStudentCurricularPlan().isEmptyDegree() && getStudentCurricularPlan().isConclusionProcessed(); } protected void checkEnrolmentWithoutRules() { if (isEnrolmentWithoutRules() && !AcademicAccessRule.isProgramAccessibleToFunction(AcademicOperationType.ENROLMENT_WITHOUT_RULES, getStudentCurricularPlan().getDegree(), getResponsiblePerson().getUser()) && !isResponsibleInternationalRelationOffice()) { throw new DomainException("error.permissions.cannot.enrol.without.rules"); } } protected void checkUpdateRegistrationAfterConclusion() { if (!AcademicAccessRule.isProgramAccessibleToFunction(AcademicOperationType.UPDATE_REGISTRATION_AFTER_CONCLUSION, getStudentCurricularPlan().getDegree(), getResponsiblePerson().getUser())) { throw new DomainException("error.permissions.cannot.update.registration.after.conclusion.process"); } } protected Set<CycleCurriculumGroup> getModifiedCycles() { final Set<CycleCurriculumGroup> result = new HashSet<CycleCurriculumGroup>(); for (final CycleCurriculumGroup cycle : getStudentCurricularPlan().getCycleCurriculumGroups()) { if (isRemovingModulesFromCycle(cycle)) { result.add(cycle); break; } if (isEnrolingInCycle(cycle)) { result.add(cycle); break; } } return result; } protected boolean isRemovingModulesFromCycle(final CycleCurriculumGroup cycle) { for (final CurriculumModule module : enrolmentContext.getToRemove()) { if (cycle.hasCurriculumModule(module)) { return true; } } return false; } protected boolean isEnrolingInCycle(final CycleCurriculumGroup cycle) { for (final IDegreeModuleToEvaluate dmte : enrolmentContext.getDegreeModulesToEvaluate()) { if (dmte.isEnroling() && cycle.hasCurriculumModule(dmte.getCurriculumGroup())) { return true; } } return false; } protected void assertStudentEnrolmentPreConditions() { if (!getResponsiblePerson().getStudent().getRegistrationsToEnrolByStudent().contains(getRegistration())) { throw new DomainException("error.StudentCurricularPlan.student.is.not.allowed.to.perform.enrol"); } if (getCurricularRuleLevel() != CurricularRuleLevel.ENROLMENT_WITH_RULES) { throw new DomainException("error.StudentCurricularPlan.invalid.curricular.rule.level"); } final EnrolmentPreConditionResult result = StudentCurricularPlanEnrolmentPreConditions.checkEnrolmentPeriods(getStudentCurricularPlan(), getExecutionSemester()); if (!result.isValid()) { throw new DomainException(result.message(), result.args()); } } protected void assertOtherRolesPreConditions() { throw new DomainException("error.invalid.user"); } private RuleResult evaluateDegreeModules(final Map<EnrolmentResultType, List<IDegreeModuleToEvaluate>> degreeModulesEnrolMap) { RuleResult finalResult = RuleResult.createInitialTrue(); final Map<IDegreeModuleToEvaluate, Set<ICurricularRule>> rulesToEvaluate = getRulesToEvaluate(); for (final Entry<IDegreeModuleToEvaluate, Set<ICurricularRule>> entry : rulesToEvaluate.entrySet()) { RuleResult result = evaluateRules(entry.getKey(), entry.getValue()); finalResult = finalResult.and(result); } finalResult = evaluateExtraRules(finalResult); if (!finalResult.isFalse()) { for (final IDegreeModuleToEvaluate degreeModuleToEvaluate : rulesToEvaluate.keySet()) { addDegreeModuleToEvaluateToMap(degreeModulesEnrolMap, finalResult.getEnrolmentResultTypeFor(degreeModuleToEvaluate.getDegreeModule()), degreeModuleToEvaluate); } } if (finalResult.isFalse()) { throw new EnrollmentDomainException(finalResult); } return finalResult; } protected RuleResult evaluateExtraRules(final RuleResult actualResult) { // no extra rules to be executed return actualResult; } private RuleResult evaluateRules(final IDegreeModuleToEvaluate degreeModuleToEvaluate, final Set<ICurricularRule> curricularRules) { RuleResult ruleResult = RuleResult.createTrue(degreeModuleToEvaluate.getDegreeModule()); for (final ICurricularRule rule : curricularRules) { ruleResult = ruleResult.and(rule.evaluate(degreeModuleToEvaluate, enrolmentContext)); } return ruleResult; } private void addDegreeModuleToEvaluateToMap(final Map<EnrolmentResultType, List<IDegreeModuleToEvaluate>> result, final EnrolmentResultType enrolmentResultType, final IDegreeModuleToEvaluate degreeModuleToEnrol) { List<IDegreeModuleToEvaluate> information = result.get(enrolmentResultType); if (information == null) { result.put(enrolmentResultType, information = new ArrayList<IDegreeModuleToEvaluate>()); } information.add(degreeModuleToEnrol); } protected ExecutionSemester getExecutionSemester() { return enrolmentContext.getExecutionPeriod(); } protected ExecutionYear getExecutionYear() { return getExecutionSemester().getExecutionYear(); } protected StudentCurricularPlan getStudentCurricularPlan() { return enrolmentContext.getStudentCurricularPlan(); } protected Registration getRegistration() { return getStudentCurricularPlan().getRegistration(); } protected RootCurriculumGroup getRoot() { return getStudentCurricularPlan().getRoot(); } protected Student getStudent() { return getRegistration().getStudent(); } protected DegreeCurricularPlan getDegreeCurricularPlan() { return getStudentCurricularPlan().getDegreeCurricularPlan(); } protected CurricularRuleLevel getCurricularRuleLevel() { return enrolmentContext.getCurricularRuleLevel(); } protected Person getResponsiblePerson() { return enrolmentContext.getResponsiblePerson(); } private boolean isEnrolmentWithoutRules() { return enrolmentContext.isEnrolmentWithoutRules(); } @Deprecated protected boolean isResponsiblePersonManager() { return Group.managers().isMember(getResponsiblePerson().getUser()); } // Old AcademicAdminOffice role check protected boolean isResponsiblePersonAllowedToEnrolStudents() { final Degree degree = getStudentCurricularPlan().getDegree(); return AcademicAccessRule .getProgramsAccessibleToFunction(AcademicOperationType.STUDENT_ENROLMENTS, getResponsiblePerson().getUser()) .anyMatch(p -> p == degree); } protected boolean isResponsibleInternationalRelationOffice() { return RoleType.INTERNATIONAL_RELATION_OFFICE.isMember(getResponsiblePerson().getUser()); } protected boolean isResponsiblePersonStudent() { return RoleType.STUDENT.isMember(getResponsiblePerson().getUser()); } protected boolean isResponsiblePersonCoordinator() { return RoleType.COORDINATOR.isMember(getResponsiblePerson().getUser()); } abstract protected void unEnrol(); abstract protected void addEnroled(); abstract protected Map<IDegreeModuleToEvaluate, Set<ICurricularRule>> getRulesToEvaluate(); abstract protected void performEnrolments(Map<EnrolmentResultType, List<IDegreeModuleToEvaluate>> degreeModulesToEnrolMap); // ------------------- // static information // ------------------- static public StudentCurricularPlanEnrolment createManager(final EnrolmentContext enrolmentContext) { return ENROLMENT_MANAGER_FACTORY.get().createManager(enrolmentContext); } public static void setEnrolmentManagerFactory(final Supplier<EnrolmentManagerFactory> input) { if (input != null && input.get() != null) { ENROLMENT_MANAGER_FACTORY = input; } else { logger.error("Could not set factory to null"); } } static private Supplier<EnrolmentManagerFactory> ENROLMENT_MANAGER_FACTORY = () -> new EnrolmentManagerFactory() { public StudentCurricularPlanEnrolment createManager(final EnrolmentContext enrolmentContext) { if (enrolmentContext.isNormal()) { if (enrolmentContext.isPhdDegree()) { return new PhdStudentCurricularPlanEnrolmentManager(enrolmentContext); } else { return new StudentCurricularPlanEnrolmentManager(enrolmentContext); } } else if (enrolmentContext.isImprovement()) { return new StudentCurricularPlanImprovementOfApprovedEnrolmentManager(enrolmentContext); } else if (enrolmentContext.isSpecialSeason()) { return new StudentCurricularPlanEnrolmentInSpecialSeasonEvaluationManager(enrolmentContext); } else if (enrolmentContext.isExtra()) { return new StudentCurricularPlanExtraEnrolmentManager(enrolmentContext); } else if (enrolmentContext.isPropaeudeutics()) { return new StudentCurricularPlanPropaeudeuticsEnrolmentManager(enrolmentContext); } else if (enrolmentContext.isStandalone()) { return new StudentCurricularPlanStandaloneEnrolmentManager(enrolmentContext); } throw new DomainException("StudentCurricularPlanEnrolment"); } }; public static interface EnrolmentManagerFactory { public StudentCurricularPlanEnrolment createManager(final EnrolmentContext enrolmentContext); } }