Skip to content

Commit

Permalink
add user context to ReportDay
Browse files Browse the repository at this point in the history
  • Loading branch information
bseber committed May 1, 2023
1 parent 46889fe commit 3b0f488
Show file tree
Hide file tree
Showing 14 changed files with 325 additions and 181 deletions.
53 changes: 51 additions & 2 deletions src/main/java/de/focusshift/zeiterfassung/report/ReportDay.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,68 @@

import de.focusshift.zeiterfassung.timeentry.PlannedWorkingHours;
import de.focusshift.zeiterfassung.timeentry.WorkDuration;
import de.focusshift.zeiterfassung.usermanagement.UserLocalId;

import java.time.Duration;
import java.time.LocalDate;
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;

record ReportDay(LocalDate date, PlannedWorkingHours plannedWorkingHours, List<ReportDayEntry> reportDayEntries) {
record ReportDay(
LocalDate date,
Map<UserLocalId, PlannedWorkingHours> plannedWorkingHoursByUser,
Map<UserLocalId, List<ReportDayEntry>> reportDayEntriesByUser
) {

public List<ReportDayEntry> reportDayEntries() {
return reportDayEntriesByUser.values().stream().flatMap(Collection::stream).toList();
}

public PlannedWorkingHours plannedWorkingHours() {
return plannedWorkingHoursByUser.values().stream().reduce(PlannedWorkingHours.ZERO, PlannedWorkingHours::plus);
}

public PlannedWorkingHours plannedWorkingHoursByUser(UserLocalId userLocalId) {
return findValueByFirstKeyMatch(plannedWorkingHoursByUser, userLocalId::equals).orElse(PlannedWorkingHours.ZERO);
}

public WorkDuration workDuration() {
final Duration duration = reportDayEntries.stream()

final Stream<ReportDayEntry> allReportDayEntries = reportDayEntriesByUser.values()
.stream()
.flatMap(Collection::stream);

return calculateWorkDurationFrom(allReportDayEntries);
}

public WorkDuration workDurationByUser(UserLocalId userLocalId) {
return workDurationByUserPredicate(userLocalId::equals);
}

private WorkDuration workDurationByUserPredicate(Predicate<UserLocalId> predicate) {
final List<ReportDayEntry> reportDayEntries = findValueByFirstKeyMatch(reportDayEntriesByUser, predicate).orElse(List.of());
return calculateWorkDurationFrom(reportDayEntries.stream());
}

private WorkDuration calculateWorkDurationFrom(Stream<ReportDayEntry> reportDayEntries) {

final Duration duration = reportDayEntries
.map(ReportDayEntry::workDuration)
.map(WorkDuration::value)
.reduce(Duration.ZERO, Duration::plus);

return new WorkDuration(duration);
}

private <K, T> Optional<T> findValueByFirstKeyMatch(Map<K, T> map, Predicate<K> predicate) {
return map.entrySet()
.stream()
.filter(entry -> predicate.test(entry.getKey()))
.findFirst()
.map(Map.Entry::getValue);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package de.focusshift.zeiterfassung.report;

import de.focusshift.zeiterfassung.timeentry.PlannedWorkingHours;
import de.focusshift.zeiterfassung.user.UserDateService;
import de.focusshift.zeiterfassung.user.UserId;
import de.focusshift.zeiterfassung.usermanagement.UserLocalId;
Expand All @@ -11,6 +10,7 @@
import java.time.YearMonth;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.IntStream;

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

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

return new ReportWeek(startOfWeekDate, reportDays);
Expand Down
111 changes: 74 additions & 37 deletions src/main/java/de/focusshift/zeiterfassung/report/ReportServiceRaw.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,15 @@
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.IntStream;
import java.util.stream.Stream;

import static java.lang.invoke.MethodHandles.lookup;
import static java.time.temporal.ChronoUnit.MONTHS;
import static java.util.Collections.emptyList;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toMap;

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

final UserLocalId userLocalId = user.localId();

return createReportWeek(year, week,
period -> timeEntryService.getEntries(period.from(), period.toExclusive(), userId),
period -> workingTimeCalendarService.getWorkingTimes(period.from(), period.toExclusive(), List.of(user.localId())));
period -> Map.of(userLocalId, timeEntryService.getEntries(period.from(), period.toExclusive(), userId)),
period -> workingTimeCalendarService.getWorkingTimes(period.from(), period.toExclusive(), List.of(userLocalId)));
}

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

final UserLocalId userLocalId = user.localId();

return createReportMonth(yearMonth,
period -> timeEntryService.getEntries(period.from(), period.toExclusive(), userId),
period -> timeEntryService.getEntriesByUserLocalIds(period.from(), period.toExclusive(), List.of(userLocalId)),
period -> workingTimeCalendarService.getWorkingTimes(period.from(), period.toExclusive(), List.of(user.localId())));
}

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

