org.fenixedu.ulisboa.specifications.ui.student.enrolment.ShiftEnrolmentController.java Source code

Java tutorial

Introduction

Here is the source code for org.fenixedu.ulisboa.specifications.ui.student.enrolment.ShiftEnrolmentController.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: shezad.anavarali@qub-it.com
 *
 * 
 * This file is part of FenixEdu fenixedu-ulisboa-specifications.
 *
 * FenixEdu fenixedu-ulisboa-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 fenixedu-ulisboa-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 fenixedu-ulisboa-specifications.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.fenixedu.ulisboa.specifications.ui.student.enrolment;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import org.fenixedu.academic.domain.Attends;
import org.fenixedu.academic.domain.Degree;
import org.fenixedu.academic.domain.Enrolment;
import org.fenixedu.academic.domain.ExecutionCourse;
import org.fenixedu.academic.domain.ExecutionSemester;
import org.fenixedu.academic.domain.Lesson;
import org.fenixedu.academic.domain.SchoolClass;
import org.fenixedu.academic.domain.Shift;
import org.fenixedu.academic.domain.ShiftType;
import org.fenixedu.academic.domain.exceptions.DomainException;
import org.fenixedu.academic.domain.student.Registration;
import org.fenixedu.academic.domain.student.Student;
import org.fenixedu.academic.dto.ShiftToEnrol;
import org.fenixedu.academic.service.services.exceptions.FenixServiceException;
import org.fenixedu.academic.service.services.exceptions.NotAuthorizedException;
import org.fenixedu.bennu.core.i18n.BundleUtil;
import org.fenixedu.bennu.core.security.Authenticate;
import org.fenixedu.bennu.spring.portal.SpringFunctionality;
import org.fenixedu.ulisboa.specifications.domain.curricularRules.StudentSchoolClassCurricularRule;
import org.fenixedu.ulisboa.specifications.domain.enrolmentPeriod.AcademicEnrolmentPeriod;
import org.fenixedu.ulisboa.specifications.domain.services.RegistrationServices;
import org.fenixedu.ulisboa.specifications.domain.services.enrollment.shift.ReadShiftsToEnroll;
import org.fenixedu.ulisboa.specifications.dto.enrolmentperiod.AcademicEnrolmentPeriodBean;
import org.fenixedu.ulisboa.specifications.ui.FenixeduUlisboaSpecificationsBaseController;
import org.fenixedu.ulisboa.specifications.ui.FenixeduUlisboaSpecificationsController;
import org.fenixedu.ulisboa.specifications.ui.student.enrolment.process.EnrolmentProcess;
import org.joda.time.DateTime;
import org.joda.time.Interval;
import org.joda.time.LocalDate;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject;

import pt.ist.fenixframework.Atomic;

/**
 * @author shezad - Aug 11, 2015
 *
 */
@SpringFunctionality(app = FenixeduUlisboaSpecificationsController.class, title = "label.title.shiftEnrolment", accessGroup = "activeStudents")
@RequestMapping("/student/shiftEnrolment")
public class ShiftEnrolmentController extends FenixeduUlisboaSpecificationsBaseController {

    public static final String CONTROLLER_URL = "/student/shiftEnrolment";

    private static final String JSP_PATH = CONTROLLER_URL.substring(1);

    private static PossibleShiftsToEnrolProvider possibleShiftsToEnrolProvider = getPossibleShiftsToEnrolDefaultProvider();

    @FunctionalInterface
    public interface PossibleShiftsToEnrolProvider {
        Collection<Shift> getShifts(ExecutionCourse executionCourse, ShiftType shiftType,
                Registration registration);
    }

    private String jspPage(final String page) {
        return JSP_PATH + "/" + page;
    }

    static public String getEntryPointURL() {
        return SWITCHENROLMENTPERIOD_URL;
    }

