Skip to content

Commit 3006830

Browse files
authored
add user dimension to report domain (#249)
as preparation for #248
2 parents 6a88bde + 7a1a712 commit 3006830

14 files changed

+355
-184
lines changed

src/main/java/de/focusshift/zeiterfassung/report/ReportDay.java

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,68 @@
22

33
import de.focusshift.zeiterfassung.timeentry.PlannedWorkingHours;
44
import de.focusshift.zeiterfassung.timeentry.WorkDuration;
5+
import de.focusshift.zeiterfassung.usermanagement.UserLocalId;
56

67
import java.time.Duration;
78
import java.time.LocalDate;
9+
import java.util.Collection;
810
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Optional;
13+
import java.util.function.Predicate;
14+
import java.util.stream.Stream;
915

10-
record ReportDay(LocalDate date, PlannedWorkingHours plannedWorkingHours, List<ReportDayEntry> reportDayEntries) {
16+
record ReportDay(
17+
LocalDate date,
18+
Map<UserLocalId, PlannedWorkingHours> plannedWorkingHoursByUser,
19+
Map<UserLocalId, List<ReportDayEntry>> reportDayEntriesByUser
20+
) {
21+
22+
public List<ReportDayEntry> reportDayEntries() {
23+
return reportDayEntriesByUser.values().stream().flatMap(Collection::stream).toList();
24+
}
25+
26+
public PlannedWorkingHours plannedWorkingHours() {
27+
return plannedWorkingHoursByUser.values().stream().reduce(PlannedWorkingHours.ZERO, PlannedWorkingHours::plus);
28+
}
29+
30+
public PlannedWorkingHours plannedWorkingHoursByUser(UserLocalId userLocalId) {
31+
return findValueByFirstKeyMatch(plannedWorkingHoursByUser, userLocalId::equals).orElse(PlannedWorkingHours.ZERO);
32+
}
1133

1234
public WorkDuration workDuration() {
13-
final Duration duration = reportDayEntries.stream()
35+
36+
final Stream<ReportDayEntry> allReportDayEntries = reportDayEntriesByUser.values()
37+
.stream()
38+
.flatMap(Collection::stream);
39+
40+
return calculateWorkDurationFrom(allReportDayEntries);
41+
}
42+
43+
public WorkDuration workDurationByUser(UserLocalId userLocalId) {
44+
return workDurationByUserPredicate(userLocalId::equals);
45+
}
46+
47+
private WorkDuration workDurationByUserPredicate(Predicate<UserLocalId> predicate) {
48+
final List<ReportDayEntry> reportDayEntries = findValueByFirstKeyMatch(reportDayEntriesByUser, predicate).orElse(List.of());
49+
return calculateWorkDurationFrom(reportDayEntries.stream());
50+
}
51+
52+
private WorkDuration calculateWorkDurationFrom(Stream<ReportDayEntry> reportDayEntries) {
53+
54+
final Duration duration = reportDayEntries
1455
.map(ReportDayEntry::workDuration)
1556
.map(WorkDuration::value)
1657
.reduce(Duration.ZERO, Duration::plus);
1758

1859
return new WorkDuration(duration);
1960
}
61+
62+
private <K, T> Optional<T> findValueByFirstKeyMatch(Map<K, T> map, Predicate<K> predicate) {
63+
return map.entrySet()
64+
.stream()
65+
.filter(entry -> predicate.test(entry.getKey()))
66+
.findFirst()
67+
.map(Map.Entry::getValue);
68+
}
2069
}

src/main/java/de/focusshift/zeiterfassung/report/ReportServicePermissionAware.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package de.focusshift.zeiterfassung.report;
22

3-
import de.focusshift.zeiterfassung.timeentry.PlannedWorkingHours;
43
import de.focusshift.zeiterfassung.user.UserDateService;
54
import de.focusshift.zeiterfassung.user.UserId;
65
import de.focusshift.zeiterfassung.usermanagement.UserLocalId;
@@ -11,6 +10,7 @@
1110
import java.time.YearMonth;
1211
import java.util.ArrayList;
1312
import java.util.List;
13+
import java.util.Map;
1414
import java.util.stream.IntStream;
1515

1616
import static java.time.temporal.ChronoUnit.MONTHS;
@@ -111,7 +111,7 @@ private ReportWeek emptyReportWeek(Year year, int week) {
111111

112112
private ReportWeek emptyReportWeek(LocalDate startOfWeekDate) {
113113
final List<ReportDay> reportDays = IntStream.rangeClosed(0, 6)
114-
.mapToObj(daysToAdd -> new ReportDay(startOfWeekDate.plusDays(daysToAdd), PlannedWorkingHours.ZERO, List.of()))
114+
.mapToObj(daysToAdd -> new ReportDay(startOfWeekDate.plusDays(daysToAdd), Map.of(), Map.of()))
115115
.toList();
116116

117117
return new ReportWeek(startOfWeekDate, reportDays);

src/main/java/de/focusshift/zeiterfassung/report/ReportServiceRaw.java

Lines changed: 74 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,15 @@
2020
import java.time.ZonedDateTime;
2121
import java.util.ArrayList;
2222
import java.util.Collection;
23+
import java.util.HashMap;
2324
import java.util.List;
2425
import java.util.Map;
2526
import java.util.Optional;
2627
import java.util.function.Function;
2728
import java.util.stream.IntStream;
28-
import java.util.stream.Stream;
2929

3030
import static java.lang.invoke.MethodHandles.lookup;
3131
import static java.time.temporal.ChronoUnit.MONTHS;
32-
import static java.util.Collections.emptyList;
3332
import static java.util.stream.Collectors.groupingBy;
3433
import static java.util.stream.Collectors.toMap;
3534

@@ -60,9 +59,11 @@ ReportWeek getReportWeek(Year year, int week, UserId userId) {
6059
final User user = userManagementService.findUserById(userId)
6160
.orElseThrow(() -> new IllegalStateException("could not find user id=%s".formatted(userId)));
6261

62+
final UserLocalId userLocalId = user.localId();
63+
6364
return createReportWeek(year, week,
64-
period -> timeEntryService.getEntries(period.from(), period.toExclusive(), userId),
65-
period -> workingTimeCalendarService.getWorkingTimes(period.from(), period.toExclusive(), List.of(user.localId())));
65+
period -> Map.of(userLocalId, timeEntryService.getEntries(period.from(), period.toExclusive(), userId)),
66+
period -> workingTimeCalendarService.getWorkingTimes(period.from(), period.toExclusive(), List.of(userLocalId)));
6667
}
6768

6869
ReportWeek getReportWeek(Year year, int week, List<UserLocalId> userLocalIds) {
@@ -82,8 +83,10 @@ ReportMonth getReportMonth(YearMonth yearMonth, UserId userId) {
8283
final User user = userManagementService.findUserById(userId)
8384
.orElseThrow(() -> new IllegalStateException("could not find user id=%s".formatted(userId)));
8485

86+
final UserLocalId userLocalId = user.localId();
87+
8588
return createReportMonth(yearMonth,
86-
period -> timeEntryService.getEntries(period.from(), period.toExclusive(), userId),
89+
period -> timeEntryService.getEntriesByUserLocalIds(period.from(), period.toExclusive(), List.of(userLocalId)),
8790
period -> workingTimeCalendarService.getWorkingTimes(period.from(), period.toExclusive(), List.of(user.localId())));
8891
}
8992

@@ -100,50 +103,61 @@ ReportMonth getReportMonthForAllUsers(YearMonth yearMonth) {
100103
}
101104

102105
private ReportWeek createReportWeek(Year year, int week,
103-
Function<Period, List<TimeEntry>> timeEntriesProvider,
106+
Function<Period, Map<UserLocalId, List<TimeEntry>>> timeEntriesProvider,
104107
Function<Period, Map<UserLocalId, WorkingTimeCalendar>> workingTimeCalendarProvider) {
105108

106109
final LocalDate firstDateOfWeek = userDateService.firstDayOfWeek(year, week);
107110

108111
final Period period = new Period(firstDateOfWeek, firstDateOfWeek.plusWeeks(1));
109112

110-
final List<TimeEntry> timeEntries = timeEntriesProvider.apply(period);
111-
final Map<UserId, User> userById = userByIdForTimeEntries(timeEntries);
113+
final Map<UserLocalId, List<TimeEntry>> timeEntries = timeEntriesProvider.apply(period);
114+
final Map<UserId, User> userById = userByIdForTimeEntries(timeEntries.values().stream().flatMap(Collection::stream).toList());
112115

113-
final Collection<WorkingTimeCalendar> workingTimeCalendars = workingTimeCalendarProvider.apply(period).values();
114-
final Function<LocalDate, PlannedWorkingHours> plannedWorkingTimeByDate = plannedWorkingTimeForDate(workingTimeCalendars);
116+
final Map<UserLocalId, WorkingTimeCalendar> workingTimeCalendars = workingTimeCalendarProvider.apply(period);
117+
final Function<LocalDate, Map<UserLocalId, PlannedWorkingHours>> plannedWorkingTimeByDate = plannedWorkingTimeForDate(workingTimeCalendars);
115118

116119
return reportWeek(firstDateOfWeek, timeEntries, userById, plannedWorkingTimeByDate);
117120
}
118121

119122
private ReportMonth createReportMonth(YearMonth yearMonth,
120-
Function<Period, List<TimeEntry>> timeEntriesProvider,
123+
Function<Period, Map<UserLocalId, List<TimeEntry>>> timeEntriesProvider,
121124
Function<Period, Map<UserLocalId, WorkingTimeCalendar>> workingTimeCalendarProvider) {
122125

123126
final LocalDate firstOfMonth = LocalDate.of(yearMonth.getYear(), yearMonth.getMonthValue(), 1);
124127

125128
final Period period = new Period(firstOfMonth, firstOfMonth.plusMonths(1));
126129

127-
final List<TimeEntry> timeEntries = timeEntriesProvider.apply(period);
128-
final Map<UserId, User> userById = userByIdForTimeEntries(timeEntries);
130+
final Map<UserLocalId, List<TimeEntry>> timeEntriesByUserId = timeEntriesProvider.apply(period);
131+
final Map<UserId, User> userById = userByIdForTimeEntries(timeEntriesByUserId.values().stream().flatMap(Collection::stream).toList());
129132

130-
final Collection<WorkingTimeCalendar> workingTimeCalendars = workingTimeCalendarProvider.apply(period).values();
131-
final Function<LocalDate, PlannedWorkingHours> plannedWorkingTimeForDate = plannedWorkingTimeForDate(workingTimeCalendars);
133+
final Map<UserLocalId, WorkingTimeCalendar> workingTimeCalendars = workingTimeCalendarProvider.apply(period);
134+
final Function<LocalDate, Map<UserLocalId, PlannedWorkingHours>> plannedWorkingTimeForDate = plannedWorkingTimeForDate(workingTimeCalendars);
132135

133-
List<ReportWeek> weeks = getStartOfWeekDatesForMonth(yearMonth)
136+
final List<ReportWeek> weeks = getStartOfWeekDatesForMonth(yearMonth)
134137
.stream()
135-
.map(startOfWeekDate -> reportWeek(startOfWeekDate, timeEntries, userById, plannedWorkingTimeForDate))
138+
.map(startOfWeekDate ->
139+
reportWeek(
140+
startOfWeekDate,
141+
timeEntriesByUserId,
142+
userById,
143+
plannedWorkingTimeForDate
144+
)
145+
)
136146
.toList();
137147

138148
return new ReportMonth(yearMonth, weeks);
139149
}
140150

141-
private Function<LocalDate, PlannedWorkingHours> plannedWorkingTimeForDate(Collection<WorkingTimeCalendar> workingTimeCalendars) {
142-
return date -> workingTimeCalendars.stream()
143-
.map(cal -> cal.plannedWorkingHours(date))
144-
.filter(Optional::isPresent)
145-
.map(Optional::get)
146-
.reduce(PlannedWorkingHours.ZERO, PlannedWorkingHours::plus);
151+
private Function<LocalDate, Map<UserLocalId, PlannedWorkingHours>> plannedWorkingTimeForDate(Map<UserLocalId, WorkingTimeCalendar> workingTimeCalendars) {
152+
return date ->
153+
workingTimeCalendars.entrySet()
154+
.stream()
155+
.collect(
156+
toMap(
157+
Map.Entry::getKey,
158+
entry -> entry.getValue().plannedWorkingHours(date).orElse(PlannedWorkingHours.ZERO)
159+
)
160+
);
147161
}
148162

149163
private Map<UserId, User> userByIdForTimeEntries(List<TimeEntry> timeEntries) {
@@ -153,19 +167,42 @@ private Map<UserId, User> userByIdForTimeEntries(List<TimeEntry> timeEntries) {
153167
.collect(toMap(User::id, Function.identity()));
154168
}
155169

156-
private ReportWeek reportWeek(LocalDate startOfWeekDate, List<TimeEntry> timeEntries, Map<UserId, User> userById,
157-
Function<LocalDate, PlannedWorkingHours> plannedWorkingHoursProvider) {
170+
private ReportWeek reportWeek(LocalDate startOfWeekDate,
171+
Map<UserLocalId, List<TimeEntry>> timeEntriesByUserLocalId,
172+
Map<UserId, User> userById,
173+
Function<LocalDate, Map<UserLocalId, PlannedWorkingHours>> plannedWorkingHoursProvider) {
158174

159-
final Map<LocalDate, List<ReportDayEntry>> reportDayEntriesByDate = timeEntries
160-
.stream()
161-
.flatMap(timeEntry -> timeEntryToReportDayEntries(timeEntry, userById::get))
162-
.collect(groupingBy(reportDayEntry -> reportDayEntry.start().toLocalDate()));
175+
final Map<LocalDate, Map<UserLocalId, List<ReportDayEntry>>> reportEntriesByDate = new HashMap<>();
176+
for (Map.Entry<UserLocalId, List<TimeEntry>> entry : timeEntriesByUserLocalId.entrySet()) {
177+
178+
final UserLocalId userLocalId = entry.getKey();
179+
180+
final Map<LocalDate, List<ReportDayEntry>> collect = entry.getValue()
181+
.stream()
182+
.map(t -> timeEntryToReportDayEntry(t, userById::get))
183+
.filter(Optional::isPresent)
184+
.map(Optional::get)
185+
.collect(groupingBy(report -> report.start().toLocalDate()));
186+
187+
for (Map.Entry<LocalDate, List<ReportDayEntry>> localDateListEntry : collect.entrySet()) {
188+
reportEntriesByDate.compute(localDateListEntry.getKey(), (localDate, userLocalIdListMap) -> {
189+
final Map<UserLocalId, List<ReportDayEntry>> map = userLocalIdListMap == null ? new HashMap<>() : userLocalIdListMap;
190+
map.put(userLocalId, collect.get(localDate));
191+
return map;
192+
});
193+
}
194+
}
163195

164-
final Function<LocalDate, List<ReportDayEntry>> resolveReportDayEntries =
165-
(LocalDate date) -> reportDayEntriesByDate.getOrDefault(date, emptyList());
196+
final Function<LocalDate, Map<UserLocalId, List<ReportDayEntry>>> resolveReportDayEntries =
197+
(LocalDate date) -> reportEntriesByDate.getOrDefault(date, Map.of());
166198

167199
final List<ReportDay> reportDays = IntStream.rangeClosed(0, 6)
168-
.mapToObj(daysToAdd -> toReportDay(startOfWeekDate.plusDays(daysToAdd), plannedWorkingHoursProvider, resolveReportDayEntries))
200+
.mapToObj(daysToAdd ->
201+
toReportDay(
202+
startOfWeekDate.plusDays(daysToAdd),
203+
plannedWorkingHoursProvider,
204+
resolveReportDayEntries
205+
))
169206
.toList();
170207

171208
return new ReportWeek(startOfWeekDate, reportDays);
@@ -185,7 +222,7 @@ private List<LocalDate> getStartOfWeekDatesForMonth(YearMonth yearMonth) {
185222
return startOfWeekDates;
186223
}
187224

188-
private static Stream<ReportDayEntry> timeEntryToReportDayEntries(TimeEntry timeEntry, Function<UserId, User> userProvider) {
225+
private static Optional<ReportDayEntry> timeEntryToReportDayEntry(TimeEntry timeEntry, Function<UserId, User> userProvider) {
189226

190227
final String comment = timeEntry.comment();
191228
final ZonedDateTime startDateTime = timeEntry.start();
@@ -194,16 +231,16 @@ private static Stream<ReportDayEntry> timeEntryToReportDayEntries(TimeEntry time
194231
final User user = userProvider.apply(timeEntry.userId());
195232
if (user == null) {
196233
LOG.info("could not find user with id={} for timeEntry={} while generating report.", timeEntry.userId(), timeEntry.id());
197-
return Stream.empty();
234+
return Optional.empty();
198235
}
199236

200237
final ReportDayEntry first = new ReportDayEntry(user, comment, startDateTime, endDateTime, timeEntry.isBreak());
201-
return Stream.of(first);
238+
return Optional.of(first);
202239
}
203240

204241
private static ReportDay toReportDay(LocalDate date,
205-
Function<LocalDate, PlannedWorkingHours> plannedWorkingHoursProvider,
206-
Function<LocalDate, List<ReportDayEntry>> resolveReportDayEntries) {
242+
Function<LocalDate, Map<UserLocalId, PlannedWorkingHours>> plannedWorkingHoursProvider,
243+
Function<LocalDate, Map<UserLocalId, List<ReportDayEntry>>> resolveReportDayEntries) {
207244

208245
return new ReportDay(date, plannedWorkingHoursProvider.apply(date), resolveReportDayEntries.apply(date));
209246
}

src/main/java/de/focusshift/zeiterfassung/timeentry/TimeEntryService.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import java.time.LocalDate;
99
import java.time.ZonedDateTime;
1010
import java.util.List;
11+
import java.util.Map;
1112
import java.util.Optional;
1213

1314
public interface TimeEntryService {
@@ -37,9 +38,9 @@ public interface TimeEntryService {
3738
* @param from first date of interval
3839
* @param toExclusive last date (exclusive) of interval
3940
*
40-
* @return unsorted list of {@linkplain TimeEntry}s.
41+
* @return unsorted list of {@linkplain TimeEntry}s grouped by user
4142
*/
42-
List<TimeEntry> getEntriesForAllUsers(LocalDate from, LocalDate toExclusive);
43+
Map<UserLocalId, List<TimeEntry>> getEntriesForAllUsers(LocalDate from, LocalDate toExclusive);
4344

4445
/**
4546
* {@linkplain TimeEntry}s for all given users and interval.
@@ -48,9 +49,9 @@ public interface TimeEntryService {
4849
* @param toExclusive last date (exclusive) of interval
4950
* @param userLocalIds {@linkplain UserLocalId}s of desired users
5051
*
51-
* @return unsorted list of {@linkplain TimeEntry}s.
52+
* @return unsorted list of {@linkplain TimeEntry}s grouped by user
5253
*/
53-
List<TimeEntry> getEntriesByUserLocalIds(LocalDate from, LocalDate toExclusive, List<UserLocalId> userLocalIds);
54+
Map<UserLocalId, List<TimeEntry>> getEntriesByUserLocalIds(LocalDate from, LocalDate toExclusive, List<UserLocalId> userLocalIds);
5455

5556
/**
5657
* {@linkplain TimeEntryWeekPage}s for the given user and week of year with sorted {@linkplain TimeEntry}s

0 commit comments

Comments
 (0)