private ReportWeek createReportWeek(Year year, int week,
Function<Period, List<TimeEntry>> timeEntriesProvider,
Function<Period, Map<UserLocalId, List<TimeEntry>>> timeEntriesProvider,
Function<Period, Map<UserLocalId, WorkingTimeCalendar>> workingTimeCalendarProvider) {

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

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

final List<TimeEntry> timeEntries = timeEntriesProvider.apply(period);
final Map<UserId, User> userById = userByIdForTimeEntries(timeEntries);
final Map<UserLocalId, List<TimeEntry>> timeEntries = timeEntriesProvider.apply(period);
final Map<UserId, User> userById = userByIdForTimeEntries(timeEntries.values().stream().flatMap(Collection::stream).toList());

final Collection<WorkingTimeCalendar> workingTimeCalendars = workingTimeCalendarProvider.apply(period).values();
final Function<LocalDate, PlannedWorkingHours> plannedWorkingTimeByDate = plannedWorkingTimeForDate(workingTimeCalendars);
final Map<UserLocalId, WorkingTimeCalendar> workingTimeCalendars = workingTimeCalendarProvider.apply(period);
final Function<LocalDate, Map<UserLocalId, PlannedWorkingHours>> plannedWorkingTimeByDate = plannedWorkingTimeForDate(workingTimeCalendars);

return reportWeek(firstDateOfWeek, timeEntries, userById, plannedWorkingTimeByDate);
}

private ReportMonth createReportMonth(YearMonth yearMonth,
Function<Period, List<TimeEntry>> timeEntriesProvider,
Function<Period, Map<UserLocalId, List<TimeEntry>>> timeEntriesProvider,
Function<Period, Map<UserLocalId, WorkingTimeCalendar>> workingTimeCalendarProvider) {

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

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

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

final Collection<WorkingTimeCalendar> workingTimeCalendars = workingTimeCalendarProvider.apply(period).values();
final Function<LocalDate, PlannedWorkingHours> plannedWorkingTimeForDate = plannedWorkingTimeForDate(workingTimeCalendars);
final Map<UserLocalId, WorkingTimeCalendar> workingTimeCalendars = workingTimeCalendarProvider.apply(period);
final Function<LocalDate, Map<UserLocalId, PlannedWorkingHours>> plannedWorkingTimeForDate = plannedWorkingTimeForDate(workingTimeCalendars);

List<ReportWeek> weeks = getStartOfWeekDatesForMonth(yearMonth)
final List<ReportWeek> weeks = getStartOfWeekDatesForMonth(yearMonth)
.stream()
.map(startOfWeekDate -> reportWeek(startOfWeekDate, timeEntries, userById, plannedWorkingTimeForDate))
.map(startOfWeekDate ->
reportWeek(
startOfWeekDate,
timeEntriesByUserId,
userById,
plannedWorkingTimeForDate
)
)
.toList();

return new ReportMonth(yearMonth, weeks);
}

