From 17a0e29dad957424044f3823f9e127aff1ee3824 Mon Sep 17 00:00:00 2001 From: Denis Natusch Date: Sat, 2 Dec 2023 16:31:58 +0100 Subject: [PATCH] Add getAvailableStaffByJob to staff package Co-authored-by: Simon Bruder --- src/main/asciidoc/models/design/staff.puml | 10 +- src/main/asciidoc/models/design/staff.svg | 4 +- .../java/catering/staff/StaffController.java | 5 +- .../java/catering/staff/StaffManagement.java | 77 +++++++++++++++- .../StaffManagementIntegrationTests.java | 91 +++++++++++++++++++ 5 files changed, 178 insertions(+), 9 deletions(-) create mode 100644 src/test/java/catering/staff/StaffManagementIntegrationTests.java diff --git a/src/main/asciidoc/models/design/staff.puml b/src/main/asciidoc/models/design/staff.puml index 131e32f..8b227cb 100644 --- a/src/main/asciidoc/models/design/staff.puml +++ b/src/main/asciidoc/models/design/staff.puml @@ -10,6 +10,11 @@ package Spring { class Model } +package catering.order { + class OrderManagement + class CustomOrder +} + package catering.staff { class Employee { @@ -48,13 +53,16 @@ package catering.staff { StaffController ..> Errors class StaffManagement { - + StaffManagement(staffRepository: StaffRepository) + + StaffManagement(staffRepository: StaffRepository, orderManagement:OrderManagement) + findById(id: Long): Optional + save(employee: Employee): Employee + findAll(): Streamable + delete(id: Long): void + + getAvailableStaffByJob(job: JobType, start:LocalDateTime, finish:LocalDateTime): Set } StaffManagement --> StaffRepository : -staffRepository + StaffManagement --> OrderManagement : -orderManagement + StaffManagement ..> Set interface StaffRepository { + findAll(): Streamable diff --git a/src/main/asciidoc/models/design/staff.svg b/src/main/asciidoc/models/design/staff.svg index 646af03..8358674 100644 --- a/src/main/asciidoc/models/design/staff.svg +++ b/src/main/asciidoc/models/design/staff.svg @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e21a55b26fdb047ad17f31b1811043524c82d78b8a1c6181168630b606c5d0d -size 28806 +oid sha256:a2f14d414c6c0ba9730741ccd2d76c70b7a1a87f0246e81f5b957fe0fd58390f +size 34925 diff --git a/src/main/java/catering/staff/StaffController.java b/src/main/java/catering/staff/StaffController.java index 79c9696..13b6231 100644 --- a/src/main/java/catering/staff/StaffController.java +++ b/src/main/java/catering/staff/StaffController.java @@ -2,6 +2,8 @@ package catering.staff; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; @@ -10,9 +12,6 @@ import org.springframework.web.bind.annotation.RequestParam; import jakarta.validation.Valid; -import org.springframework.ui.Model; -import org.springframework.validation.Errors; - @Controller public class StaffController { diff --git a/src/main/java/catering/staff/StaffManagement.java b/src/main/java/catering/staff/StaffManagement.java index 72c694c..3844946 100644 --- a/src/main/java/catering/staff/StaffManagement.java +++ b/src/main/java/catering/staff/StaffManagement.java @@ -1,10 +1,19 @@ package catering.staff; +import java.time.LocalDateTime; import java.util.Optional; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +import org.salespointframework.order.OrderManagement; +import org.springframework.data.domain.Pageable; +import org.springframework.data.util.Streamable; +import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.SessionAttributes; -import org.springframework.stereotype.Component; -import org.springframework.data.util.Streamable; + +import catering.order.CustomOrder; @Component @Transactional @@ -12,9 +21,11 @@ import org.springframework.data.util.Streamable; public class StaffManagement { private final StaffRepository staffRepository; + private final OrderManagement orderManagement; - public StaffManagement(StaffRepository staffRepository) { + public StaffManagement(StaffRepository staffRepository, OrderManagement orderManagement) { this.staffRepository = staffRepository; + this.orderManagement = orderManagement; } public Optional findById(Long id) { @@ -33,4 +44,64 @@ public class StaffManagement { staffRepository.deleteById(id); } + /** + * Returns a {@link Set} of all {@link Employee}s of given {@link JobType} + * available in the full time range between the given start and end + * {@link LocalDateTime}s. + * + * The range is inclusive, e.g., {@link Employee}s part of an order that lasts + * until the given start + * are not considered available. + * + * @param job the {@link JobType} of {@link Employee}s to search for + * @param start a {@link LocalDateTime} describing the start of the range the + * employee is needed for + * @param finish a {@link LocalDateTime} describing the end of the range the + * employee is needed for + * @return a {@link Set} of {@link Employee}s matching the requirements + */ + public Set getAvailableStaffByJob(JobType job, LocalDateTime start, LocalDateTime finish) { + /* + * This function first computes all unavailable employees + * to then later take the set difference of all employees and the previous + * result + * to get a set of all employees available in the given range. + */ + Set unavailable = orderManagement.findAll(Pageable.unpaged()).stream() + /* + * The most complex part is getting all orders overlapping the given range. + * It can be computed with the following: + * Let (S, F) be the tuple describing the start and finish of any given order O, + * let (S', F') be the tuple describing the start and finish of the given date range. + * Then the following holds: + * + * @formatter:off + * O overlaps with (S', F') + * ⇔ F ≥ S' ∧ S ≤ F' + * ⇔ ¬¬(F ≥ S' ∧ S ≤ F') (¬¬X ⇔ X ∀ X ∈ {0,1}) + * ⇔ ¬(¬(F ≥ S') ∨ ¬(S ≤ F')) (De Morgan’s laws: ¬(X ∧ Y) ⇔ ¬X ∨ ¬Y ∀ X,Y ∈ {0,1}) + * ⇔ ¬(F < S' ∨ S > F') (¬(a ≥ b) ⇔ a < b and ¬(a ≤ b) ⇔ a > b ∀ a,b totally ordered) + * ⇔ ¬(F < S') ∧ ¬(S > F') (De Morgan’s laws: ¬(X ∨ Y) ⇔ ¬X ∧ ¬Y ∀ X,Y ∈ {0,1}) + * @formatter:on + * + * The last expression can be easily implemented with two filters, + * as chaining filters is equivalent to the logical conjunction, + * using LocalDateTime::isBefore and LocalDateTime::isAfter, + * which are < and > on the total ordering of all LocalDateTimes. + * + * Sadly, we can’t really avoid lambdas here + * as function composition and partial application are not first-class citizens in Java. + */ + .filter(order -> !order.getFinish().isBefore(start)) + .filter(order -> !order.getStart().isAfter(finish)) + .map(CustomOrder::getStaff) + .flatMap(Set::stream) + .filter(employee -> employee.getJob().equals(job)) + .collect(Collectors.toSet()); + return findAll().stream() + .filter(e -> e.getJob().equals(job)) + .filter(Predicate.not(unavailable::contains)) + .collect(Collectors.toSet()); + } + } diff --git a/src/test/java/catering/staff/StaffManagementIntegrationTests.java b/src/test/java/catering/staff/StaffManagementIntegrationTests.java new file mode 100644 index 0000000..77ad897 --- /dev/null +++ b/src/test/java/catering/staff/StaffManagementIntegrationTests.java @@ -0,0 +1,91 @@ +package catering.staff; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.time.LocalDateTime; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.salespointframework.order.OrderManagement; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import catering.order.CustomCart; +import catering.order.CustomOrder; +import catering.order.OrderType; +import catering.users.User; +import catering.users.UserManagement; + +@SpringBootTest +class StaffManagmentIntegratonTest { + + @Autowired + private StaffManagement staffManagement; + + @Autowired + private OrderManagement orderManagement; + + @Autowired + private UserManagement userManagement; + + private User defaultUser; + + Employee e1; + Employee e2; + Employee e3; + Employee e4; + Employee e5; + Employee e6; + + CustomOrder c1; + CustomOrder c2; + CustomOrder c3; + CustomOrder c4; + CustomOrder c5; + CustomOrder c6; + CustomOrder c7; + + CustomOrder createCustomOrder(LocalDateTime start, LocalDateTime end, Set staff) { + CustomOrder order = orderManagement.save(new CustomOrder(defaultUser.getUserAccount().getId(), + new CustomCart(OrderType.EVENT_CATERING, start, end))); + staff.forEach(order::addEmployee); + return orderManagement.save(order); + } + + @BeforeEach + void addEmployees() { + defaultUser = userManagement.createCustomer("sarah", "Baum Weg", "123", "Sarah Klaus"); + + e1 = staffManagement.save(new Employee("Alan Turing", JobType.COOK)); + e2 = staffManagement.save(new Employee("Ada Lovelace", JobType.COOK)); + e3 = staffManagement.save(new Employee("Donald Knuth", JobType.COOK)); + e4 = staffManagement.save(new Employee("Grace Hopper", JobType.SERVICE)); + e5 = staffManagement.save(new Employee("John von Neumann", JobType.SERVICE)); + e6 = staffManagement.save(new Employee("Noam Chomsky", JobType.SERVICE)); + + c1 = createCustomOrder(LocalDateTime.of(2023, 10, 27, 6, 0), LocalDateTime.of(2023, 10, 27, 20, 0), Set.of(e1)); + c2 = createCustomOrder(LocalDateTime.of(2023, 10, 27, 8, 0), LocalDateTime.of(2023, 10, 27, 16, 0), Set.of(e2)); + c3 = createCustomOrder(LocalDateTime.of(2023, 10, 27, 18, 0), LocalDateTime.of(2023, 10, 27, 22, 0), + Set.of(e3)); + c4 = createCustomOrder(LocalDateTime.of(2023, 10, 27, 16, 0), LocalDateTime.of(2023, 10, 27, 18, 0), + Set.of(e4)); + c5 = createCustomOrder(LocalDateTime.of(2023, 10, 27, 8, 0), LocalDateTime.of(2023, 10, 27, 12, 0), Set.of(e5)); + c6 = createCustomOrder(LocalDateTime.of(2023, 10, 21, 12, 0), LocalDateTime.of(2023, 10, 21, 22, 0), + Set.of(e6)); + c7 = createCustomOrder(LocalDateTime.of(2023, 10, 27, 19, 0), LocalDateTime.of(2023, 10, 28, 12, 0), Set.of(e2, e3)); + } + + @Test + void getAvailableEmployees() throws Exception { + Set availableService = staffManagement.getAvailableStaffByJob(JobType.SERVICE, + LocalDateTime.of(2023, 10, 27, 16, 0), LocalDateTime.of(2023, 10, 27, 18, 0)); + Set availableCook = staffManagement.getAvailableStaffByJob(JobType.COOK, + LocalDateTime.of(2023, 10, 27, 16, 0), LocalDateTime.of(2023, 10, 27, 18, 0)); + + assertThat(availableCook.size()).isEqualTo(0); + assertThat(availableService.size()).isEqualTo(2); + assertThat(availableService).contains(e5, e6); + } + +}