/** * Copyright © 2011 Instituto Superior Técnico * * This file is part of FenixEdu Teacher Credits. * * FenixEdu Teacher Credits 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 Teacher Credits 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 Teacher Credits. If not, see <http://www.gnu.org/licenses/>. */ package pt.ist.fenixedu.teacher.domain; import java.text.ParseException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Set; import org.fenixedu.academic.FenixEduAcademicConfiguration; import org.fenixedu.academic.domain.ExecutionSemester; import org.fenixedu.academic.domain.Teacher; import org.fenixedu.academic.domain.TeacherAuthorization; import org.fenixedu.academic.domain.organizationalStructure.AccountabilityTypeEnum; import org.fenixedu.academic.domain.organizationalStructure.Unit; import org.fenixedu.academic.domain.thesis.ThesisEvaluationParticipant; import org.joda.time.Interval; import org.joda.time.LocalDate; import org.joda.time.PeriodType; import org.joda.time.YearMonthDay; import pt.ist.fenixedu.contracts.domain.Employee; import pt.ist.fenixedu.contracts.domain.organizationalStructure.PersonFunction; import pt.ist.fenixedu.contracts.domain.personnelSection.contracts.PersonContractSituation; import pt.ist.fenixedu.contracts.domain.personnelSection.contracts.PersonProfessionalData; import pt.ist.fenixedu.contracts.domain.personnelSection.contracts.PersonProfessionalExemption; import pt.ist.fenixedu.contracts.domain.personnelSection.contracts.ProfessionalCategory; import pt.ist.fenixedu.teacher.domain.teacher.TeacherService; public class TeacherCredits extends TeacherCredits_Base { public static TeacherCredits readTeacherCredits(ExecutionSemester executionSemester, Teacher teacher) { for (TeacherCredits teacherCredit : teacher.getTeacherCreditsSet()) { if (teacherCredit.getTeacherCreditsState().getExecutionSemester().equals(executionSemester)) { return teacherCredit; } } return null; } public TeacherCreditsDocument getLastTeacherCreditsDocument() { TeacherCreditsDocument lastTeacherCreditsDocument = null; for (TeacherCreditsDocument teacherCreditsDocument : getTeacherCreditsDocumentSet()) { if (lastTeacherCreditsDocument == null || lastTeacherCreditsDocument.getUploadTime().isBefore(teacherCreditsDocument.getUploadTime())) { lastTeacherCreditsDocument = teacherCreditsDocument; } } return lastTeacherCreditsDocument; } public static Double calculateMandatoryLessonHours(Teacher teacher, ExecutionSemester executionSemester) { TeacherAuthorization teacherAuthorization = teacher.getTeacherAuthorization(executionSemester.getAcademicInterval()).orElse(null); if (teacherAuthorization != null) { if (teacherAuthorization.isContracted()) { return teacherAuthorization.getLessonHours(); } else { TeacherService teacherService = TeacherService.getTeacherServiceByExecutionPeriod(teacher, executionSemester); return teacherService == null ? 0 : teacherService.getTeachingDegreeCredits(); } } return 0.0; } public static double calculateBalanceOfCreditsUntil(Teacher teacher, ExecutionSemester executionSemester) throws ParseException { double balanceCredits = 0.0; ExecutionSemester firstExecutionPeriod = TeacherCredits.readStartExecutionSemesterForCredits(); if (executionSemester != null && executionSemester.isAfter(firstExecutionPeriod)) { balanceCredits = sumCreditsBetweenPeriods(teacher, firstExecutionPeriod.getNextExecutionPeriod(), executionSemester, balanceCredits); } return balanceCredits; } private static double sumCreditsBetweenPeriods(Teacher teacher, ExecutionSemester startPeriod, ExecutionSemester endExecutionPeriod, double totalCredits) throws ParseException { ExecutionSemester lastExecutionSemester = readLastExecutionSemesterForCredits(); ExecutionSemester executionPeriodAfterEnd = endExecutionPeriod.getNextExecutionPeriod(); while (startPeriod != executionPeriodAfterEnd && endExecutionPeriod.isBeforeOrEquals(lastExecutionSemester)) { TeacherCredits teacherCredits = readTeacherCredits(startPeriod, teacher); if (teacherCredits != null && teacherCredits.getTeacherCreditsState().isCloseState()) { totalCredits += teacherCredits.getTotalCredits().subtract(teacherCredits.getMandatoryLessonHours()).doubleValue(); } else if (!ProfessionalCategory.isMonitor(teacher, startPeriod)) { final ExecutionSemester executionSemester = startPeriod; TeacherService teacherService = TeacherService.getTeacherServiceByExecutionPeriod(teacher, executionSemester); if (teacherService != null) { totalCredits += teacherService.getCredits(); } totalCredits += calculateThesesCredits(teacher, startPeriod); totalCredits += calculateManagementFunctionsCredits(teacher, startPeriod); totalCredits += calculateServiceExemptionCredits(teacher, startPeriod); totalCredits -= calculateMandatoryLessonHours(teacher, startPeriod); } startPeriod = startPeriod.getNextExecutionPeriod(); } return totalCredits; } public static double calculateThesesCredits(Teacher teacher, ExecutionSemester executionSemester) { final ArrayList<ThesisEvaluationParticipant> participants = new ArrayList<ThesisEvaluationParticipant>(); for (final ThesisEvaluationParticipant participant : teacher.getPerson().getThesisEvaluationParticipantsSet()) { if (participant.getThesis().getEnrolment().getExecutionYear().equals(executionSemester.getExecutionYear())) { participants.add(participant); } } Collections.sort(participants, ThesisEvaluationParticipant.COMPARATOR_BY_STUDENT_NUMBER); return round(participants.stream().mapToDouble(p -> p.getParticipationCredits()).sum()); } public static double calculateManagementFunctionsCredits(Teacher teacher, ExecutionSemester executionSemester) { double totalCredits = 0.0; for (PersonFunction personFunction : (Collection<PersonFunction>) teacher.getPerson().getParentAccountabilities( AccountabilityTypeEnum.MANAGEMENT_FUNCTION, PersonFunction.class)) { if (personFunction.belongsToPeriod(executionSemester.getBeginDateYearMonthDay(), executionSemester.getEndDateYearMonthDay()) && !personFunction.getFunction().isVirtual()) { totalCredits = (personFunction.getCredits() != null) ? totalCredits + personFunction.getCredits() : totalCredits; } } return round(totalCredits); } private static Double round(double n) { return Math.round((n * 100.0)) / 100.0; } public static double calculateServiceExemptionCredits(Teacher teacher, ExecutionSemester executionSemester) { Set<PersonContractSituation> personProfessionalExemptions = PersonContractSituation.getValidTeacherServiceExemptions(teacher, executionSemester); Interval semesterInterval = new Interval(executionSemester.getBeginDateYearMonthDay().toLocalDate().toDateTimeAtStartOfDay(), executionSemester.getEndDateYearMonthDay().toLocalDate().toDateTimeAtStartOfDay()); int lessonsDays = semesterInterval.toPeriod(PeriodType.days()).getDays(); List<Interval> notYetOverlapedIntervals = new ArrayList<Interval>(); List<Interval> newIntervals = new ArrayList<Interval>(); notYetOverlapedIntervals.add(semesterInterval); Double mandatoryLessonHours = calculateMandatoryLessonHours(teacher, executionSemester); Double maxSneHours = mandatoryLessonHours; TeacherService teacherService = TeacherService.getTeacherServiceByExecutionPeriod(teacher, executionSemester); if (teacherService != null && teacherService.getReductionService() != null) { maxSneHours = Math.max(0, (mandatoryLessonHours - teacherService.getReductionServiceCredits().doubleValue())); } for (PersonContractSituation personContractSituation : personProfessionalExemptions) { LocalDate exemptionEnd = personContractSituation.getServiceExemptionEndDate() == null ? semesterInterval.getEnd().toLocalDate() : personContractSituation .getServiceExemptionEndDate(); Interval exemptionInterval = new Interval(personContractSituation.getBeginDate().toDateTimeAtStartOfDay(), exemptionEnd.toDateTimeAtStartOfDay()); PersonProfessionalExemption personProfessionalExemption = personContractSituation.getPersonProfessionalExemption(); if (personContractSituation.countForCredits(semesterInterval)) { if (personProfessionalExemption != null) { exemptionEnd = personProfessionalExemption.getEndDate() == null ? semesterInterval.getEnd().toLocalDate() : personProfessionalExemption .getEndDate(); exemptionInterval = new Interval(personProfessionalExemption.getBeginDate().toDateTimeAtStartOfDay(), exemptionEnd.toDateTimeAtStartOfDay()); if (personProfessionalExemption.getIsSabaticalOrEquivalent()) { if (isSabbaticalForSemester(teacher, exemptionInterval, semesterInterval)) { return maxSneHours; } else { continue; } } } for (Interval notYetOverlapedInterval : notYetOverlapedIntervals) { Interval overlapInterval = exemptionInterval.overlap(notYetOverlapedInterval); if (overlapInterval != null) { newIntervals.addAll(getNotOverlapedIntervals(overlapInterval, notYetOverlapedInterval)); } else { newIntervals.add(notYetOverlapedInterval); } } notYetOverlapedIntervals.clear(); notYetOverlapedIntervals.addAll(newIntervals); newIntervals.clear(); } } int notOverlapedDays = 0; for (Interval interval : notYetOverlapedIntervals) { notOverlapedDays += interval.toPeriod(PeriodType.days()).getDays(); } int overlapedDays = lessonsDays - notOverlapedDays; Double overlapedPercentage = round(Double.valueOf(overlapedDays) / Double.valueOf(lessonsDays)); return round(overlapedPercentage * maxSneHours); } private static List<Interval> getNotOverlapedIntervals(Interval overlapInterval, Interval notYetOverlapedInterval) { List<Interval> intervals = new ArrayList<Interval>(); LocalDate overlapIntervalStart = overlapInterval.getStart().toLocalDate(); LocalDate overlapIntervalEnd = overlapInterval.getEnd().toLocalDate(); LocalDate notYetOverlapedIntervalStart = notYetOverlapedInterval.getStart().toLocalDate(); LocalDate notYetOverlapedIntervalEnd = notYetOverlapedInterval.getEnd().toLocalDate(); if (overlapIntervalStart.equals(notYetOverlapedIntervalStart) && !overlapIntervalEnd.equals(notYetOverlapedIntervalEnd)) { intervals.add(new Interval(overlapInterval.getEnd().plusDays(1), notYetOverlapedInterval.getEnd())); } else if (!overlapIntervalStart.equals(notYetOverlapedIntervalStart) && overlapIntervalEnd.equals(notYetOverlapedIntervalEnd)) { intervals.add(new Interval(notYetOverlapedInterval.getStart(), overlapInterval.getStart().minusDays(1))); } else if (!overlapIntervalStart.equals(notYetOverlapedIntervalStart) && !overlapIntervalEnd.equals(notYetOverlapedIntervalEnd)) { intervals.add(new Interval(notYetOverlapedInterval.getStart(), overlapInterval.getStart().minusDays(1))); intervals.add(new Interval(overlapInterval.getEnd().plusDays(1), notYetOverlapedInterval.getEnd())); } return intervals; } private static boolean isSabbaticalForSemester(Teacher teacher, Interval exemptionInterval, Interval semesterPeriod) { double overlapPercentageThisSemester = calculateLessonsIntervalAndExemptionOverlapPercentage(semesterPeriod, exemptionInterval); if (overlapPercentageThisSemester == 1) { return true; } if (semesterPeriod.contains(exemptionInterval.getStart())) { return overlapPercentageThisSemester >= 0.5; } ExecutionSemester firstExecutionPeriod = ExecutionSemester.readByDateTime(exemptionInterval.getStart()); Interval firstExecutionPeriodInterval = new Interval(firstExecutionPeriod.getBeginDateYearMonthDay().toLocalDate().toDateTimeAtStartOfDay(), firstExecutionPeriod.getEndDateYearMonthDay().toLocalDate().toDateTimeAtStartOfDay()); double overlapPercentageFirstSemester = calculateLessonsIntervalAndExemptionOverlapPercentage(firstExecutionPeriodInterval, exemptionInterval); return overlapPercentageFirstSemester < 0.5; } private static double calculateLessonsIntervalAndExemptionOverlapPercentage(Interval lessonsInterval, Interval exemptionInterval) { if (lessonsInterval != null) { Interval overlapInterval = lessonsInterval.overlap(exemptionInterval); if (overlapInterval != null) { int intersectedDays = overlapInterval.toPeriod(PeriodType.days()).getDays(); return round(Double.valueOf(intersectedDays) / Double.valueOf(lessonsInterval.toPeriod(PeriodType.days()).getDays())); } } return 0.0; } public static List<Teacher> getAllTeachersFromUnit(Unit unit, YearMonthDay begin, YearMonthDay end) { List<Teacher> teachers = new ArrayList<Teacher>(); List<Employee> employees = Employee.getAllWorkingEmployees(unit, begin, end); for (Employee employee : employees) { Teacher teacher = employee.getPerson().getTeacher(); if (teacher != null && PersonProfessionalData.hasAnyTeacherContractSituation(teacher, begin.toLocalDate(), end.toLocalDate())) { teachers.add(teacher); } } return teachers; } static public ExecutionSemester readStartExecutionSemesterForCredits() { String yearString = FenixEduAcademicConfiguration.getConfiguration().getStartYearForCredits(); String semesterString = FenixEduAcademicConfiguration.getConfiguration().getStartSemesterForCredits(); if (yearString != null && yearString.length() != 0 && semesterString != null && semesterString.length() != 0) { return ExecutionSemester.readBySemesterAndExecutionYear(Integer.valueOf(semesterString), yearString); } return null; } static public ExecutionSemester readLastExecutionSemesterForCredits() { String yearString = FenixEduAcademicConfiguration.getConfiguration().getLastYearForCredits(); String semesterString = FenixEduAcademicConfiguration.getConfiguration().getLastSemesterForCredits(); if (yearString != null && yearString.length() != 0 && semesterString != null && semesterString.length() != 0) { return ExecutionSemester.readBySemesterAndExecutionYear(Integer.valueOf(semesterString), yearString); } return null; } }