/** * 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.spring.controller.teacher; import org.fenixedu.academic.domain.ExecutionCourse; import org.fenixedu.academic.domain.ExportGrouping; import org.fenixedu.academic.domain.Grouping; import org.fenixedu.academic.domain.Professorship; import org.fenixedu.academic.domain.Shift; import org.fenixedu.academic.domain.ShiftType; import org.fenixedu.academic.domain.StudentGroup; import org.fenixedu.academic.domain.student.Registration; import org.fenixedu.academic.ui.struts.action.teacher.ManageExecutionCourseDA; import org.fenixedu.academic.util.ProposalState; import org.fenixedu.bennu.spring.security.CSRFTokenBean; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.servlet.view.AbstractUrlBasedView; import org.springframework.web.servlet.view.RedirectView; import pt.ist.fenixframework.FenixFramework; import java.util.ArrayList; import java.util.Comparator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.TreeSet; import java.util.stream.Collectors; @Controller @RequestMapping("/teacher/{executionCourse}/student-groups/") public class GroupingController extends ExecutionCourseController { @Autowired StudentGroupService studentGroupService; // hack @Autowired CSRFTokenBean csrfTokenBean; @Override protected Class<?> getFunctionalityType() { return ManageExecutionCourseDA.class; } @Override Boolean getPermission(Professorship prof) { return prof.getPermissions().getGroups(); } @RequestMapping(value = "/show", method = RequestMethod.GET) public TeacherView showStudentGroups(Model model) { model.addAttribute("csrf",csrfTokenBean); return new TeacherView("executionCourse/groupings/viewProjectsAndLink"); } @RequestMapping(value = "/create", method = RequestMethod.GET) public TeacherView create(Model model) { model.addAttribute("csrf",csrfTokenBean); model.addAttribute("projectGroup", new ProjectGroupBean(this.executionCourse)); return new TeacherView("executionCourse/groupings/insertGroupProperties"); } @RequestMapping(value = "/edit/{grouping}", method = RequestMethod.GET) public TeacherView edit(Model model, Grouping grouping) { model.addAttribute("csrf",csrfTokenBean); model.addAttribute("projectGroup", new ProjectGroupBean(grouping, this.executionCourse)); return new TeacherView("executionCourse/groupings/insertGroupProperties"); } @RequestMapping(value = "/create", method = RequestMethod.POST) public AbstractUrlBasedView create(Model model, @ModelAttribute("projectGroup") ProjectGroupBean projectGroup, @PathVariable ExecutionCourse executionCourse, BindingResult bindingResult) { ArrayList<String> errors = new ArrayList<>(); if (bindingResult.hasErrors()) { errors.add("error.groupProperties.errorsDetected"); } if (projectGroup.getName().isEmpty()) { errors.add("error.groupProperties.missingName"); } Grouping groupingWithSameName = executionCourse.getGroupingByName(projectGroup.getName()); if (groupingWithSameName != null && !groupingWithSameName.getExternalId().equals(projectGroup.getExternalId())) { errors.add("error.exception.existing.groupProperties"); } ShiftType shiftType = projectGroup.getShiftType() == null || projectGroup.getShiftType().isEmpty() ? null : ShiftType .valueOf(projectGroup.getShiftType()); if (projectGroup.getDifferentiatedCapacity() && projectGroup.getMaximumGroupCapacity() != null && projectGroup .getDifferentiatedCapacityShifts() .entrySet() .stream() .anyMatch( entry -> ((Shift) FenixFramework.getDomainObject(entry.getKey())).getTypes().contains(shiftType) && entry.getValue() != null && ((Shift) FenixFramework.getDomainObject(entry.getKey())).getLotacao() != 0 /* it means it was locked from students enrolment only*/ && entry.getValue() * projectGroup.getMaximumGroupCapacity() > ((Shift) FenixFramework .getDomainObject(entry.getKey())).getLotacao())) { errors.add("error.groupProperties.capacityOverflow"); } if (projectGroup.getMaximumGroupCapacity() == null || projectGroup.getMaximumGroupCapacity() < 0) { errors.add("error.groupProperties.capacity.negative"); } if (projectGroup.getMinimumGroupCapacity() == null || projectGroup.getMinimumGroupCapacity() < 0) { errors.add("error.groupProperties.capacity.negative"); } if (projectGroup.getMaxGroupNumber() == null || projectGroup.getMaxGroupNumber() < 0) { errors.add("error.groupProperties.capacity.negative"); } if (projectGroup.getIdealGroupCapacity() != null && projectGroup.getIdealGroupCapacity() < 0) { errors.add("error.groupProperties.capacity.negative"); } if (smallerThan(projectGroup.getMaximumGroupCapacity(), projectGroup.getMinimumGroupCapacity())) { errors.add("error.groupProperties.minimum"); } if (smallerThan(projectGroup.getMaximumGroupCapacity(), projectGroup.getIdealGroupCapacity())) { errors.add("error.groupProperties.ideal.maximum"); } if(groupingWithSameName != null && !groupingWithSameName.getStudentGroupsSet().isEmpty()) { Integer maxStudentGroupsOnShift = groupingWithSameName.getStudentGroupsSet().stream() .filter(studentGroup -> !studentGroup.wasDeleted()) .filter(studentGroup -> studentGroup.getShift() != null) .collect(Collectors.groupingBy(studentGroup -> studentGroup.getShift(), Collectors.counting())) .values().stream().max(Comparator.naturalOrder()).orElseGet(()->new Long(0)).intValue(); if( smallerThan(projectGroup.getMaxGroupNumber(), maxStudentGroupsOnShift)){ errors.add("error.groupProperties.many.shift.groups"); } } if (smallerThan(projectGroup.getIdealGroupCapacity(), projectGroup.getMinimumGroupCapacity())) { errors.add("error.groupProperties.ideal.minimum"); } if (projectGroup.getEnrolmentBeginDay() == null) { errors.add("error.groupProperties.missingEnrolmentBeginDay"); } if (projectGroup.getEnrolmentEndDay() == null) { errors.add("error.groupProperties.missingEnrolmentEndDay"); } if (projectGroup.getEnrolmentEndDay() != null && projectGroup.getEnrolmentEndDay() != null && projectGroup.getEnrolmentEndDay().isBefore(projectGroup.getEnrolmentBeginDay())) { errors.add("error.manager.wrongDates"); } if (!errors.isEmpty()) { model.addAttribute("errors", errors); return new TeacherView("executionCourse/groupings/insertGroupProperties"); } Grouping grouping = studentGroupService.createOrEditGrouping(projectGroup, executionCourse); return new RedirectView("/teacher/" + executionCourse.getExternalId() + "/student-groups/view/" + grouping.getExternalId(), true); } private static Boolean smallerThan(Integer a, Integer b) { return a != null && b != null && a < b; } @RequestMapping(value = "/view/{grouping}", method = RequestMethod.GET) public TeacherView viewGrouping(Model model, @PathVariable Grouping grouping) { List<Shift> shiftList = new ArrayList<Shift>(); model.addAttribute("csrf",csrfTokenBean); if (grouping.getShiftType() != null) { shiftList = grouping.getExportGroupingsSet().stream().map(ExportGrouping::getExecutionCourse) .flatMap(ec -> ec.getAssociatedShifts().stream()).sorted(Shift.SHIFT_COMPARATOR_BY_NAME) .filter(shift -> shift.containsType(grouping.getShiftType())).collect(Collectors.toList()); } HashMap<Shift, TreeSet<StudentGroup>> studentGroupsByShift = new HashMap<Shift, TreeSet<StudentGroup>>(); for (Shift shift : shiftList) { TreeSet<StudentGroup> tree = new TreeSet<StudentGroup>(StudentGroup.COMPARATOR_BY_GROUP_NUMBER); tree.addAll(shift.getAssociatedStudentGroups(grouping)); studentGroupsByShift.put(shift, tree); } if (shiftList.isEmpty()) { TreeSet<StudentGroup> studentGroups = new TreeSet<StudentGroup>(StudentGroup.COMPARATOR_BY_GROUP_NUMBER); studentGroups.addAll(grouping.getStudentGroupsSet()); model.addAttribute("studentGroups", studentGroups); } model.addAttribute("studentGroupsByShift", studentGroupsByShift); model.addAttribute("grouping", grouping); model.addAttribute("shifts", shiftList); return new TeacherView("executionCourse/groupings/viewShiftsAndGroups"); } @RequestMapping(value = "/viewAttends/{grouping}", method = RequestMethod.GET) public TeacherView viewAttends(Model model, @PathVariable Grouping grouping) { model.addAttribute("csrf",csrfTokenBean); model.addAttribute("grouping", grouping); ArrayList<Registration> studentsNotAttending = new ArrayList<Registration>(); for (final ExportGrouping exportGrouping : grouping.getExportGroupingsSet()) { if (exportGrouping.getProposalState().getState() == ProposalState.ACEITE || exportGrouping.getProposalState().getState() == ProposalState.CRIADOR) { exportGrouping.getExecutionCourse().getAttendsSet().stream() .filter(attend -> !grouping.getAttendsSet().contains(attend)) .forEach(attend -> studentsNotAttending.add(attend.getRegistration())); } } model.addAttribute("studentsNotAttending", studentsNotAttending); return new TeacherView("executionCourse/groupings/viewAttendsSet"); } @RequestMapping(value = "/editAttends/{grouping}", method = RequestMethod.POST) public TeacherView editAttends(Model model, @PathVariable Grouping grouping, @ModelAttribute("attends") @Validated AttendsBean attendsBean, @PathVariable ExecutionCourse executionCourse, BindingResult bindingResult) { Map<String, Boolean> studentsToRemove = attendsBean.getRemoveStudent(); Map<String, Boolean> studentsToAdd = attendsBean.getAddStudent(); if (bindingResult.hasErrors()) { model.addAttribute("removeStudent", studentsToRemove); model.addAttribute("addStudent", studentsToAdd); model.addAttribute("errors", "binding error " + bindingResult.getAllErrors()); return viewAttends(model, grouping); } studentGroupService.updateGroupingAttends(grouping, studentsToRemove, studentsToAdd); return viewAttends(model, grouping); } @RequestMapping(value = "/viewAllStudentsAndGroups/{grouping}", method = RequestMethod.GET) public TeacherView viewAllStudentsAndGroups(Model model, @PathVariable Grouping grouping) { model.addAttribute("csrf",csrfTokenBean); model.addAttribute("grouping", grouping); model.addAttribute("studentsInStudentGroupsSize", grouping.getStudentGroupsSet().stream().mapToInt(sg -> sg.getAttendsSet().size()).sum()); return new TeacherView("executionCourse/groupings/viewAllStudentsAndGroups"); } @RequestMapping(value = "/viewStudentsAndGroupsByShift/{grouping}", method = RequestMethod.GET) public TeacherView viewStudentsAndGroupsByShift(Model model, @PathVariable Grouping grouping) { model.addAttribute("csrf",csrfTokenBean); model.addAttribute("grouping", grouping); return new TeacherView("executionCourse/groupings/viewStudentsAndGroupsByShift"); } @RequestMapping(value = "/viewStudentsAndGroupsByShift/{grouping}/shift/{shift}", method = RequestMethod.GET) public TeacherView viewStudentsAndGroupsByShift(Model model, @PathVariable Grouping grouping, @PathVariable Shift shift) { model.addAttribute("csrf",csrfTokenBean); model.addAttribute("shift", shift); model.addAttribute("grouping", grouping); TreeSet<StudentGroup> studentsByGroup = new TreeSet<StudentGroup>(StudentGroup.COMPARATOR_BY_GROUP_NUMBER); studentsByGroup.addAll(shift.getAssociatedStudentGroups(grouping)); model.addAttribute("studentsByGroup", studentsByGroup); return new TeacherView("executionCourse/groupings/viewStudentsAndGroupsByShift"); } @RequestMapping(value = "/deleteGrouping/{grouping}", method = RequestMethod.POST) public RedirectView deleteGrouping(Model model, @PathVariable Grouping grouping) { studentGroupService.deleteGrouping(grouping); return new RedirectView("/teacher/" + executionCourse.getExternalId() + "/student-groups/show", true); } }