    @RequestMapping
    public String home(Model model) {

        Registration selectedRegistration = (Registration) model.asMap().get("registration");
        AcademicEnrolmentPeriod selectedEnrolmentPeriod = (AcademicEnrolmentPeriod) model.asMap()
                .get("enrolmentPeriod");

        final Student student = checkUser(selectedRegistration);
        final List<EnrolmentPeriodDTO> enrolmentBeans = new ArrayList<EnrolmentPeriodDTO>();

        for (final AcademicEnrolmentPeriodBean iter : AcademicEnrolmentPeriod
                .getEnrolmentPeriodsOpenOrUpcoming(student)) {
            if (isValidPeriodForUser(iter)) {

                boolean selected = selectedRegistration == iter.getRegistration()
                        && selectedEnrolmentPeriod == iter.getEnrolmentPeriod();
                enrolmentBeans.add(new EnrolmentPeriodDTO(iter, selected));
            }
        }

        if (!enrolmentBeans.isEmpty()) {
            enrolmentBeans.sort((eb1, eb2) -> eb1.compareTo(eb2));
            if (selectedRegistration == null || selectedEnrolmentPeriod == null) {
                final EnrolmentPeriodDTO selectedEnrolmentBean = enrolmentBeans.iterator().next();
                selectedEnrolmentBean.setSelected(true);
                selectedRegistration = selectedEnrolmentBean.getRegistration();
                selectedEnrolmentPeriod = selectedEnrolmentBean.getEnrolmentPeriod();
            }

            final ExecutionSemester executionSemester = selectedEnrolmentPeriod.getExecutionSemester();

            try {
                final List<ShiftToEnrol> shiftsToEnrol = ReadShiftsToEnroll
                        .readWithStudentRestrictionsForShiftsEnrolments(selectedRegistration, executionSemester);
                shiftsToEnrol.sort(
                        (s1, s2) -> s1.getExecutionCourse().getName().compareTo(s2.getExecutionCourse().getName()));
                model.addAttribute("shiftsToEnrol", shiftsToEnrol);
            } catch (NotAuthorizedException e) {
                addErrorMessage(e.getLocalizedMessage(), model);
            } catch (FenixServiceException e) {
                addErrorMessage(
                        BundleUtil.getString("resources.FenixeduUlisboaSpecificationsResources", e.getMessage()),
                        model);
            } catch (DomainException e) {
                addErrorMessage(e.getLocalizedMessage(), model);
            }
        }

        model.addAttribute("enrolmentBeans", enrolmentBeans);
        if (selectedRegistration != null && selectedEnrolmentPeriod != null) {
            model.addAttribute("enrolmentProcess", EnrolmentProcess.find(
                    selectedEnrolmentPeriod.getExecutionSemester(),
                    selectedRegistration.getStudentCurricularPlan(selectedEnrolmentPeriod.getExecutionYear())));
        }

        checkIfMandatoryShiftsAreEnrolled(enrolmentBeans, model);

        return jspPage("shiftEnrolment");
    }

    private void checkIfMandatoryShiftsAreEnrolled(final List<EnrolmentPeriodDTO> enrolmentBeans, Model model) {
        for (final EnrolmentPeriodDTO enrolmentBean : enrolmentBeans) {
            final ExecutionSemester executionSemester = enrolmentBean.getExecutionSemester();
            final Registration registration = enrolmentBean.getRegistration();
            for (Enrolment enrolment : registration.getEnrolments(executionSemester)) {
                boolean allAvailableShiftsMustBeEnrolled = enrolment.getCurricularRules(executionSemester).stream()
                        .filter(cr -> cr instanceof StudentSchoolClassCurricularRule)
                        .map(cr -> (StudentSchoolClassCurricularRule) cr)
                        .anyMatch(ssccr -> ssccr.getAllAvailableShiftsMustBeEnrolled());

                if (allAvailableShiftsMustBeEnrolled) {
                    final Attends attends = enrolment.getAttendsFor(executionSemester);
                    if (attends != null) {
                        final ExecutionCourse executionCourse = attends.getExecutionCourse();
                        if (executionCourse.getAssociatedShifts().stream().flatMap(s -> s.getSortedTypes().stream())
                                .distinct().anyMatch(st -> registration.getShiftFor(executionCourse, st) == null)) {
                            model.addAttribute("mandatoryShiftsEnrolled", false);
                            addErrorMessage(BundleUtil.getString("resources.FenixeduUlisboaSpecificationsResources",
                                    "message.StudentSchoolClassCurricularRule.allAvailableShiftsMustBeEnrolled.error"),
                                    model);
                            return;
                        }
                    }

                }
            }
        }
        model.addAttribute("mandatoryShiftsEnrolled", true);
    }

    private static final String _SWITCHENROLMENTPERIOD_URI = "/switchEnrolmentPeriod/";
    public static final String SWITCHENROLMENTPERIOD_URL = CONTROLLER_URL + _SWITCHENROLMENTPERIOD_URI;

    @RequestMapping(value = _SWITCHENROLMENTPERIOD_URI + "{registrationOid}/{periodOid}")
    public String switchEnrolmentPeriod(@PathVariable("registrationOid") Registration registration,
            @PathVariable("periodOid") AcademicEnrolmentPeriod enrolmentPeriod, Model model) {

        model.addAttribute("registration", registration);
        model.addAttribute("enrolmentPeriod", enrolmentPeriod);

        return home(model);
    }

