Repository: urlaubsverwaltung/urlaubsverwaltung (java)

Issue:
[API|Persons] Ability to retrieve all persons via the Api
**Is your feature request related to a problem? Please describe.**

In order for our [ConnId](https://github.com/Tirasa/ConnId) identity connector to create new users in the UV, 
we need a way to find out which users already exist in the UV.
We must be able to retrieve both active and inactive users, 
so that our IDM system knows which users have not yet been created in the UV.

Currently, `/api/persons` will return _only active_ users, omitting inactive ones.

**Describe the solution you'd like**

ANY OF these ways:

- have the `/api/persons` endpoint return all users by default
  - Clarification: This change may break some systems
  - Optionally: Have additional filter query parameters like `active=true` to narrow down the list of returned persons.
- have the `/api/persons` endpoint accept a query parameter to return all persons instead of only the active ones
  - Clarification: `/api/persons` would retain its current behaviour of only returning active persons
  - Possible Implementation: `/api/persons?includeInactive=true` would return a list of all persons
  - Possible Implementation: `/api/persons?inactive=true` would return only inactive persons

**Optionally describe alternatives you've considered**
- Reuse the internal Controllers used by the thymeleaf UI
- Have the IDM system try to create users regardless and catch the resulting `409 CONFLICT` errors

**Optionally additional context**
N/A

Pull request:
🎁 feat: Allow retrieving all users via API
Changes:
- Calls to `/api/persons` and `/api/persons?active=true` will return only active users, to remain backwards compatible
- Calls to `/api/persons?active=false` will return all users, including inactive ones.

closes #5125

Patch:
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/person/PersonService.java b/src/main/java/org/synyx/urlaubsverwaltung/person/PersonService.java
index bfe57c3c1..25b9a0678 100644
--- a/src/main/java/org/synyx/urlaubsverwaltung/person/PersonService.java
+++ b/src/main/java/org/synyx/urlaubsverwaltung/person/PersonService.java
@@ -86,6 +86,13 @@ public interface PersonService {
      */
     List<Person> getActivePersons();
 
+    /**
+     * returns all inactive persons ordered by first name.
+     *
+     * @return returns all active persons
+     */
+    List<Person> getInactivePersons();
+
 
     /**
      * returns all persons ordered by id.
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/person/PersonServiceImpl.java b/src/main/java/org/synyx/urlaubsverwaltung/person/PersonServiceImpl.java
index 56e8b69fa..5d643bb38 100644
--- a/src/main/java/org/synyx/urlaubsverwaltung/person/PersonServiceImpl.java
+++ b/src/main/java/org/synyx/urlaubsverwaltung/person/PersonServiceImpl.java
@@ -170,6 +170,11 @@ class PersonServiceImpl implements PersonService {
         return personRepository.findByPermissionsNotContainingOrderByFirstNameAscLastNameAsc(INACTIVE);
     }
 
+    @Override
+    public List<Person> getInactivePersons() {
+        return personRepository.findByPermissionsContainingOrderByFirstNameAscLastNameAsc(INACTIVE);
+    }
+
     @Override
     public List<Person> getAllPersons() {
         return personRepository.findAllByOrderByIdAsc();
diff --git a/src/main/java/org/synyx/urlaubsverwaltung/person/api/PersonApiController.java b/src/main/java/org/synyx/urlaubsverwaltung/person/api/PersonApiController.java
index 9cdde6b99..26f87b1ba 100644
--- a/src/main/java/org/synyx/urlaubsverwaltung/person/api/PersonApiController.java
+++ b/src/main/java/org/synyx/urlaubsverwaltung/person/api/PersonApiController.java
@@ -1,6 +1,7 @@
 package org.synyx.urlaubsverwaltung.person.api;
 
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.validation.Valid;
 import org.springframework.beans.factory.annotation.Autowired;
@@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.PathVariable;
 import org.springframework.web.bind.annotation.PostMapping;
 import org.springframework.web.bind.annotation.RequestBody;
 import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RestController;
 import org.springframework.web.server.ResponseStatusException;
 import org.synyx.urlaubsverwaltung.api.RestControllerAdviceMarker;
@@ -96,9 +98,9 @@ public class PersonApiController {
     }
 
     @Operation(
-        summary = "Returns all active persons",
+        summary = "Returns all active or all inactive persons",
         description = """
-            Returns all active persons.
+            Returns all active persons (default) or all inactive persons.
 
             Needed basic authorities:
             * user
@@ -110,13 +112,19 @@ public class PersonApiController {
     )
     @GetMapping(produces = {APPLICATION_JSON_VALUE, HAL_JSON_VALUE})
     @PreAuthorize(IS_BOSS_OR_OFFICE)
-    public ResponseEntity<PersonsDto> persons() {
+    public ResponseEntity<PersonsDto> persons(
+        @Parameter(description = "Whether to return all active persons (default) or all inactive persons")
+        @RequestParam(name = "active", defaultValue = "true")
+        boolean active
+    ) {
 
-        final List<PersonDto> persons = personService.getActivePersons().stream()
+        final List<Person> persons = active ? personService.getActivePersons() : personService.getInactivePersons();
+
+        final List<PersonDto> personDtoList = persons.stream()
             .map(PersonMapper::mapToDto)
             .toList();
 
-        return new ResponseEntity<>(new PersonsDto(persons), OK);
+        return new ResponseEntity<>(new PersonsDto(personDtoList), OK);
     }
 
 


Test patch:
diff --git a/src/test/java/org/synyx/urlaubsverwaltung/person/api/PersonApiControllerIT.java b/src/test/java/org/synyx/urlaubsverwaltung/person/api/PersonApiControllerIT.java
index 09198ff18..9082f91c5 100644
--- a/src/test/java/org/synyx/urlaubsverwaltung/person/api/PersonApiControllerIT.java
+++ b/src/test/java/org/synyx/urlaubsverwaltung/person/api/PersonApiControllerIT.java
@@ -15,6 +15,7 @@ import org.springframework.web.context.WebApplicationContext;
 import org.synyx.urlaubsverwaltung.SingleTenantTestContainersBase;
 import org.synyx.urlaubsverwaltung.person.Person;
 import org.synyx.urlaubsverwaltung.person.PersonService;
+import org.synyx.urlaubsverwaltung.person.Role;
 
 import java.util.List;
 import java.util.Optional;
@@ -225,6 +226,57 @@ class PersonApiControllerIT extends SingleTenantTestContainersBase {
                         """, STRICT));
     }
 
+    @Test
+    void ensureReturnsInactivePersons() throws Exception {
+
+        final Person carl = new Person("carl@example.org", "carl", "carl", "carl@example.org");
+        carl.setPermissions(List.of(Role.INACTIVE));
+        carl.setId(2L);
+
+        when(personService.getInactivePersons()).thenReturn(List.of(carl));
+
+        perform(get("/api/persons").queryParam("active", "false")
+            .with(oidcLogin().idToken(builder -> builder.subject("shane@example.org")).authorities(new SimpleGrantedAuthority("USER"), new SimpleGrantedAuthority("OFFICE")))
+            .accept(HAL_JSON_VALUE)
+        )
+            .andExpect(status().isOk())
+            .andExpect(content().contentType(HAL_JSON_VALUE))
+            .andExpect(content().json("""
+                        {
+                           "persons": [
+                             {
+                               "id": 2,
+                               "email": "carl@example.org",
+                               "firstName": "carl",
+                               "lastName": "carl",
+                               "niceName": "carl carl",
+                               "_links": {
+                                 "self": {
+                                   "href": "http://localhost/api/persons/2"
+                                 },
+                                 "absences": {
+                                   "href": "http://localhost/api/persons/2/absences?from={from}&to={to}&absence-types=vacation&absence-types=sick_note&absence-types=public_holiday&absence-types=no_workday",
+                                   "templated": true
+                                 },
+                                 "sicknotes": {
+                                   "href": "http://localhost/api/persons/2/sicknotes?from={from}&to={to}",
+                                   "templated": true
+                                 },
+                                 "vacations": {
+                                   "href": "http://localhost/api/persons/2/vacations?from={from}&to={to}&status=waiting&status=temporary_allowed&status=allowed&status=allowed_cancellation_requested",
+                                   "templated": true
+                                 },
+                                 "workdays": {
+                                   "href": "http://localhost/api/persons/2/workdays?from={from}&to={to}{&length}",
+                                   "templated": true
+                                 }
+                               }
+                             }
+                           ]
+                         }
+                        """, STRICT));
+    }
+
     @Test
     void ensureToCreateNewPerson() throws Exception {