mirror of
https://github.com/st-tu-dresden-praktikum/swt23w23
synced 2024-07-19 21:04:36 +02:00
Implement per-month employee working hours
Closes #76 Co-auther-by: Denis Natusch <denis.natusch@mailbox.tu-dresden.de>
This commit is contained in:
parent
e4d23d8e81
commit
580d3a6af6
|
@ -25,7 +25,14 @@ package catering.order {
|
||||||
class CustomOrder {
|
class CustomOrder {
|
||||||
- start : LocalDateTime
|
- start : LocalDateTime
|
||||||
- finish : LocalDateTime
|
- finish : LocalDateTime
|
||||||
|
+ getDurationInSeconds(start:LocalDateTime,finish:LocalDateTime): long
|
||||||
|
+ getDurationInHours(start:LocalDateTime,finish:LocalDateTime): long
|
||||||
|
+ min(a:LocalDateTime,b:LocalDateTime): LocalDateTime
|
||||||
|
+ max(b:LocalDateTime,b:LocalDateTime): LocalDateTime
|
||||||
|
+ getDurationInSecondsDuringMonth(month:YearMonth): long
|
||||||
}
|
}
|
||||||
|
CustomOrder ..> time.LocalDateTime
|
||||||
|
CustomOrder ..> time.YearMonth
|
||||||
|
|
||||||
enum EventType {
|
enum EventType {
|
||||||
EVENT_CATERING
|
EVENT_CATERING
|
||||||
|
@ -72,6 +79,7 @@ OrderController .....> catering.orderCatalog.OrderCatalogEntry
|
||||||
package time {
|
package time {
|
||||||
class LocalDateTime
|
class LocalDateTime
|
||||||
class DateTimeFormatter
|
class DateTimeFormatter
|
||||||
|
class YearMonth
|
||||||
}
|
}
|
||||||
|
|
||||||
@enduml
|
@enduml
|
||||||
|
|
BIN
src/main/asciidoc/models/design/order.svg
(Stored with Git LFS)
BIN
src/main/asciidoc/models/design/order.svg
(Stored with Git LFS)
Binary file not shown.
|
@ -47,8 +47,8 @@ package catering.staff {
|
||||||
|
|
||||||
class StaffController {
|
class StaffController {
|
||||||
+ StaffController(staffRepository: StaffRepository)
|
+ StaffController(staffRepository: StaffRepository)
|
||||||
+ getStaff(model: Model): String
|
+ getStaff(model: Model, month:Optional<YearMonth>): String
|
||||||
+ getStaff(model: Model, form: StaffForm): String
|
+ getStaff(model: Model, form: StaffForm, month:Optional<YearMonth>): String
|
||||||
+ removeEmployee(employee: Employee, model: Model): String
|
+ removeEmployee(employee: Employee, model: Model): String
|
||||||
+ addEmployee(form:StaffForm, result:Errors, model: Model): String
|
+ addEmployee(form:StaffForm, result:Errors, model: Model): String
|
||||||
+ editEmployee(employee: Employee, model Model): String
|
+ editEmployee(employee: Employee, model Model): String
|
||||||
|
@ -70,6 +70,7 @@ package catering.staff {
|
||||||
+ findAll(): Streamable<Employee>
|
+ findAll(): Streamable<Employee>
|
||||||
+ delete(id: Long): void
|
+ delete(id: Long): void
|
||||||
+ getAvailableStaffByJob(job: JobType, start:LocalDateTime, finish:LocalDateTime): Set<Employee>
|
+ getAvailableStaffByJob(job: JobType, start:LocalDateTime, finish:LocalDateTime): Set<Employee>
|
||||||
|
+ getWorkingHourseByemployee(e: Employee,month: YearMonth): double
|
||||||
}
|
}
|
||||||
StaffManagement --> StaffRepository : -staffRepository
|
StaffManagement --> StaffRepository : -staffRepository
|
||||||
StaffManagement --> OrderManagement : -orderManagement
|
StaffManagement --> OrderManagement : -orderManagement
|
||||||
|
|
|
@ -11,6 +11,7 @@ import org.springframework.data.annotation.Id;
|
||||||
import javax.money.MonetaryAmount;
|
import javax.money.MonetaryAmount;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.YearMonth;
|
||||||
import java.time.format.DateTimeFormatter;
|
import java.time.format.DateTimeFormatter;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -40,12 +41,37 @@ public class CustomOrder extends Order {
|
||||||
|
|
||||||
public CustomOrder() { }
|
public CustomOrder() { }
|
||||||
|
|
||||||
|
private long getDurationInSeconds(LocalDateTime start, LocalDateTime finish) {
|
||||||
|
if (start.compareTo(finish) > 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
return Duration.between(start, finish).getSeconds();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper function to get the amount of hours in between start and finish (analogous to CustomCart)
|
* Helper function to get the amount of hours in between start and finish (analogous to CustomCart)
|
||||||
* @return hours between start and finish
|
* @return hours between start and finish
|
||||||
*/
|
*/
|
||||||
|
private long getDurationInHours(LocalDateTime start, LocalDateTime finish) {
|
||||||
|
return getDurationInSeconds(start, finish) / 3600;
|
||||||
|
}
|
||||||
|
|
||||||
public long getDurationInHours() {
|
public long getDurationInHours() {
|
||||||
return Duration.between(start, finish).getSeconds() / 3600;
|
return getDurationInHours(start, finish);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LocalDateTime min(LocalDateTime a, LocalDateTime b) {
|
||||||
|
return (a.compareTo(b)) < 0 ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LocalDateTime max(LocalDateTime a, LocalDateTime b) {
|
||||||
|
return (a.compareTo(b)) > 0 ? a : b;
|
||||||
|
}
|
||||||
|
|
||||||
|
public long getDurationInSecondsDuringMonth(YearMonth month) {
|
||||||
|
LocalDateTime startInMonth = max(this.start, month.atDay(1).atStartOfDay());
|
||||||
|
LocalDateTime finishInMonth = min(this.finish, month.plusMonths(1).atDay(1).atStartOfDay());
|
||||||
|
return getDurationInSeconds(startInMonth, finishInMonth);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -2,6 +2,9 @@ package catering.staff;
|
||||||
|
|
||||||
import static org.salespointframework.core.Currencies.EURO;
|
import static org.salespointframework.core.Currencies.EURO;
|
||||||
|
|
||||||
|
import java.time.YearMonth;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.javamoney.moneta.Money;
|
import org.javamoney.moneta.Money;
|
||||||
import org.springframework.security.access.prepost.PreAuthorize;
|
import org.springframework.security.access.prepost.PreAuthorize;
|
||||||
import org.springframework.stereotype.Controller;
|
import org.springframework.stereotype.Controller;
|
||||||
|
@ -26,13 +29,15 @@ public class StaffController {
|
||||||
|
|
||||||
@GetMapping("/staff")
|
@GetMapping("/staff")
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
public String getStaff(Model model) {
|
public String getStaff(Model model, @RequestParam Optional<YearMonth> month) {
|
||||||
return getStaff(model, new StaffForm());
|
return getStaff(model, new StaffForm(), month.orElseGet(YearMonth::now));
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getStaff(Model model, @Valid StaffForm form) {
|
public String getStaff(Model model, @Valid StaffForm form, YearMonth month) {
|
||||||
model.addAttribute("staff", staffManagement.findAll());
|
model.addAttribute("staff", staffManagement.findAll());
|
||||||
model.addAttribute("form", form);
|
model.addAttribute("form", form);
|
||||||
|
model.addAttribute("month", month);
|
||||||
|
model.addAttribute("management", staffManagement);
|
||||||
return "staff";
|
return "staff";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +46,7 @@ public class StaffController {
|
||||||
public String addEmployee(@Valid @ModelAttribute("form") StaffForm form, Errors result, Model model) {
|
public String addEmployee(@Valid @ModelAttribute("form") StaffForm form, Errors result, Model model) {
|
||||||
form.validate(result);
|
form.validate(result);
|
||||||
if (result.hasErrors()) {
|
if (result.hasErrors()) {
|
||||||
return getStaff(model, form);
|
return getStaff(model, form, YearMonth.now());
|
||||||
}
|
}
|
||||||
staffManagement.save(new Employee(form.getName(), form.getJob(), Money.of(form.getWage(), EURO)));
|
staffManagement.save(new Employee(form.getName(), form.getJob(), Money.of(form.getWage(), EURO)));
|
||||||
return "redirect:/staff";
|
return "redirect:/staff";
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package catering.staff;
|
package catering.staff;
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.YearMonth;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
@ -104,4 +105,10 @@ public class StaffManagement {
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public double getWorkingHoursByEmployee(Employee e, YearMonth month) {
|
||||||
|
return orderManagement.findAll(Pageable.unpaged()).stream()
|
||||||
|
.filter(order -> order.getStaff().contains(e))
|
||||||
|
.map(order -> order.getDurationInSecondsDuringMonth(month))
|
||||||
|
.collect(Collectors.summingDouble(seconds -> (double) seconds / 3600));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,11 +10,19 @@
|
||||||
<div layout:fragment="content">
|
<div layout:fragment="content">
|
||||||
<div>
|
<div>
|
||||||
<h2>Mitarbeiterdetails</h2>
|
<h2>Mitarbeiterdetails</h2>
|
||||||
|
<form th:action="@{/staff}" method="get">
|
||||||
|
<div class="mb-3">
|
||||||
|
<label for="month">Monat:</label>
|
||||||
|
<input type="month" name="month" id="month" th:value="${month}" required>
|
||||||
|
</div>
|
||||||
|
<button class="btn btn-primary" type="submit">Zeitangabe aktualisieren</button>
|
||||||
|
</form>
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th>Name</th>
|
||||||
<th>Beruf</th>
|
<th>Beruf</th>
|
||||||
<th>Lohn</th>
|
<th>Lohn</th>
|
||||||
|
<th>Arbeitszeit</th>
|
||||||
<th></th>
|
<th></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
|
@ -23,6 +31,7 @@
|
||||||
<td th:text="${employee.name}">Max</td>
|
<td th:text="${employee.name}">Max</td>
|
||||||
<td th:text="${employee.job}">Koch</td>
|
<td th:text="${employee.job}">Koch</td>
|
||||||
<td th:text="${employee.wage}"></td>
|
<td th:text="${employee.wage}"></td>
|
||||||
|
<td th:with="month=${month}" th:text="${management.getWorkingHoursByEmployee(employee, month)}"></td>
|
||||||
<td>
|
<td>
|
||||||
<a th:href="@{'/staff/edit/' + ${employee.id}}"
|
<a th:href="@{'/staff/edit/' + ${employee.id}}"
|
||||||
><button class="btn btn-warning">Bearbeiten</button></a
|
><button class="btn btn-warning">Bearbeiten</button></a
|
||||||
|
|
|
@ -72,6 +72,7 @@ public class OrderControllerIntegrationTests {
|
||||||
myUser = userAccountManagement.findByUsername("andi").get();
|
myUser = userAccountManagement.findByUsername("andi").get();
|
||||||
admin = userAccountManagement.findByUsername("admin").get();
|
admin = userAccountManagement.findByUsername("admin").get();
|
||||||
|
|
||||||
|
orderManagement.findAll(Pageable.unpaged()).forEach(orderManagement::delete);
|
||||||
|
|
||||||
if (orderManagement.findAll(Pageable.unpaged()).stream().findAny().isEmpty()) {
|
if (orderManagement.findAll(Pageable.unpaged()).stream().findAny().isEmpty()) {
|
||||||
myCart = new CustomCart(OrderType.EVENT_CATERING,
|
myCart = new CustomCart(OrderType.EVENT_CATERING,
|
||||||
|
|
|
@ -3,6 +3,7 @@ package catering.staff;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.hamcrest.CoreMatchers.endsWith;
|
import static org.hamcrest.CoreMatchers.endsWith;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.salespointframework.core.Currencies.EURO;
|
import static org.salespointframework.core.Currencies.EURO;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
@ -11,19 +12,32 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.time.YearMonth;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.javamoney.moneta.Money;
|
import org.javamoney.moneta.Money;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.salespointframework.order.OrderManagement;
|
||||||
|
import org.salespointframework.useraccount.UserAccountManagement;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
|
||||||
import org.springframework.boot.test.context.SpringBootTest;
|
import org.springframework.boot.test.context.SpringBootTest;
|
||||||
|
import org.springframework.data.domain.Pageable;
|
||||||
import org.springframework.http.HttpHeaders;
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.security.test.context.support.WithAnonymousUser;
|
import org.springframework.security.test.context.support.WithAnonymousUser;
|
||||||
import org.springframework.security.test.context.support.WithMockUser;
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
|
||||||
|
|
||||||
|
import catering.order.CustomCart;
|
||||||
|
import catering.order.CustomOrder;
|
||||||
|
import catering.order.OrderType;
|
||||||
|
import catering.users.User;
|
||||||
|
import catering.users.UserManagement;
|
||||||
|
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
class StaffControllerIntegrationTests {
|
class StaffControllerIntegrationTests {
|
||||||
|
@ -38,13 +52,44 @@ class StaffControllerIntegrationTests {
|
||||||
@Autowired
|
@Autowired
|
||||||
private StaffManagement staffManagement;
|
private StaffManagement staffManagement;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private OrderManagement<CustomOrder> orderManagement;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserManagement userManagement;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private UserAccountManagement userAccountManagement;
|
||||||
|
|
||||||
Employee defaultEmployee;
|
Employee defaultEmployee;
|
||||||
Long defaultEmployeeId;
|
Long defaultEmployeeId;
|
||||||
|
User defaultCustomer;
|
||||||
|
Employee orderEmployee;
|
||||||
|
CustomOrder defaultStaffOrder;
|
||||||
|
|
||||||
|
CustomOrder createCustomOrder(LocalDateTime start, LocalDateTime end, Set<Employee> staff) {
|
||||||
|
CustomOrder order = orderManagement.save(new CustomOrder(defaultCustomer.getUserAccount().getId(),
|
||||||
|
new CustomCart(OrderType.EVENT_CATERING, start, end)));
|
||||||
|
staff.forEach(order::addEmployee);
|
||||||
|
return orderManagement.save(order);
|
||||||
|
}
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setup() throws Exception {
|
void setup() throws Exception {
|
||||||
|
orderManagement.findAll(Pageable.unpaged()).forEach(orderManagement::delete);
|
||||||
|
staffManagement.findAll().map(Employee::getId).forEach(staffManagement::delete);
|
||||||
|
|
||||||
defaultEmployee = staffManagement.save(new Employee("Dieter Baum", JobType.COOK, Money.of(0, EURO)));
|
defaultEmployee = staffManagement.save(new Employee("Dieter Baum", JobType.COOK, Money.of(0, EURO)));
|
||||||
defaultEmployeeId = defaultEmployee.getId();
|
defaultEmployeeId = defaultEmployee.getId();
|
||||||
|
|
||||||
|
if (userAccountManagement.findByUsername("hemming").isEmpty()) {
|
||||||
|
userManagement.createCustomer("hemming", "Nürnberger Platz", "123", "Hemming Quark");
|
||||||
|
}
|
||||||
|
defaultCustomer = userManagement.getUserByName("hemming").get();
|
||||||
|
|
||||||
|
orderEmployee = staffManagement.save(new Employee("Tyler Baum", JobType.COOK, Money.of(10, EURO)));
|
||||||
|
defaultStaffOrder = createCustomOrder(LocalDateTime.of(2023, 12, 20, 10, 0),
|
||||||
|
LocalDateTime.of(2023, 12, 20, 21, 0), Set.of(orderEmployee));
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
@AfterEach
|
||||||
|
@ -158,6 +203,14 @@ class StaffControllerIntegrationTests {
|
||||||
.containsExactly("Dieter Baum", JobType.COOK);
|
.containsExactly("Dieter Baum", JobType.COOK);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(username = "admin", password = "admin", roles = "ADMIN")
|
||||||
|
void viewHoursesOfWork() throws Exception {
|
||||||
|
mvc.perform(get("/staff"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(content().string(containsString("11.0")));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@WithMockUser(username = "dieter", password = "123", roles = "CUSTOMER")
|
@WithMockUser(username = "dieter", password = "123", roles = "CUSTOMER")
|
||||||
void refuseCustomer() throws Exception {
|
void refuseCustomer() throws Exception {
|
||||||
|
|
Loading…
Reference in a new issue