    @RequestMapping(value = "possibleShiftsToEnrol.json/{registrationOid}/{executionCourseOid}/{shiftType}")
    public @ResponseBody String getPossibleShiftsToEnrol(@PathVariable("registrationOid") Registration registration,
            @PathVariable("executionCourseOid") ExecutionCourse executionCourse,
            @PathVariable("shiftType") ShiftType shiftType) {

        checkUser(registration);

        final Set<Shift> shifts = new HashSet<>();

        // if student has a school class and it has shifts for EC and type, we only can allow him to add those shifts
        final Optional<SchoolClass> optionalSchoolClass = RegistrationServices.getSchoolClassBy(registration,
                executionCourse.getExecutionPeriod());
        if (optionalSchoolClass.isPresent()) {
            shifts.addAll(optionalSchoolClass.get().getAssociatedShiftsSet().stream()
                    .filter(s -> s.getExecutionCourse() == executionCourse && s.getTypes().contains(shiftType))
                    .collect(Collectors.toSet()));
        }

        // otherwise, he can chose any shift
        if (shifts.isEmpty()) {
            shifts.addAll(possibleShiftsToEnrolProvider.getShifts(executionCourse, shiftType, registration));
        }

        final JsonArray result = new JsonArray();
        for (final Shift shift : shifts) {
            if (shift.getLotacao().intValue() > shift.getStudentsSet().size()) {
                JsonObject jsonShift = new JsonObject();
                jsonShift.addProperty("name", shift.getNome());
                jsonShift.addProperty("type", shift.getShiftTypesPrettyPrint());
                jsonShift.addProperty("lessons", shift.getLessonPresentationString());
                jsonShift.addProperty("externalId", shift.getExternalId());
                result.add(jsonShift);
            }
        }

        return new GsonBuilder().create().toJson(result);
    }

    public static PossibleShiftsToEnrolProvider getPossibleShiftsToEnrolDefaultProvider() {
        return (ExecutionCourse ec, ShiftType st, Registration r) -> ec.getShiftsByTypeOrderedByShiftName(st);
    }

    public static void setPossibleShiftsToEnrolProvider(
            PossibleShiftsToEnrolProvider possibleShiftsToEnrolProvider) {
        ShiftEnrolmentController.possibleShiftsToEnrolProvider = possibleShiftsToEnrolProvider;
    }

    @RequestMapping(value = "/addShift/{registrationOid}/{periodOid}/{shiftOid}")
    public String addShift(@PathVariable("registrationOid") Registration registration,
            @PathVariable("periodOid") AcademicEnrolmentPeriod enrolmentPeriod,
            @PathVariable("shiftOid") Shift shift, Model model) {

        checkUser(registration);

        try {
            addShiftService(registration, shift);
            addInfoMessage(BundleUtil.getString("resources.FenixeduUlisboaSpecificationsResources",
                    "message.shiftEnrolment.addShift.success"), model);
        } catch (DomainException e) {
            addErrorMessage(
                    BundleUtil.getString("resources.FenixeduUlisboaSpecificationsResources", e.getMessage()),
                    model);
        }

        model.addAttribute("registration", registration);
        model.addAttribute("enrolmentPeriod", enrolmentPeriod);

        return home(model);
    }

    @Atomic
    protected void addShiftService(Registration registration, Shift shift) {
        if (!shift.reserveForStudent(registration)) {
            throw new DomainException("error.shiftEnrolment.shiftFull", shift.getNome(),
                    shift.getShiftTypesPrettyPrint(), shift.getExecutionCourse().getName());
        }
    }

    @RequestMapping(value = "/removeShift/{registrationOid}/{periodOid}/{shiftOid}")
    public String removeShift(@PathVariable("registrationOid") Registration registration,
            @PathVariable("periodOid") AcademicEnrolmentPeriod enrolmentPeriod,
            @PathVariable("shiftOid") Shift shift, Model model) {

        checkUser(registration);

        try {
            removeShiftService(registration, shift);
            addInfoMessage(BundleUtil.getString("resources.FenixeduUlisboaSpecificationsResources",
                    "message.shiftEnrolment.removeShift.success"), model);
        } catch (DomainException e) {
            addErrorMessage(e.getLocalizedMessage(), model);
        }

        model.addAttribute("registration", registration);
        model.addAttribute("enrolmentPeriod", enrolmentPeriod);

        return home(model);
    }

    @Atomic
    private void removeShiftService(Registration registration, Shift shift) {
        registration.removeShifts(shift);
    }

