Repository: urlaubsverwaltung/urlaubsverwaltung (java)

Issue:
Inkonsistente Angabe von Zeiträumen bei eingereichten Krankmeldungen
![grafik](https://github.com/urlaubsverwaltung/urlaubsverwaltung/assets/5683677/d3eac33e-045c-417d-b604-f0efe6798829)

Hier sollte statt 'Mi, 12.06.2024,  ganztägig' dann das stehen 'für Mittwoch, 19.06.2024, ganztägig'

Pull request:
unify date ranges in dashboard for submitted sicknotes
closes #4735

Patch:
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/application/application/ApplicationForLeaveViewController.java b/src/main/java/org/synyx/urlaubsverwaltung/application/application/ApplicationForLeaveViewController.java
index 8722e0672..265015738 100644
--- a/src/main/java/org/synyx/urlaubsverwaltung/application/application/ApplicationForLeaveViewController.java
+++ b/src/main/java/org/synyx/urlaubsverwaltung/application/application/ApplicationForLeaveViewController.java
@@ -14,6 +14,7 @@ import org.synyx.urlaubsverwaltung.person.Person;
 import org.synyx.urlaubsverwaltung.person.PersonService;
 import org.synyx.urlaubsverwaltung.settings.SettingsService;
 import org.synyx.urlaubsverwaltung.sicknote.settings.SickNoteSettings;
+import org.synyx.urlaubsverwaltung.sicknote.sicknote.SickNote;
 import org.synyx.urlaubsverwaltung.sicknote.sicknote.SickNoteService;
 import org.synyx.urlaubsverwaltung.sicknote.sicknote.SickNoteStatus;
 import org.synyx.urlaubsverwaltung.util.DurationFormatter;
@@ -126,8 +127,9 @@ class ApplicationForLeaveViewController implements HasLaunchpad {
         final List<ApplicationForLeaveDto> otherApplicationsDtos = mapToApplicationForLeaveDtoList(otherApplications, signedInUser, membersAsDepartmentHead, membersAsSecondStageAuthority, locale);
         model.addAttribute("otherApplications", otherApplicationsDtos);
 
-        final List<SickNoteDto> otherSickNotes = sickNoteService.getForStatesAndPerson(List.of(SickNoteStatus.SUBMITTED), getPersonsForRelevantSubmittedSickNotes(signedInUser)).stream().map(SickNoteDto::new).toList();
-        model.addAttribute("otherSickNotes", otherSickNotes);
+        final List<SickNote> otherSickNotes = sickNoteService.getForStatesAndPerson(List.of(SickNoteStatus.SUBMITTED), getPersonsForRelevantSubmittedSickNotes(signedInUser)).stream().toList();
+        final List<SickNoteDto> otherSickNotesDtos = mapToSickNoteDtoList(otherSickNotes, locale);
+        model.addAttribute("otherSickNotes", otherSickNotesDtos);
 
         final List<ApplicationForLeave> applicationsForLeaveCancellationRequests = getAllRelevantApplicationsForLeaveCancellationRequests(signedInUser, membersAsDepartmentHead, membersAsSecondStageAuthority);
         final List<ApplicationForLeaveDto> cancellationDtoList = mapToApplicationForLeaveDtoList(applicationsForLeaveCancellationRequests, signedInUser, membersAsDepartmentHead, membersAsSecondStageAuthority, locale);
@@ -158,6 +160,23 @@ class ApplicationForLeaveViewController implements HasLaunchpad {
             || (signedInUser.hasRole(SICK_NOTE_EDIT) && (signedInUser.hasAnyRole(BOSS, DEPARTMENT_HEAD, SECOND_STAGE_AUTHORITY)));
     }
 
+    private List<SickNoteDto> mapToSickNoteDtoList(List<SickNote> sickNotes, Locale locale) {
+        return sickNotes.stream()
+            .map(sickNote -> toView(sickNote, messageSource, locale))
+            .toList();
+    }
+
+    private static SickNoteDto toView(SickNote sickNote, MessageSource messageSource, Locale locale) {
+        return new SickNoteDto(
+            sickNote.getId().toString(),
+            sickNote.getWorkDays(),
+            new SickNotePersonDto(sickNote.getPerson().getNiceName(), sickNote.getPerson().getGravatarURL(), sickNote.getPerson().isInactive(), sickNote.getId()),
+            sickNote.getSickNoteType().getMessageKey(),
+            sickNote.getStatus().name(),
+            toDurationOfAbsenceDescription(sickNote, messageSource, locale)
+        );
+    }
+
     private List<ApplicationForLeaveDto> mapToApplicationForLeaveDtoList(List<ApplicationForLeave> applications, Person signedInUser, List<Person> membersAsDepartmentHead,
                                                                          List<Person> membersAsSecondStageAuthority, Locale locale) {
 
@@ -239,6 +258,24 @@ class ApplicationForLeaveViewController implements HasLaunchpad {
         return messageSource.getMessage("absence.period.multipleDays", new Object[]{dateStartString, dateEndString}, locale);
     }
 
+    private static String toDurationOfAbsenceDescription(SickNote sickNote, MessageSource messageSource, Locale locale) {
+        final DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("EEEE, dd.MM.yyyy", locale);
+
+        final LocalDate startDate = sickNote.getStartDate();
+        final LocalDate endDate = sickNote.getEndDate();
+
+        final String dateStartString = startDate.format(dateFormatter);
+
+        if (startDate.isEqual(endDate)) {
+            final DayLength dayLength = sickNote.getDayLength();
+            final String dayLengthText = dayLength == null ? "" : messageSource.getMessage(dayLength.name(), new Object[]{}, locale);
+            return messageSource.getMessage("absence.period.singleDay", new Object[]{dateStartString, dayLengthText}, locale);
+        }
+
+        final String dateEndString = endDate.format(dateFormatter);
+        return messageSource.getMessage("absence.period.multipleDays", new Object[]{dateStartString, dateEndString}, locale);
+    }
+
     private static ApplicationPersonDto toViewPerson(Person person, boolean allowedToAccessPersonData) {
         final Long id = allowedToAccessPersonData ? person.getId() : null;
         return new ApplicationPersonDto(person.getNiceName(), person.getGravatarURL(), person.isInactive(), id);
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/application/application/SickNoteDto.java b/src/main/java/org/synyx/urlaubsverwaltung/application/application/SickNoteDto.java
index ffbd6a16e..18b42eece 100644
--- a/src/main/java/org/synyx/urlaubsverwaltung/application/application/SickNoteDto.java
+++ b/src/main/java/org/synyx/urlaubsverwaltung/application/application/SickNoteDto.java
@@ -1,70 +1,37 @@
 package org.synyx.urlaubsverwaltung.application.application;
 
-import org.synyx.urlaubsverwaltung.period.DayLength;
-import org.synyx.urlaubsverwaltung.sicknote.sicknote.SickNote;
-
 import java.math.BigDecimal;
-import java.time.DayOfWeek;
-import java.time.LocalDate;
+import java.util.Objects;
 
-public class SickNoteDto {
+public final class SickNoteDto {
 
     private final String id;
-    private final LocalDate startDate;
-    private final DayOfWeek weekDayOfStartDate;
-    private final LocalDate endDate;
-    private final DayLength dayLength;
     private final BigDecimal workDays;
     private final SickNotePersonDto person;
     private final String type;
     private final String status;
-
-    private SickNoteDto(String id, LocalDate startDate, DayOfWeek weekDayOfStartDate, LocalDate endDate,
-                       DayLength dayLength, BigDecimal workDays, SickNotePersonDto person, String type,
-                       String status) {
+    private final String durationOfAbsenceDescription;
+
+    public SickNoteDto(
+        String id,
+        BigDecimal workDays,
+        SickNotePersonDto person,
+        String type,
+        String status,
+        String durationOfAbsenceDescription
+    ) {
         this.id = id;
-        this.startDate = startDate;
-        this.weekDayOfStartDate = weekDayOfStartDate;
-        this.endDate = endDate;
-        this.dayLength = dayLength;
         this.workDays = workDays;
         this.person = person;
         this.type = type;
         this.status = status;
-    }
-
-    SickNoteDto(SickNote sickNote) {
-        this(sickNote.getId().toString(),
-                sickNote.getStartDate(),
-                sickNote.getWeekDayOfStartDate(),
-                sickNote.getEndDate(),
-                sickNote.getDayLength(),
-                sickNote.getWorkDays(),
-                new SickNotePersonDto(sickNote.getPerson().getNiceName(), sickNote.getPerson().getGravatarURL(), sickNote.getPerson().isInactive(), sickNote.getPerson().getId()),
-                sickNote.getSickNoteType().getCategory().getMessageKey(),
-                sickNote.getStatus().name());
+        this.durationOfAbsenceDescription = durationOfAbsenceDescription;
     }
 
     public String getId() {
         return id;
     }
 
-    public LocalDate getStartDate() {
-        return startDate;
-    }
-
-    public DayOfWeek getWeekDayOfStartDate() {
-        return weekDayOfStartDate;
-    }
-
-    public LocalDate getEndDate() {
-        return endDate;
-    }
-
-    public DayLength getDayLength() {
-        return dayLength;
-    }
-
     public BigDecimal getWorkDays() {
         return workDays;
     }
@@ -81,4 +48,36 @@ public class SickNoteDto {
         return status;
     }
 
+    public String getDurationOfAbsenceDescription() {
+        return durationOfAbsenceDescription;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) return true;
+        if (obj == null || obj.getClass() != this.getClass()) return false;
+        var that = (SickNoteDto) obj;
+        return Objects.equals(this.id, that.id) &&
+            Objects.equals(this.workDays, that.workDays) &&
+            Objects.equals(this.person, that.person) &&
+            Objects.equals(this.type, that.type) &&
+            Objects.equals(this.status, that.status) &&
+            Objects.equals(this.durationOfAbsenceDescription, that.durationOfAbsenceDescription);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(id, workDays, person, type, status, durationOfAbsenceDescription);
+    }
+
+    @Override
+    public String toString() {
+        return "SickNoteDto[" +
+            "id=" + id + ", " +
+            "workDays=" + workDays + ", " +
+            "person=" + person + ", " +
+            "type=" + type + ", " +
+            "status=" + status + ", " +
+            "durationOfAbsenceDescription=" + durationOfAbsenceDescription + ']';
+    }
 }
diff --git a/src/main/resources/templates/application/application-overview.html b/src/main/resources/templates/application/application-overview.html
index 97eef6bae..6a7979b2a 100644
--- a/src/main/resources/templates/application/application-overview.html
+++ b/src/main/resources/templates/application/application-overview.html
@@ -592,18 +592,10 @@
                       <th:block th:text="#{overview.sicknotes.sickdays.days(${otherSickNote.workDays})}" />
                       <th:block th:text="${#messages.msg(otherSickNote.type)}"></th:block>
                     </span>
-                    <span class="tw-flex tw-flex-col lg:tw-flex-row tw-text-[#444444] dark:tw-text-zinc-200">
-                      <th:block th:if="${otherSickNote.startDate == otherSickNote.endDate}">
-                        <th:block th:text="|${#messages.msg(otherSickNote.weekDayOfStartDate + '.short')}|"></th:block>,
-                        <th:block th:replace="~{fragments/date::date(${otherSickNote.startDate})}"></th:block>,&nbsp;
-                        <th:block th:text="${#messages.msg(otherSickNote.dayLength)}"></th:block>
-                      </th:block>
-                      <th:block th:if="${otherSickNote.startDate != otherSickNote.endDate}">
-                        <span
-                          th:replace="~{fragments/date-range::date-range(from=${otherSickNote.startDate}, to=${otherSickNote.endDate}, pattern='E, dd.MM.yyyy')}"
-                        ></span>
-                      </th:block>
-                    </span>
+                    <span
+                      class="tw-block tw-text-black-almost dark:tw-text-zinc-200"
+                      th:text="${otherSickNote.durationOfAbsenceDescription}"
+                    ></span>
                   </a>
                 </div>
                 <div class="print:tw-hidden">


Test patch:
diff --git a/src/test/java/org/synyx/urlaubsverwaltung/application/application/ApplicationForLeaveViewControllerTest.java b/src/test/java/org/synyx/urlaubsverwaltung/application/application/ApplicationForLeaveViewControllerTest.java
index 92158a160..ced9753b3 100644
--- a/src/test/java/org/synyx/urlaubsverwaltung/application/application/ApplicationForLeaveViewControllerTest.java
+++ b/src/test/java/org/synyx/urlaubsverwaltung/application/application/ApplicationForLeaveViewControllerTest.java
@@ -33,7 +33,6 @@ import java.time.LocalDate;
 import java.util.List;
 import java.util.Map;
 
-import static java.time.DayOfWeek.THURSDAY;
 import static org.hamcrest.Matchers.allOf;
 import static org.hamcrest.Matchers.contains;
 import static org.hamcrest.Matchers.equalTo;
@@ -1553,16 +1552,13 @@ class ApplicationForLeaveViewControllerTest {
                 allOf(
                     instanceOf(SickNoteDto.class),
                     hasProperty("id", equalTo("1")),
-                    hasProperty("startDate", equalTo(startDate)),
-                    hasProperty("endDate", equalTo(endDate)),
-                    hasProperty("dayLength", equalTo(FULL)),
                     hasProperty("workDays", equalTo(BigDecimal.valueOf(2L))),
-                    hasProperty("weekDayOfStartDate", equalTo(THURSDAY)),
                     hasProperty("person",
                         hasProperty("name", equalTo("Hans Dampf"))
                     ),
-                    hasProperty("type", equalTo("application.data.sicknotetype.sicknote")),
-                    hasProperty("status", equalTo("SUBMITTED"))
+                    hasProperty("type", equalTo("sickNoteTypeMessageKey")),
+                    hasProperty("status", equalTo("SUBMITTED")),
+                    hasProperty("durationOfAbsenceDescription")
                 )
             )))
             .andExpect(view().name("application/application-overview"));
@@ -1609,16 +1605,13 @@ class ApplicationForLeaveViewControllerTest {
                 allOf(
                     instanceOf(SickNoteDto.class),
                     hasProperty("id", equalTo("1")),
-                    hasProperty("startDate", equalTo(startDate)),
-                    hasProperty("endDate", equalTo(endDate)),
-                    hasProperty("dayLength", equalTo(FULL)),
                     hasProperty("workDays", equalTo(BigDecimal.valueOf(2L))),
-                    hasProperty("weekDayOfStartDate", equalTo(THURSDAY)),
                     hasProperty("person",
                         hasProperty("name", equalTo("Hans Dampf"))
                     ),
-                    hasProperty("type", equalTo("application.data.sicknotetype.sicknote")),
-                    hasProperty("status", equalTo("SUBMITTED"))
+                    hasProperty("type", equalTo("sickNoteTypeMessageKey")),
+                    hasProperty("status", equalTo("SUBMITTED")),
+                    hasProperty("durationOfAbsenceDescription")
                 )
             )))
             .andExpect(view().name("application/application-overview"));
@@ -1665,16 +1658,13 @@ class ApplicationForLeaveViewControllerTest {
                 allOf(
                     instanceOf(SickNoteDto.class),
                     hasProperty("id", equalTo("1")),
-                    hasProperty("startDate", equalTo(startDate)),
-                    hasProperty("endDate", equalTo(endDate)),
-                    hasProperty("dayLength", equalTo(FULL)),
                     hasProperty("workDays", equalTo(BigDecimal.valueOf(2L))),
-                    hasProperty("weekDayOfStartDate", equalTo(THURSDAY)),
                     hasProperty("person",
                         hasProperty("name", equalTo("Hans Dampf"))
                     ),
-                    hasProperty("type", equalTo("application.data.sicknotetype.sicknote")),
-                    hasProperty("status", equalTo("SUBMITTED"))
+                    hasProperty("type", equalTo("sickNoteTypeMessageKey")),
+                    hasProperty("status", equalTo("SUBMITTED")),
+                    hasProperty("durationOfAbsenceDescription")
                 )
             )))
             .andExpect(view().name("application/application-overview"));
