/** * 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.ui.struts.action.academicAdministration; import java.io.Serializable; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.commons.collections.comparators.ReverseComparator; import org.apache.struts.action.ActionForm; import org.apache.struts.action.ActionForward; import org.apache.struts.action.ActionMapping; import org.fenixedu.academic.domain.DegreeCurricularPlan; import org.fenixedu.academic.domain.EmptyDegreeCurricularPlan; import org.fenixedu.academic.domain.EnrolmentInstructions; import org.fenixedu.academic.domain.EnrolmentPeriod; import org.fenixedu.academic.domain.EnrolmentPeriodInClasses; import org.fenixedu.academic.domain.EnrolmentPeriodInClassesMobility; import org.fenixedu.academic.domain.EnrolmentPeriodInCurricularCourses; import org.fenixedu.academic.domain.EnrolmentPeriodInCurricularCoursesFlunkedSeason; import org.fenixedu.academic.domain.EnrolmentPeriodInCurricularCoursesSpecialSeason; import org.fenixedu.academic.domain.EnrolmentPeriodInImprovementOfApprovedEnrolment; import org.fenixedu.academic.domain.EnrolmentPeriodInSpecialSeasonEvaluations; import org.fenixedu.academic.domain.ExecutionDegree; import org.fenixedu.academic.domain.ExecutionSemester; import org.fenixedu.academic.domain.ReingressionPeriod; import org.fenixedu.academic.domain.degree.DegreeType; import org.fenixedu.academic.domain.enrolmentPeriods.EnrolmentPeriodType; import org.fenixedu.academic.predicate.AcademicPredicates; import org.fenixedu.academic.service.services.manager.CreateEnrolmentPeriods; import org.fenixedu.academic.ui.struts.action.academicAdministration.AcademicAdministrationApplication.AcademicAdminExecutionsApp; import org.fenixedu.academic.ui.struts.action.base.FenixDispatchAction; import org.fenixedu.bennu.struts.annotations.Forward; import org.fenixedu.bennu.struts.annotations.Forwards; import org.fenixedu.bennu.struts.annotations.Mapping; import org.fenixedu.bennu.struts.portal.EntryPoint; import org.fenixedu.bennu.struts.portal.StrutsFunctionality; import org.joda.time.DateTime; import org.joda.time.Interval; import pt.ist.fenixWebFramework.renderers.utils.RenderUtils; import pt.ist.fenixframework.Atomic; import pt.ist.fenixframework.FenixFramework; @StrutsFunctionality(app = AcademicAdminExecutionsApp.class, path = "manage-enrolment-periods", titleKey = "title.manage.enrolement.period", accessGroup = "academic(MANAGE_ENROLMENT_PERIODS)") @Mapping(module = "academicAdministration", path = "/manageEnrolementPeriods", input = "/manageEnrolementPeriods.do?method=prepare&page=0", formBean = "enrolementPeriodsForm") @Forwards({ @Forward(name = "editEnrolmentInstructions", path = "/academicAdministration/enrolmentPeriodManagement/editEnrolmentInstructions.jsp"), @Forward(name = "showEnrolementPeriods", path = "/academicAdministration/enrolmentPeriodManagement/enrolementPeriods.jsp"), @Forward(name = "createPeriod", path = "/academicAdministration/enrolmentPeriodManagement/createPeriod.jsp"), @Forward(name = "changePeriodValues", path = "/academicAdministration/enrolmentPeriodManagement/changePeriodValues.jsp") }) public class ManageEnrolementPeriodsDA extends FenixDispatchAction { static List<Class<? extends EnrolmentPeriod>> VALID_ENROLMENT_PERIODS = Arrays.<Class<? extends EnrolmentPeriod>> asList( EnrolmentPeriodInCurricularCourses.class, EnrolmentPeriodInSpecialSeasonEvaluations.class, EnrolmentPeriodInClasses.class, EnrolmentPeriodInClassesMobility.class, EnrolmentPeriodInImprovementOfApprovedEnrolment.class, EnrolmentPeriodInCurricularCoursesSpecialSeason.class, EnrolmentPeriodInCurricularCoursesFlunkedSeason.class, ReingressionPeriod.class); public static class EnrolmentPeriodBean implements Serializable { private ExecutionSemester semester; public EnrolmentPeriodBean() { } public ExecutionSemester getSemester() { return semester; } public void setSemester(ExecutionSemester semester) { this.semester = semester; } public SortedSet<ExecutionSemester> getSemesters() { TreeSet<ExecutionSemester> semesters = new TreeSet<ExecutionSemester>(new ReverseComparator(ExecutionSemester.COMPARATOR_BY_SEMESTER_AND_YEAR)); semesters.addAll(rootDomainObject.getExecutionPeriodsSet()); return semesters; } public List<EnrolmentPeriodTypeConfiguration> getConfigurations() { List<EnrolmentPeriodTypeConfiguration> configurations = new ArrayList<EnrolmentPeriodTypeConfiguration>(); if (semester != null) { Map<Class<? extends EnrolmentPeriod>, EnrolmentPeriodTypeConfiguration> map = new HashMap<Class<? extends EnrolmentPeriod>, EnrolmentPeriodTypeConfiguration>(); for (final EnrolmentPeriod period : semester.getEnrolmentPeriodSet()) { if (VALID_ENROLMENT_PERIODS.contains(period.getClass())) { if (!AcademicPredicates.MANAGE_ENROLMENT_PERIODS.evaluate(period.getDegree())) { continue; } if (!map.containsKey(period.getClass())) { map.put(period.getClass(), new EnrolmentPeriodTypeConfiguration(period.getClass(), semester)); } map.get(period.getClass()).addPeriod(period); } } configurations.addAll(map.values()); Collections.sort(configurations); } return configurations; } } public static class EnrolmentPeriodTypeConfiguration implements Serializable, Comparable<EnrolmentPeriodTypeConfiguration> { protected Class<? extends EnrolmentPeriod> type; protected ExecutionSemester semester; protected Map<Interval, EnrolmentPeriodConfigurationForEdit> configurations = new HashMap<Interval, EnrolmentPeriodConfigurationForEdit>(); public EnrolmentPeriodTypeConfiguration(Class<? extends EnrolmentPeriod> type, ExecutionSemester semester) { this.type = type; this.semester = semester; } public void addPeriod(EnrolmentPeriod period) { if (!configurations.containsKey(period.getInterval())) { configurations.put(period.getInterval(), new EnrolmentPeriodConfigurationForEdit(period.getInterval(), semester)); } configurations.get(period.getInterval()).addPeriod(period); } public Class<? extends EnrolmentPeriod> getType() { return type; } public Collection<EnrolmentPeriodConfigurationForEdit> getConfigurations() { return configurations.values(); } @Override public int compareTo(EnrolmentPeriodTypeConfiguration o) { return getType().getSimpleName().compareTo(o.getType().getSimpleName()); } } public static abstract class AbstractEnrolmentPeriodConfiguration implements Serializable { protected DateTime start; protected DateTime end; protected ExecutionSemester semester; protected List<DegreeCurricularPlan> scope = new ArrayList<DegreeCurricularPlan>(); public AbstractEnrolmentPeriodConfiguration(Interval interval, ExecutionSemester semester) { if (interval != null) { this.start = interval.getStart(); this.end = interval.getEnd(); } this.semester = semester; } public Interval getInterval() { return new Interval(start, end); } public DateTime getStart() { return start; } public void setStart(DateTime start) { this.start = start; } public DateTime getEnd() { return end; } public void setEnd(DateTime end) { this.end = end; } public ExecutionSemester getSemester() { return semester; } public List<DegreeCurricularPlan> getScope() { return scope; } public void setScope(List<DegreeCurricularPlan> scope) { this.scope = scope; } public abstract SortedSet<DegreeCurricularPlan> getPossibleScope(); public abstract void save(); } public static class EnrolmentPeriodConfigurationForCreation extends AbstractEnrolmentPeriodConfiguration { private DegreeType degreeType; private EnrolmentPeriodType type; public EnrolmentPeriodConfigurationForCreation(ExecutionSemester semester) { super(null, semester); } public DegreeType getDegreeType() { return degreeType; } public void setDegreeType(DegreeType degreeType) { this.degreeType = degreeType; } public EnrolmentPeriodType getType() { return type; } public void setType(EnrolmentPeriodType type) { this.type = type; } public List<DegreeType> getDegreeTypes() { return DegreeType.all().sorted().collect(Collectors.toList()); } @Override public SortedSet<DegreeCurricularPlan> getPossibleScope() { SortedSet<DegreeCurricularPlan> possible = new TreeSet<DegreeCurricularPlan>(DegreeCurricularPlan.COMPARATOR_BY_PRESENTATION_NAME); if (degreeType != null && type != null && semester != null) { if (degreeType.isEmpty() && EmptyDegreeCurricularPlan.getInstance() != null) { addIfNotUsedInPeriod(possible, EmptyDegreeCurricularPlan.getInstance()); } else if (degreeType.isBolonhaType()) { for (ExecutionDegree execution : semester.getExecutionYear().getExecutionDegreesByType(degreeType)) { DegreeCurricularPlan dcp = execution.getDegreeCurricularPlan(); addIfNotUsedInPeriod(possible, dcp); } //add curricular plans that still need improvement period and that have transitioned the year before to a new one if (EnrolmentPeriodType.ENROLMENT_PERIOD_IN_IMPROVEMENT_OF_APPROVED_ENROLMENT.equals(type) && semester.getPreviousExecutionPeriod().getExecutionYear() != semester.getExecutionYear()) { for (ExecutionDegree execution : semester.getPreviousExecutionPeriod().getExecutionYear() .getExecutionDegreesByType(degreeType)) { DegreeCurricularPlan dcp = execution.getDegreeCurricularPlan(); if (!possible.contains(dcp)) { addIfNotUsedInPeriod(possible, dcp); } } } } else { for (DegreeCurricularPlan dcp : DegreeCurricularPlan.readPreBolonhaDegreeCurricularPlans()) { addIfNotUsedInPeriod(possible, dcp); } } } return possible; } private void addIfNotUsedInPeriod(SortedSet<DegreeCurricularPlan> possible, DegreeCurricularPlan dcp) { boolean found = false; boolean hasAccess = AcademicPredicates.MANAGE_ENROLMENT_PERIODS.evaluate(dcp.getDegree()); for (EnrolmentPeriod period : dcp.getEnrolmentPeriodsSet()) { if (type.is(period) && period.getExecutionPeriod().equals(semester)) { found = true; break; } } if (!found && hasAccess) { possible.add(dcp); } } @Override public void save() { CreateEnrolmentPeriods.run(semester, degreeType, type, start, end, scope); } } public static class EnrolmentPeriodConfigurationForEdit extends AbstractEnrolmentPeriodConfiguration { protected Set<EnrolmentPeriod> periods = new HashSet<EnrolmentPeriod>(); public EnrolmentPeriodConfigurationForEdit(Interval interval, ExecutionSemester semester) { super(interval, semester); } public void addPeriod(EnrolmentPeriod period) { if (!AcademicPredicates.MANAGE_ENROLMENT_PERIODS.evaluate(period.getDegree())) { return; } periods.add(period); scope.add(period.getDegreeCurricularPlan()); } @Override public SortedSet<DegreeCurricularPlan> getPossibleScope() { SortedSet<DegreeCurricularPlan> possible = new TreeSet<DegreeCurricularPlan>(DegreeCurricularPlan.COMPARATOR_BY_PRESENTATION_NAME); for (EnrolmentPeriod period : periods) { possible.add(period.getDegreeCurricularPlan()); } return possible; } public Collection<String> getDegrees() { List<String> degrees = new ArrayList<String>(); for (EnrolmentPeriod period : periods) { degrees.add(period.getDegree().getPresentationName(semester.getExecutionYear())); } Collections.sort(degrees); return degrees; } public String getPeriodOids() { return periods.stream().map(EnrolmentPeriod::getExternalId).collect(Collectors.joining(":")); } @Override @Atomic public void save() { for (EnrolmentPeriod period : periods) { if (scope.contains(period.getDegreeCurricularPlan())) { period.setStartDateDateTime(start); period.setEndDateDateTime(end); } } } } @EntryPoint public ActionForward prepare(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { ExecutionSemester semester = getDomainObject(request, "semester"); EnrolmentPeriodBean bean = new EnrolmentPeriodBean(); if (semester != null) { bean.setSemester(semester); } request.setAttribute("executionSemester", bean); return mapping.findForward("showEnrolementPeriods"); } public ActionForward selectSemester(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { EnrolmentPeriodBean bean = getRenderedObject("executionSemester"); RenderUtils.invalidateViewState(); request.setAttribute("executionSemester", bean); return mapping.findForward("showEnrolementPeriods"); } public ActionForward prepareEditEnrolmentInstructions(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { ExecutionSemester semester = getDomainObject(request, "semester"); EnrolmentInstructions.createIfNecessary(semester); request.setAttribute("executionSemester", semester); return mapping.findForward("editEnrolmentInstructions"); } public ActionForward prepareChangePeriodValues(final ActionMapping mapping, final ActionForm form, final HttpServletRequest request, final HttpServletResponse response) { ExecutionSemester semester = getDomainObject(request, "semester"); String periodOids = request.getParameter("periods"); EnrolmentPeriodConfigurationForEdit conf = null; for (String periodOid : periodOids.split(":")) { EnrolmentPeriod period = FenixFramework.getDomainObject(periodOid); if (conf == null) { conf = new EnrolmentPeriodConfigurationForEdit(period.getInterval(), semester); conf.addPeriod(period); } else if (conf.getInterval().equals(period.getInterval())) { conf.addPeriod(period); } else { // something went wrong, most likely someone changed a date concurrently. we just leave it out of the edit } } request.setAttribute("configuration", conf); return mapping.findForward("changePeriodValues"); } public ActionForward changePeriodValues(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { EnrolmentPeriodConfigurationForEdit configuration = getRenderedObject("configuration"); configuration.save(); EnrolmentPeriodBean bean = new EnrolmentPeriodBean(); bean.setSemester(configuration.getSemester()); request.setAttribute("executionSemester", bean); return mapping.findForward("showEnrolementPeriods"); } public ActionForward prepareCreatePeriod(final ActionMapping mapping, final ActionForm form, HttpServletRequest request, final HttpServletResponse response) { ExecutionSemester semester = getDomainObject(request, "semester"); EnrolmentPeriodConfigurationForCreation conf = new EnrolmentPeriodConfigurationForCreation(semester); request.setAttribute("configuration", conf); return mapping.findForward("createPeriod"); } public ActionForward selectType(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) { EnrolmentPeriodConfigurationForCreation configuration = getRenderedObject("configuration"); RenderUtils.invalidateViewState(); request.setAttribute("configuration", configuration); return mapping.findForward("createPeriod"); } public ActionForward createPeriods(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response) throws Exception { EnrolmentPeriodConfigurationForCreation configuration = getRenderedObject("configuration"); configuration.save(); EnrolmentPeriodBean bean = new EnrolmentPeriodBean(); bean.setSemester(configuration.getSemester()); request.setAttribute("executionSemester", bean); return mapping.findForward("showEnrolementPeriods"); } }