Skip to content

Commit

Permalink
Make shouldWorkingHours implementation aware of multiple users (#922)
Browse files Browse the repository at this point in the history
fixes #905

ToDos:

- [x] Additional test cases
- [x] Review other methods of report day to by aware of multiple users

Here are some things you should have thought about:

**Multi-Tenancy**
- [ ] ~~Extended new entities with `AbstractTenantAwareEntity`?~~
- [ ] ~~New entity added to `TenantAwareDatabaseConfiguration`?~~
- [x] Tested with `dev-multitenant` profile?

<!--

Thanks for contributing to the zeiterfassung.
Please review the following notes before submitting you pull request.

Please look for other issues or pull requests which already work on this
topic. Is somebody already on it? Do you need to synchronize?

# Security Vulnerabilities

🛑 STOP! 🛑 If your contribution fixes a security vulnerability, please do
not submit it.
Instead, please write an E-Mail to [email protected] with all the
information
to recreate the security vulnerability.

# Describing Your Changes

If, having reviewed the notes above, you're ready to submit your pull
request, please
provide a brief description of the proposed changes.

If they:
🐞 fix a bug, please describe the broken behaviour and how the changes
fix it.
    Please label with 'type: bug' and 'status: new'
    
🎁 make an enhancement, please describe the new functionality and why you
believe it's useful.
    Please label with 'type: enhancement' and 'status: new'
 
If your pull request relates to any existing issues,
please reference them by using the issue number prefixed with #.

-->
  • Loading branch information
derTobsch authored Oct 31, 2024
2 parents 39e5764 + d1a5c5c commit 7ea85d4
Show file tree
Hide file tree
Showing 2 changed files with 123 additions and 18 deletions.
45 changes: 29 additions & 16 deletions src/main/java/de/focusshift/zeiterfassung/report/ReportDay.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,18 @@
import de.focusshift.zeiterfassung.usermanagement.UserLocalId;
import de.focusshift.zeiterfassung.workingtime.PlannedWorkingHours;

import java.time.Duration;
import java.time.LocalDate;
import java.util.AbstractMap;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Stream;

import static java.util.stream.Collectors.toMap;

record ReportDay(
LocalDate date,
Map<UserIdComposite, PlannedWorkingHours> plannedWorkingHoursByUser,
Expand All @@ -33,24 +37,33 @@ public PlannedWorkingHours plannedWorkingHours() {

public ShouldWorkingHours shouldWorkingHours() {

final double absenceDayLengthValue = detailDayAbsencesByUser.values().stream()
.flatMap(Collection::stream)
.map(ReportDayAbsence::absence)
.map(Absence::dayLength)
.map(DayLength::getValue)
.reduce(0.0, Double::sum);

if (absenceDayLengthValue >= 1.0) {
return ShouldWorkingHours.ZERO;
}

final PlannedWorkingHours plannedWorkingHours = plannedWorkingHours();

if (absenceDayLengthValue == 0.5) {
return new ShouldWorkingHours(plannedWorkingHours.duration().dividedBy(2));
final Map<UserIdComposite, Double> absenceLengthByUser = detailDayAbsencesByUser.entrySet().stream()
.map(entry ->
new AbstractMap.SimpleEntry<>(
entry.getKey(),
entry.getValue().stream().map(ReportDayAbsence::absence)
.map(Absence::dayLength)
.map(DayLength::getValue)
.reduce(0.0, Double::sum)
)
)
.collect(toMap(Map.Entry::getKey, Map.Entry::getValue));

Duration plannedWorkingHoursOfAllUsers = plannedWorkingHours().duration();
for (final Map.Entry<UserIdComposite, Double> absenceLengths : absenceLengthByUser.entrySet()) {

final Duration plannedWorkingHoursOfUser = plannedWorkingHoursByUser.get(absenceLengths.getKey()).duration();
final Double absenceLength = absenceLengths.getValue();

if (absenceLength == 1.0) {
plannedWorkingHoursOfAllUsers = plannedWorkingHoursOfAllUsers.minus(plannedWorkingHoursOfUser);
}
else if (absenceLength == 0.5) {
plannedWorkingHoursOfAllUsers = plannedWorkingHoursOfAllUsers.minus(plannedWorkingHoursOfUser.dividedBy(2));
}
}

return new ShouldWorkingHours(plannedWorkingHours.duration());
return new ShouldWorkingHours(plannedWorkingHoursOfAllUsers);
}

public PlannedWorkingHours plannedWorkingHoursByUser(UserLocalId userLocalId) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
package de.focusshift.zeiterfassung.report;

import de.focusshift.zeiterfassung.absence.Absence;
import de.focusshift.zeiterfassung.absence.DayLength;
import de.focusshift.zeiterfassung.tenancy.user.EMailAddress;
import de.focusshift.zeiterfassung.timeentry.ShouldWorkingHours;
import de.focusshift.zeiterfassung.user.UserId;
import de.focusshift.zeiterfassung.user.UserIdComposite;
import de.focusshift.zeiterfassung.usermanagement.User;
import de.focusshift.zeiterfassung.usermanagement.UserLocalId;
import de.focusshift.zeiterfassung.workingtime.PlannedWorkingHours;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import java.time.Duration;
import java.time.LocalDate;
Expand All @@ -17,12 +22,19 @@
import java.util.Map;
import java.util.Set;

import static de.focusshift.zeiterfassung.absence.AbsenceColor.RED;
import static de.focusshift.zeiterfassung.absence.AbsenceTypeCategory.HOLIDAY;
import static de.focusshift.zeiterfassung.absence.DayLength.FULL;
import static org.assertj.core.api.Assertions.assertThat;

class ReportDayTest {

private static final ZoneId ZONE_ID_BERLIN = ZoneId.of("Europe/Berlin");

private static ZonedDateTime dateTime(int year, int month, int dayOfMonth, int hour, int minute) {
return ZonedDateTime.of(LocalDateTime.of(year, month, dayOfMonth, hour, minute), ZONE_ID_BERLIN);
}

@Test
void ensureToRemoveBreaks() {

Expand All @@ -40,7 +52,87 @@ void ensureToRemoveBreaks() {
assertThat(reportDay.workDuration().duration()).isEqualTo(Duration.ZERO);
}

private static ZonedDateTime dateTime(int year, int month, int dayOfMonth, int hour, int minute) {
return ZonedDateTime.of(LocalDateTime.of(year, month, dayOfMonth, hour, minute), ZONE_ID_BERLIN);
@Test
void shouldWorkingHoursReturnsZeroForFullyAbsentUser() {

final UserId batmanId = new UserId("uuid");
final UserLocalId batmanLocalId = new UserLocalId(1337L);
final UserIdComposite batmanIdComposite = new UserIdComposite(batmanId, batmanLocalId);
final User batman = new User(batmanIdComposite, "Bruce", "Wayne", new EMailAddress("[email protected]"), Set.of());

final ZonedDateTime from = dateTime(2021, 1, 4, 1, 0);
final ZonedDateTime to = dateTime(2021, 1, 4, 2, 0);
Absence absence = new Absence(batmanId, from, to, FULL, locale -> "foo", RED, HOLIDAY);

final ReportDay reportDay = new ReportDay(LocalDate.of(2021, 1, 4), Map.of(batmanIdComposite, PlannedWorkingHours.EIGHT), Map.of(batmanIdComposite, List.of()), Map.of(batmanIdComposite, List.of(new ReportDayAbsence(batman, absence))));

assertThat(reportDay.shouldWorkingHours()).isEqualTo(new ShouldWorkingHours(Duration.ZERO));
}

@ParameterizedTest
@EnumSource(value = DayLength.class, names = {"MORNING", "NOON"})
void shouldWorkingHoursReturnsHalfShouldWorkingHoursForHalfDayAbsentUser(DayLength dayLength) {

final UserId batmanId = new UserId("uuid");
final UserLocalId batmanLocalId = new UserLocalId(1337L);
final UserIdComposite batmanIdComposite = new UserIdComposite(batmanId, batmanLocalId);
final User batman = new User(batmanIdComposite, "Bruce", "Wayne", new EMailAddress("[email protected]"), Set.of());

final ZonedDateTime from = dateTime(2021, 1, 4, 1, 0);
final ZonedDateTime to = dateTime(2021, 1, 4, 2, 0);
Absence absence = new Absence(batmanId, from, to, dayLength, locale -> "foo", RED, HOLIDAY);

final ReportDay reportDay = new ReportDay(LocalDate.of(2021, 1, 4), Map.of(batmanIdComposite, PlannedWorkingHours.EIGHT), Map.of(batmanIdComposite, List.of()), Map.of(batmanIdComposite, List.of(new ReportDayAbsence(batman, absence))));

assertThat(reportDay.shouldWorkingHours()).isEqualTo(new ShouldWorkingHours(Duration.ofHours(4)));
}

@Test
void shouldWorkingHoursReturnsShouldHoursForOneAbsentUserAndOneWorkingUser() {

final UserId batmanId = new UserId("uuid1");
final UserLocalId batmanLocalId = new UserLocalId(1337L);
final UserIdComposite batmanIdComposite = new UserIdComposite(batmanId, batmanLocalId);
final User batman = new User(batmanIdComposite, "Bruce", "Wayne", new EMailAddress("[email protected]"), Set.of());

final ZonedDateTime from = dateTime(2021, 1, 4, 1, 0);
final ZonedDateTime to = dateTime(2021, 1, 4, 2, 0);
Absence absence = new Absence(batmanId, from, to, FULL, locale -> "foo", RED, HOLIDAY);

final UserId robinId = new UserId("uuid2");
final UserLocalId robinLocalId = new UserLocalId(1337L);
final UserIdComposite robinIdComposite = new UserIdComposite(robinId, robinLocalId);

Map<UserIdComposite, List<ReportDayEntry>> entriesByUser = Map.of(batmanIdComposite, List.of(), robinIdComposite, List.of());
Map<UserIdComposite, List<ReportDayAbsence>> absencesByUser = Map.of(batmanIdComposite, List.of(new ReportDayAbsence(batman, absence)), robinIdComposite, List.of());
Map<UserIdComposite, PlannedWorkingHours> plannedWorkingHoursByUser = Map.of(batmanIdComposite, PlannedWorkingHours.EIGHT, robinIdComposite, new PlannedWorkingHours(Duration.ofHours(4L)));
final ReportDay reportDay = new ReportDay(LocalDate.of(2021, 1, 4), plannedWorkingHoursByUser, entriesByUser, absencesByUser);

assertThat(reportDay.shouldWorkingHours()).isEqualTo(new ShouldWorkingHours(Duration.ofHours(4L)));
}

@ParameterizedTest
@EnumSource(value = DayLength.class, names = {"MORNING", "NOON"})
void shouldWorkingHoursReturnsSummedShouldHoursForOneHalfAbsentUserAndOneWorkingUser(DayLength dayLength) {

final UserId batmanId = new UserId("uuid1");
final UserLocalId batmanLocalId = new UserLocalId(1337L);
final UserIdComposite batmanIdComposite = new UserIdComposite(batmanId, batmanLocalId);
final User batman = new User(batmanIdComposite, "Bruce", "Wayne", new EMailAddress("[email protected]"), Set.of());

final ZonedDateTime from = dateTime(2021, 1, 4, 1, 0);
final ZonedDateTime to = dateTime(2021, 1, 4, 2, 0);
Absence absence = new Absence(batmanId, from, to, dayLength, locale -> "foo", RED, HOLIDAY);

final UserId robinId = new UserId("uuid2");
final UserLocalId robinLocalId = new UserLocalId(1337L);
final UserIdComposite robinIdComposite = new UserIdComposite(robinId, robinLocalId);

Map<UserIdComposite, List<ReportDayEntry>> entriesByUser = Map.of(batmanIdComposite, List.of(), robinIdComposite, List.of());
Map<UserIdComposite, List<ReportDayAbsence>> absencesByUser = Map.of(batmanIdComposite, List.of(new ReportDayAbsence(batman, absence)), robinIdComposite, List.of());
Map<UserIdComposite, PlannedWorkingHours> plannedWorkingHoursByUser = Map.of(batmanIdComposite, PlannedWorkingHours.EIGHT, robinIdComposite, new PlannedWorkingHours(Duration.ofHours(4L)));
final ReportDay reportDay = new ReportDay(LocalDate.of(2021, 1, 4), plannedWorkingHoursByUser, entriesByUser, absencesByUser);

assertThat(reportDay.shouldWorkingHours()).isEqualTo(new ShouldWorkingHours(Duration.ofHours(8L)));
}
}

0 comments on commit 7ea85d4

Please sign in to comment.