private Function<LocalDate, PlannedWorkingHours> plannedWorkingTimeForDate(Collection<WorkingTimeCalendar> workingTimeCalendars) {
return date -> workingTimeCalendars.stream()
.map(cal -> cal.plannedWorkingHours(date))
.filter(Optional::isPresent)
.map(Optional::get)
.reduce(PlannedWorkingHours.ZERO, PlannedWorkingHours::plus);
private Function<LocalDate, Map<UserLocalId, PlannedWorkingHours>> plannedWorkingTimeForDate(Map<UserLocalId, WorkingTimeCalendar> workingTimeCalendars) {
return date ->
workingTimeCalendars.entrySet()
.stream()
.collect(
toMap(
Map.Entry::getKey,
entry -> entry.getValue().plannedWorkingHours(date).orElse(PlannedWorkingHours.ZERO)
)
);
}

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

private ReportWeek reportWeek(LocalDate startOfWeekDate, List<TimeEntry> timeEntries, Map<UserId, User> userById,
Function<LocalDate, PlannedWorkingHours> plannedWorkingHoursProvider) {
private ReportWeek reportWeek(LocalDate startOfWeekDate,
Map<UserLocalId, List<TimeEntry>> timeEntriesByUserLocalId,
Map<UserId, User> userById,
Function<LocalDate, Map<UserLocalId, PlannedWorkingHours>> plannedWorkingHoursProvider) {

final Map<LocalDate, List<ReportDayEntry>> reportDayEntriesByDate = timeEntries
.stream()
.flatMap(timeEntry -> timeEntryToReportDayEntries(timeEntry, userById::get))
.collect(groupingBy(reportDayEntry -> reportDayEntry.start().toLocalDate()));
final Map<LocalDate, Map<UserLocalId, List<ReportDayEntry>>> reportEntriesByDate = new HashMap<>();
for (Map.Entry<UserLocalId, List<TimeEntry>> entry : timeEntriesByUserLocalId.entrySet()) {

final UserLocalId userLocalId = entry.getKey();

final Map<LocalDate, List<ReportDayEntry>> collect = entry.getValue()
.stream()
.map(t -> timeEntryToReportDayEntry(t, userById::get))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(groupingBy(report -> report.start().toLocalDate()));

for (Map.Entry<LocalDate, List<ReportDayEntry>> localDateListEntry : collect.entrySet()) {
reportEntriesByDate.compute(localDateListEntry.getKey(), (localDate, userLocalIdListMap) -> {
final Map<UserLocalId, List<ReportDayEntry>> map = userLocalIdListMap == null ? new HashMap<>() : userLocalIdListMap;
map.put(userLocalId, collect.get(localDate));
return map;
});
}
}

final Function<LocalDate, List<ReportDayEntry>> resolveReportDayEntries =
(LocalDate date) -> reportDayEntriesByDate.getOrDefault(date, emptyList());
final Function<LocalDate, Map<UserLocalId, List<ReportDayEntry>>> resolveReportDayEntries =
(LocalDate date) -> reportEntriesByDate.getOrDefault(date, Map.of());

final List<ReportDay> reportDays = IntStream.rangeClosed(0, 6)
.mapToObj(daysToAdd -> toReportDay(startOfWeekDate.plusDays(daysToAdd), plannedWorkingHoursProvider, resolveReportDayEntries))
.mapToObj(daysToAdd ->
toReportDay(
startOfWeekDate.plusDays(daysToAdd),
plannedWorkingHoursProvider,
resolveReportDayEntries
))
.toList();

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

private static Stream<ReportDayEntry> timeEntryToReportDayEntries(TimeEntry timeEntry, Function<UserId, User> userProvider) {
private static Optional<ReportDayEntry> timeEntryToReportDayEntry(TimeEntry timeEntry, Function<UserId, User> userProvider) {

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

final ReportDayEntry first = new ReportDayEntry(user, comment, startDateTime, endDateTime, timeEntry.isBreak());
return Stream.of(first);
return Optional.of(first);
}

private static ReportDay toReportDay(LocalDate date,
Function<LocalDate, PlannedWorkingHours> plannedWorkingHoursProvider,
Function<LocalDate, List<ReportDayEntry>> resolveReportDayEntries) {
Function<LocalDate, Map<UserLocalId, PlannedWorkingHours>> plannedWorkingHoursProvider,
Function<LocalDate, Map<UserLocalId, List<ReportDayEntry>>> resolveReportDayEntries) {

return new ReportDay(date, plannedWorkingHoursProvider.apply(date), resolveReportDayEntries.apply(date));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import java.time.LocalDate;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Optional;

public interface TimeEntryService {
Expand Down Expand Up @@ -37,9 +38,9 @@ public interface TimeEntryService {
* @param from first date of interval
* @param toExclusive last date (exclusive) of interval
*
* @return unsorted list of {@linkplain TimeEntry}s.
* @return unsorted list of {@linkplain TimeEntry}s grouped by user
*/
List<TimeEntry> getEntriesForAllUsers(LocalDate from, LocalDate toExclusive);
Map<UserLocalId, List<TimeEntry>> getEntriesForAllUsers(LocalDate from, LocalDate toExclusive);

/**
* {@linkplain TimeEntry}s for all given users and interval.
Expand All @@ -48,9 +49,9 @@ public interface TimeEntryService {
* @param toExclusive last date (exclusive) of interval
* @param userLocalIds {@linkplain UserLocalId}s of desired users
*
* @return unsorted list of {@linkplain TimeEntry}s.
* @return unsorted list of {@linkplain TimeEntry}s grouped by user
*/
List<TimeEntry> getEntriesByUserLocalIds(LocalDate from, LocalDate toExclusive, List<UserLocalId> userLocalIds);
Map<UserLocalId, List<TimeEntry>> getEntriesByUserLocalIds(LocalDate from, LocalDate toExclusive, List<UserLocalId> userLocalIds);

/**
* {@linkplain TimeEntryWeekPage}s for the given user and week of year with sorted {@linkplain TimeEntry}s
Expand Down
Loading

0 comments on commit 3b0f488

Please sign in to comment.