    @RequestMapping(value = "currentSchedule.json/{registrationOid}/{executionSemesterOid}", produces = "application/json; charset=utf-8")
    public @ResponseBody String schedule(@PathVariable("registrationOid") Registration registration,
            @PathVariable("executionSemesterOid") ExecutionSemester executionSemester) {

        checkUser(registration);

        final JsonArray result = new JsonArray();

        for (final Shift shift : registration.getShiftsFor(executionSemester)) {
            for (Lesson lesson : shift.getAssociatedLessonsSet()) {
                final DateTime now = new DateTime();
                final DateTime weekDay = now
                        .withDayOfWeek(lesson.getDiaSemana().getDiaSemanaInDayOfWeekJodaFormat());
                final DateTime startTime = weekDay.withTime(lesson.getBeginHourMinuteSecond().getHour(),
                        lesson.getBeginHourMinuteSecond().getMinuteOfHour(), 0, 0);
                final DateTime endTime = weekDay.withTime(lesson.getEndHourMinuteSecond().getHour(),
                        lesson.getEndHourMinuteSecond().getMinuteOfHour(), 0, 0);

                final JsonObject event = new JsonObject();
                event.addProperty("id", lesson.getExternalId());
                event.addProperty("start", startTime.toString());
                event.addProperty("end", endTime.toString());
                event.addProperty("title", shift.getExecutionCourse().getName() + " ("
                        + shift.getShiftTypesCodePrettyPrint() + " - " + shift.getNome() + ")");
                result.add(event);
            }
        }

        return result.toString();
    }

    static private Student checkUser(final Registration input) {
        final Student student = Authenticate.getUser().getPerson().getStudent();

        if (student == null || (input != null && input.getStudent() != student)) {
            throw new SecurityException("error.authorization.notGranted");
        }

        return student;
    }

    @SuppressWarnings("serial")
    public static class EnrolmentPeriodDTO implements Serializable, Comparable<EnrolmentPeriodDTO> {

        private final AcademicEnrolmentPeriodBean enrolmentPeriod;
        private Boolean selected;

        public EnrolmentPeriodDTO(final AcademicEnrolmentPeriodBean enrolmentPeriod, final Boolean selected) {
            super();
            this.enrolmentPeriod = enrolmentPeriod;
            this.selected = selected;
        }

        public Registration getRegistration() {
            return enrolmentPeriod.getRegistration();
        }

        public AcademicEnrolmentPeriod getEnrolmentPeriod() {
            return enrolmentPeriod.getEnrolmentPeriod();
        }

        public ExecutionSemester getExecutionSemester() {
            return enrolmentPeriod.getExecutionSemester();
        }

        public Boolean getSelected() {
            return selected;
        }

        public void setSelected(Boolean selected) {
            this.selected = selected;
        }

        @Override
        public int compareTo(EnrolmentPeriodDTO o) {
            int result = Degree.COMPARATOR_BY_NAME_AND_ID.compare(getRegistration().getDegree(),
                    o.getRegistration().getDegree());
            return result == 0 ? getExecutionSemester().compareTo(o.getExecutionSemester()) : result;
        }

        public Map<Lesson, Collection<Lesson>> getLessonsOverlaps() {
            final Map<Lesson, Collection<Lesson>> overlapsMap = new HashMap<Lesson, Collection<Lesson>>();

            try {
                final List<Lesson> allLessons = getRegistration().getShiftsFor(getExecutionSemester()).stream()
                        .flatMap(s -> s.getAssociatedLessonsSet().stream()).collect(Collectors.toList());
                while (!allLessons.isEmpty()) {
                    final Lesson lesson = allLessons.remove(0);
                    final Set<Lesson> overlappingLessons = allLessons.stream()
                            .filter(l -> getLessonIntervalHack(l).overlaps(getLessonIntervalHack(lesson)))
                            .collect(Collectors.toSet());
                    if (!overlappingLessons.isEmpty()) {
                        overlapsMap.put(lesson, overlappingLessons);
                    }
                }
            } catch (Exception e) {
                // new code that will enter in production the day before of enrolments
                // just in case of some untested exception, prevent the blowing of rest...
            }

            return overlapsMap;
        }

        /**
         * HACK: this interval is not accurate, because it doesn't takes into account lesson instance dates
         */
        private static Interval getLessonIntervalHack(final Lesson lesson) {
            final int weekDay = lesson.getDiaSemana().getDiaSemanaInDayOfWeekJodaFormat();
            return new Interval(
                    new LocalDate().toDateTime(lesson.getBeginHourMinuteSecond().toLocalTime())
                            .withDayOfWeek(weekDay),
                    new LocalDate().toDateTime(lesson.getEndHourMinuteSecond().toLocalTime())
                            .withDayOfWeek(weekDay));
        }
    }

    static private boolean isValidPeriodForUser(final AcademicEnrolmentPeriodBean ep) {
        return ep.isOpen() && ep.isForShift();
    }

}