Add getAvailableStaffByJob to staff package

Co-authored-by: Simon Bruder <simon.bruder@mailbox.tu-dresden.de>
This commit is contained in:
Denis Natusch 2023-12-02 16:31:58 +01:00 committed by Simon Bruder
parent 2dff2842fc
commit 17a0e29dad
Signed by: simon
GPG key ID: 8D3C82F9F309F8EC
5 changed files with 178 additions and 9 deletions

View file

@ -10,6 +10,11 @@ package Spring {
class Model class Model
} }
package catering.order {
class OrderManagement
class CustomOrder
}
package catering.staff { package catering.staff {
class Employee { class Employee {
@ -48,13 +53,16 @@ package catering.staff {
StaffController ..> Errors StaffController ..> Errors
class StaffManagement { class StaffManagement {
+ StaffManagement(staffRepository: StaffRepository) + StaffManagement(staffRepository: StaffRepository, orderManagement:OrderManagement<CustomOrder>)
+ findById(id: Long): Optional<Employee> + findById(id: Long): Optional<Employee>
+ save(employee: Employee): Employee + save(employee: Employee): Employee
+ findAll(): Streamable<Employee> + findAll(): Streamable<Employee>
+ delete(id: Long): void + delete(id: Long): void
+ getAvailableStaffByJob(job: JobType, start:LocalDateTime, finish:LocalDateTime): Set<Employee>
} }
StaffManagement --> StaffRepository : -staffRepository StaffManagement --> StaffRepository : -staffRepository
StaffManagement --> OrderManagement : -orderManagement
StaffManagement ..> Set
interface StaffRepository { interface StaffRepository {
+ findAll(): Streamable<Employee> + findAll(): Streamable<Employee>

BIN
src/main/asciidoc/models/design/staff.svg (Stored with Git LFS)

Binary file not shown.

View file

@ -2,6 +2,8 @@ package catering.staff;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller; 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.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
@ -10,9 +12,6 @@ import org.springframework.web.bind.annotation.RequestParam;
import jakarta.validation.Valid; import jakarta.validation.Valid;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
@Controller @Controller
public class StaffController { public class StaffController {

View file

@ -1,10 +1,19 @@
package catering.staff; package catering.staff;
import java.time.LocalDateTime;
import java.util.Optional; 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.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.SessionAttributes; import org.springframework.web.bind.annotation.SessionAttributes;
import org.springframework.stereotype.Component;
import org.springframework.data.util.Streamable; import catering.order.CustomOrder;
@Component @Component
@Transactional @Transactional
@ -12,9 +21,11 @@ import org.springframework.data.util.Streamable;
public class StaffManagement { public class StaffManagement {
private final StaffRepository staffRepository; private final StaffRepository staffRepository;
private final OrderManagement<CustomOrder> orderManagement;
public StaffManagement(StaffRepository staffRepository) { public StaffManagement(StaffRepository staffRepository, OrderManagement<CustomOrder> orderManagement) {
this.staffRepository = staffRepository; this.staffRepository = staffRepository;
this.orderManagement = orderManagement;
} }
public Optional<Employee> findById(Long id) { public Optional<Employee> findById(Long id) {
@ -33,4 +44,64 @@ public class StaffManagement {
staffRepository.deleteById(id); 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<Employee> 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<Employee> 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 Morgans 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 Morgans 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 cant 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());
}
} }

View file

@ -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<CustomOrder> 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<Employee> 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<Employee> availableService = staffManagement.getAvailableStaffByJob(JobType.SERVICE,
LocalDateTime.of(2023, 10, 27, 16, 0), LocalDateTime.of(2023, 10, 27, 18, 0));
Set<Employee> 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);
}
}