@@ -1721,15 +1711,11 @@ class ApplicationForLeaveViewControllerTest {
                 allOf(
                     instanceOf(SickNoteDto.class),
                     hasProperty("id", equalTo("1")),
-                    hasProperty("startDate", equalTo(startDate)),
-                    hasProperty("endDate", equalTo(endDate)),
-                    hasProperty("dayLength", equalTo(FULL)),
                     hasProperty("workDays", equalTo(BigDecimal.valueOf(2L))),
-                    hasProperty("weekDayOfStartDate", equalTo(THURSDAY)),
                     hasProperty("person",
                         hasProperty("name", equalTo("Hans Dampf"))
                     ),
-                    hasProperty("type", equalTo("application.data.sicknotetype.sicknote")),
+                    hasProperty("type", equalTo("sickNoteTypeMessageKey")),
                     hasProperty("status", equalTo("SUBMITTED"))
                 )
             )))
@@ -1739,6 +1725,7 @@ class ApplicationForLeaveViewControllerTest {
     private static SickNoteType anySickNoteType() {
         final SickNoteType sickNoteType = new SickNoteType();
         sickNoteType.setCategory(SICK_NOTE);
+        sickNoteType.setMessageKey("sickNoteTypeMessageKey");
         return sickNoteType;
     }
 
diff --git a/src/test/java/org/synyx/urlaubsverwaltung/application/application/SickNoteDtoTest.java b/src/test/java/org/synyx/urlaubsverwaltung/application/application/SickNoteDtoTest.java
deleted file mode 100644
index 9f84dd3a7..000000000
--- a/src/test/java/org/synyx/urlaubsverwaltung/application/application/SickNoteDtoTest.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package org.synyx.urlaubsverwaltung.application.application;
-
-import org.junit.jupiter.api.Test;
-import org.synyx.urlaubsverwaltung.person.Person;
-import org.synyx.urlaubsverwaltung.sicknote.sicknote.SickNote;
-import org.synyx.urlaubsverwaltung.sicknote.sicknote.SickNoteCategory;
-import org.synyx.urlaubsverwaltung.sicknote.sicknotetype.SickNoteType;
-import org.synyx.urlaubsverwaltung.workingtime.WorkingTimeCalendar;
-
-import java.math.BigDecimal;
-import java.time.LocalDate;
-import java.util.Map;
-
-import static java.time.DayOfWeek.FRIDAY;
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.synyx.urlaubsverwaltung.period.DayLength.FULL;
-import static org.synyx.urlaubsverwaltung.sicknote.sicknote.SickNoteStatus.SUBMITTED;
-import static org.synyx.urlaubsverwaltung.workingtime.WorkingTimeCalendar.WorkingDayInformation.WorkingTimeCalendarEntryType.WORKDAY;
-
-class SickNoteDtoTest {
-
-    @Test
-    void sickNoteDtoBySickNote() {
-
-        final SickNoteType sickNoteType = new SickNoteType();
-        sickNoteType.setCategory(SickNoteCategory.SICK_NOTE);
-        final Person person = new Person("username", "lastname", "firstname", "email");
-        person.setId(2L);
-
-        final WorkingTimeCalendar.WorkingDayInformation workingDayInformation = new WorkingTimeCalendar.WorkingDayInformation(FULL, WORKDAY, WORKDAY);
-        final WorkingTimeCalendar workingTimeCalendar = new WorkingTimeCalendar(
-                Map.of(
-                        LocalDate.of(2024, 1, 5), workingDayInformation,
-                        LocalDate.of(2024, 1, 8), workingDayInformation));
-
-        final SickNote sickNote = SickNote.builder()
-                .id(1L)
-                .startDate(LocalDate.of(2024, 1, 5))
-                .endDate(LocalDate.of(2024, 1, 8))
-                .dayLength(FULL)
-                .person(person)
-                .sickNoteType(sickNoteType)
-                .status(SUBMITTED)
-                .workingTimeCalendar(workingTimeCalendar)
-                .build();
-
-        final SickNoteDto sickNoteDto = new SickNoteDto(sickNote);
-
-        assertThat(sickNoteDto.getId()).isEqualTo("1");
-        assertThat(sickNoteDto.getStartDate()).isEqualTo(LocalDate.of(2024, 1, 5));
-        assertThat(sickNoteDto.getWeekDayOfStartDate()).isEqualTo(FRIDAY);
-        assertThat(sickNoteDto.getEndDate()).isEqualTo(LocalDate.of(2024, 1, 8));
-        assertThat(sickNoteDto.getDayLength()).isEqualTo(FULL);
-        assertThat(sickNoteDto.getWorkDays()).isEqualTo(BigDecimal.valueOf(2L));
-        assertThat(sickNoteDto.getPerson().getId()).isEqualTo(2L);
-        assertThat(sickNoteDto.getPerson().getName()).isEqualTo("firstname lastname");
-        assertThat(sickNoteDto.getPerson().getAvatarUrl()).isEqualTo("https://gravatar.com/avatar/0c83f57c786a0b4a39efab23731c7ebc");
-        assertThat(sickNoteDto.getPerson().getIsInactive()).isFalse();
-        assertThat(sickNoteDto.getType()).isEqualTo("application.data.sicknotetype.sicknote");
-        assertThat(sickNoteDto.getStatus()).isEqualTo("SUBMITTED");
-    }
-}