Repository: urlaubsverwaltung/urlaubsverwaltung (java)

Issue:
Beim Editieren von Überstundenabbau wird der aktuelle Überstundenabbau doppelt berechnet
Überstundenabbau 12 Stunden, somit rutscht die Person auf -2,5 Überstunden. Antrag wird editiert

![grafik](https://github.com/urlaubsverwaltung/urlaubsverwaltung/assets/5683677/60f24624-6e1e-4291-9dc0-4f84dc8aecd7)

und soll gespeichert werden mit z.b. 11 - dann folgt der Fehler, obwohl die Person somit nur noch auf -1.5 sein sollte. Jedoch wird der aktuelle Antrag in der Berechnung mit berechnet und somit 12+11 berechnet als Abbau, was mehr als -5 Minusstunden sind, welche eingestellt sind.

![grafik](https://github.com/urlaubsverwaltung/urlaubsverwaltung/assets/5683677/adc1860e-0e45-471b-85d6-612b5dbcae73)

Beheben: Der Antrag, welcher bearbeitet wird, darf nur mit dem neuen Wert einberechnet werden.



Pull request:
Fix double calculated overtime hours on application for leave edit
closes #4451

ToDo:
- [x] test
- [x] better create an overtime service method with an exclude of application by ids ?

Patch:
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/application/application/ApplicationForLeaveFormValidator.java b/src/main/java/org/synyx/urlaubsverwaltung/application/application/ApplicationForLeaveFormValidator.java
index b57955fd0..e98651e95 100644
--- a/src/main/java/org/synyx/urlaubsverwaltung/application/application/ApplicationForLeaveFormValidator.java
+++ b/src/main/java/org/synyx/urlaubsverwaltung/application/application/ApplicationForLeaveFormValidator.java
@@ -27,6 +27,7 @@ import java.time.Duration;
 import java.time.LocalDate;
 import java.time.LocalTime;
 import java.time.ZonedDateTime;
+import java.util.List;
 import java.util.Optional;
 
 import static org.springframework.util.StringUtils.hasText;
@@ -92,11 +93,17 @@ class ApplicationForLeaveFormValidator implements Validator {
     private final Clock clock;
 
     @Autowired
-    ApplicationForLeaveFormValidator(WorkingTimeService workingTimeService, WorkDaysCountService workDaysCountService,
-                                     OverlapService overlapService, CalculationService calculationService, SettingsService settingsService,
-                                     OvertimeService overtimeService, VacationTypeService vacationTypeService, ApplicationMapper applicationMapper,
-                                     Clock clock) {
-
+    ApplicationForLeaveFormValidator(
+        WorkingTimeService workingTimeService,
+        WorkDaysCountService workDaysCountService,
+        OverlapService overlapService,
+        CalculationService calculationService,
+        SettingsService settingsService,
+        OvertimeService overtimeService,
+        VacationTypeService vacationTypeService,
+        ApplicationMapper applicationMapper,
+        Clock clock
+    ) {
         this.workingTimeService = workingTimeService;
         this.workDaysCountService = workDaysCountService;
         this.overlapService = overlapService;
@@ -445,7 +452,14 @@ class ApplicationForLeaveFormValidator implements Validator {
     private boolean checkOvertimeHours(ApplicationForLeaveForm applicationForLeaveForm, OvertimeSettings settings) {
 
         final Duration minimumOvertime = Duration.ofHours(settings.getMinimumOvertime());
-        final Duration leftOvertimeForPerson = overtimeService.getLeftOvertimeForPerson(applicationForLeaveForm.getPerson());
+
+        final Duration leftOvertimeForPerson;
+        if (applicationForLeaveForm.getId() != null) {
+            leftOvertimeForPerson = overtimeService.getLeftOvertimeForPerson(applicationForLeaveForm.getPerson(), List.of(applicationForLeaveForm.getId()));
+        } else {
+            leftOvertimeForPerson = overtimeService.getLeftOvertimeForPerson(applicationForLeaveForm.getPerson());
+        }
+
         final Duration temporaryOvertimeForPerson = leftOvertimeForPerson.minus(Duration.ofHours(applicationForLeaveForm.getHours().longValue()));
 
         return temporaryOvertimeForPerson.compareTo(minimumOvertime.negated()) >= 0;
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/overtime/OvertimeService.java b/src/main/java/org/synyx/urlaubsverwaltung/overtime/OvertimeService.java
index c2d1dc973..16ba11ef2 100644
--- a/src/main/java/org/synyx/urlaubsverwaltung/overtime/OvertimeService.java
+++ b/src/main/java/org/synyx/urlaubsverwaltung/overtime/OvertimeService.java
@@ -80,6 +80,15 @@ public interface OvertimeService {
      */
     Duration getLeftOvertimeForPerson(Person person);
 
+    /**
+     * Get the left overtime hours of the given person: the difference between the total overtime and the overtime
+     * reduction excluding the given applications
+     *
+     * @param person to get the left overtime for
+     * @return the left overtime, never {@code null}
+     */
+    Duration getLeftOvertimeForPerson(Person person, List<Long> excludingApplicationIDs);
+
     /**
      * Get the left overtime hours of the given persons: the difference between the total overtime and the overtime
      * reduction.
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/overtime/OvertimeServiceImpl.java b/src/main/java/org/synyx/urlaubsverwaltung/overtime/OvertimeServiceImpl.java
index c8bb26aaf..b2c594ba6 100644
--- a/src/main/java/org/synyx/urlaubsverwaltung/overtime/OvertimeServiceImpl.java
+++ b/src/main/java/org/synyx/urlaubsverwaltung/overtime/OvertimeServiceImpl.java
@@ -135,6 +135,18 @@ class OvertimeServiceImpl implements OvertimeService {
         return totalOvertime.minus(overtimeReduction);
     }
 
+    @Override
+    public Duration getLeftOvertimeForPerson(Person person, List<Long> excludingApplicationIDs) {
+
+        final List<Application> applications = applicationService.findApplicationsByIds(excludingApplicationIDs);
+        final Duration leftOvertimeForPerson = getLeftOvertimeForPerson(person);
+
+        return applications.stream()
+            .filter(application -> application.getVacationType().isOfCategory(OVERTIME))
+            .map(Application::getHours)
+            .reduce(leftOvertimeForPerson, Duration::plus);
+    }
+
     @Override
     public Map<Person, LeftOvertime> getLeftOvertimeTotalAndDateRangeForPersons(List<Person> persons, List<Application> applications, LocalDate start, LocalDate end) {
 


Test patch:
diff --git a/src/test/java/org/synyx/urlaubsverwaltung/overtime/OvertimeServiceImplTest.java b/src/test/java/org/synyx/urlaubsverwaltung/overtime/OvertimeServiceImplTest.java
index 5e7e16e1f..f847ab9cf 100644
--- a/src/test/java/org/synyx/urlaubsverwaltung/overtime/OvertimeServiceImplTest.java
+++ b/src/test/java/org/synyx/urlaubsverwaltung/overtime/OvertimeServiceImplTest.java
@@ -322,6 +322,30 @@ class OvertimeServiceImplTest {
         assertThat(leftOvertime).isEqualTo(Duration.ZERO);
     }
 
+    @Test
+    void ensureToExcludeApplicationsIdsForLeftOvertimeForPerson() {
+
+        final Person person = new Person("muster", "Muster", "Marlene", "muster@example.org");
+
+        when(overtimeRepository.calculateTotalHoursForPerson(person)).thenReturn(Optional.of((double) Duration.ofHours(10L).toMinutes() / 60));
+        when(applicationService.getTotalOvertimeReductionOfPerson(person)).thenReturn(Duration.ofHours(5));
+
+        final VacationType<?> overtimeVacationType = ProvidedVacationType.builder(new StaticMessageSource()).id(1L).category(OVERTIME).build();
+        final Application applicationToEdit = new Application();
+        applicationToEdit.setId(1L);
+        applicationToEdit.setPerson(person);
+        applicationToEdit.setStatus(ApplicationStatus.ALLOWED);
+        applicationToEdit.setVacationType(overtimeVacationType);
+        applicationToEdit.setHours(Duration.ofHours(1L));
+        applicationToEdit.setStartDate(LocalDate.now(clock).withMonth(JANUARY.getValue()));
+        applicationToEdit.setEndDate(LocalDate.now(clock).withMonth(JANUARY.getValue()));
+
+        when(applicationService.findApplicationsByIds(List.of(applicationToEdit.getId()))).thenReturn(List.of(applicationToEdit));
+
+        final Duration totalHours = sut.getLeftOvertimeForPerson(person, List.of(overtimeVacationType.getId()));
+        assertThat(totalHours).isEqualTo(Duration.ofHours(6));
+    }
+
     @ParameterizedTest
     @ValueSource(booleans = {true, false})
     void ensureOfficeIsAllowedToWriteOthersOvertime(boolean overtimeWritePrivilegedOnly) {