Associate rentables with order and event explicitly

This closes #74

Co-authored-by: Theo Reichert <theo.reichert@mailbox.tu-dresden.de>
This commit is contained in:
Mathis Kral 2023-12-07 16:58:29 +01:00
parent cef94a0bdb
commit 34c7c96c70
15 changed files with 368 additions and 129 deletions

View file

@ -19,11 +19,14 @@ package catering.catalog;
import org.salespointframework.catalog.Catalog;
import org.salespointframework.catalog.Product;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.util.Streamable;
import org.springframework.lang.NonNull;
import catering.order.OrderType;
import org.springframework.stereotype.Repository;
@Repository
public interface CateringCatalog extends Catalog<Product> {
static final Sort DEFAULT_SORT = Sort.sort(Product.class).by(Product::getName).ascending();
@ -46,8 +49,14 @@ public interface CateringCatalog extends Catalog<Product> {
return findByCategories(category, DEFAULT_SORT);
}
Streamable<Product> findRentablesByCategories(@NonNull String category);
@Query("select p from #{#entityName} p")
Streamable<Rentable> findRentables();
Streamable<Product> findConsumablesByCategories(@NonNull String category);
@Query("select p from #{#entityName} p")
Streamable<Consumable> findConsumables();
Streamable<Rentable> findRentablesByCategoriesContains(@NonNull String category);
Streamable<Consumable> findConsumablesByCategoriesContains(@NonNull String category);
}

View file

@ -15,6 +15,7 @@ import jakarta.persistence.Entity;
public class Rentable extends Product {
private MonetaryAmount wholesalePrice;
private MonetaryAmount pricePerHour;
@SuppressWarnings({ "deprecation" })
public Rentable() {
@ -41,4 +42,8 @@ public class Rentable extends Product {
public MonetaryAmount getRetailPrice() {
return super.getPrice();
}
public MonetaryAmount getPriceForTime(int hours) {
return getPrice().multiply(hours).add(getPrice());
}
}

View file

@ -1,8 +1,13 @@
package catering.order;
import catering.catalog.Rentable;
import catering.staff.Employee;
import org.javamoney.moneta.Money;
import org.salespointframework.inventory.UniqueInventory;
import org.salespointframework.inventory.UniqueInventoryItem;
import org.salespointframework.order.Cart;
import org.salespointframework.order.CartItem;
import org.salespointframework.order.OrderLine;
import javax.money.MonetaryAmount;
import java.time.Duration;
@ -63,6 +68,31 @@ public class CustomCart extends Cart {
return order;
}
/**
* Add ChargeLines for rental costs of each Rentable to {@param order}
*/
public CustomOrder addRentablesToOrder(CustomOrder order, UniqueInventory<UniqueInventoryItem> inventory) {
for (CartItem item : this) {
if (item.getProduct() instanceof Rentable rentable) {
OrderLine orderLine = order.getOrderLines(rentable).stream().findFirst().get();
order.addChargeLine(
rentable.getPrice()
.multiply(getDurationInHours())
.multiply(item.getQuantity().getAmount()),
"rental costs",
orderLine
);
// neutralises automatic reduction of rentables in inventory
inventory.save(inventory.findByProduct(rentable).get().increaseQuantity(orderLine.getQuantity()));
}
}
return order;
}
public OrderType getOrderType() {
return orderType;
}
@ -80,10 +110,10 @@ public class CustomCart extends Cart {
}
/**
* @return hours between start and finish multiplied with a rate
* @return hours between start and finish
*/
public double getDurationInHoursTimesRate(double rate) {
return (float) Duration.between(start, finish).getSeconds() / 3600 * rate;
public int getDurationInHours() {
return (int) (Duration.between(start, finish).getSeconds() / 3600);
}
public LocalDateTime getFinish() {
@ -100,14 +130,19 @@ public class CustomCart extends Cart {
@Override
public MonetaryAmount getPrice() {
MonetaryAmount total = super.getPrice();
MonetaryAmount total = Money.of(0, "EUR");
for (int i = 0; i < staff.size(); i++) { // TODO: get this from employee itself
total = total.add(Money.of(getDurationInHoursTimesRate(12.0), "EUR"));
for (CartItem item : this) {
if (item.getProduct() instanceof Rentable rentable) {
// final_price = (price + price * time) * quantity
total = total.add(rentable.getPriceForTime(getDurationInHours()).multiply(item.getQuantity().getAmount()));
} else {
total = total.add(item.getProduct().getPrice().multiply(item.getQuantity().getAmount()));
}
}
for (Employee employee : staff) {
total = total.add(Money.of(getDurationInHoursTimesRate(12), "EUR")); // TODO: get from employee
total = total.add(Money.of(getDurationInHours() * employee.getWage(), "EUR"));
}
return total;

View file

@ -22,7 +22,7 @@ public class CustomOrder extends Order {
private Long id;
@ManyToMany
private Set<Employee> staff;
private OrderType orderType = OrderType.SOMETHING_ELSE;
private OrderType orderType = OrderType.EVENT_CATERING;
private LocalDateTime start;
private LocalDateTime finish;
private boolean invoiceAvailable = false;
@ -44,7 +44,7 @@ public class CustomOrder extends Order {
* Helper function to get the amount of hours in between start and finish (analogous to CustomCart)
* @return hours between start and finish
*/
private long getDurationInHours(LocalDateTime start, LocalDateTime finish) {
public long getDurationInHours() {
return Duration.between(start, finish).getSeconds() / 3600;
}
@ -52,8 +52,8 @@ public class CustomOrder extends Order {
* Adds an employee to the order and adds a new {@Link ChangeLine} containing the costs
*/
public boolean addEmployee(Employee employee) {
MonetaryAmount cost = Money.of(12.0, "EUR")
.multiply(getDurationInHours(this.start, this.finish)); // TODO: Get salary from employee
MonetaryAmount cost = Money.of(employee.getWage(), "EUR")
.multiply(getDurationInHours());
if (this.staff.add(employee)) {
super.addChargeLine(cost, employee.getId().toString());

View file

@ -0,0 +1,24 @@
package catering.order;
import catering.catalog.Rentable;
import org.salespointframework.inventory.UniqueInventory;
import org.salespointframework.inventory.UniqueInventoryItem;
import org.salespointframework.order.Order;
import org.salespointframework.quantity.Quantity;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.util.Streamable;
import org.springframework.data.repository.Repository;
import java.time.LocalDateTime;
public interface CustomOrderRepository extends Repository<CustomOrder, Order.OrderIdentifier>{
@Query("""
select order from #{#entityName} order
where
not order.start > ?2 and
not order.finish < ?1
""")
Streamable<CustomOrder> findOrdersByInterval(LocalDateTime start, LocalDateTime finish);
}

View file

@ -1,5 +1,8 @@
package catering.order;
import catering.catalog.CateringCatalog;
import catering.catalog.Consumable;
import catering.catalog.Rentable;
import catering.staff.Employee;
import catering.staff.StaffManagement;
import org.salespointframework.catalog.Product;
@ -11,7 +14,6 @@ import org.salespointframework.useraccount.Role;
import org.salespointframework.useraccount.UserAccount;
import org.salespointframework.useraccount.web.LoggedIn;
import org.springframework.data.domain.Pageable;
import org.springframework.data.util.Streamable;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
@ -31,11 +33,15 @@ import java.util.stream.Collectors;
public class OrderController {
private final OrderManagement<CustomOrder> orderManagement;
private final CustomOrderRepository customOrderRepository;
private final UniqueInventory<UniqueInventoryItem> inventory;
private final CateringCatalog catalog;
private final StaffManagement staffManagement;
public OrderController(OrderManagement<CustomOrder> orderManagement, UniqueInventory<UniqueInventoryItem> inventory, StaffManagement staffManagement) {
public OrderController(OrderManagement<CustomOrder> orderManagement, CustomOrderRepository customOrderRepository, UniqueInventory<UniqueInventoryItem> inventory, CateringCatalog catalog, StaffManagement staffManagement) {
this.orderManagement = orderManagement;
this.customOrderRepository = customOrderRepository;
this.catalog = catalog;
this.inventory = inventory;
this.staffManagement = staffManagement;
}
@ -71,11 +77,9 @@ public class OrderController {
// Obtains an instance of LocalDate from a text string such as 2007-12-03.
LocalDate date = LocalDate.parse(day);
List<CustomOrder> myOrders = orderManagement.findAll(Pageable.unpaged()).stream().filter(
order ->
(order.getStart().toLocalDate().isBefore(date) || order.getStart().toLocalDate().isEqual(date))
&& (order.getFinish().toLocalDate().isAfter(date) || order.getFinish().toLocalDate().isEqual(date))
).collect(Collectors.toList());
List<CustomOrder> myOrders = customOrderRepository.findOrdersByInterval(date.atStartOfDay(),
date.atStartOfDay().plusHours(23).withMinute(59).withSecond(59))
.stream().toList();
model.addAttribute("orders", myOrders);
model.addAttribute("total", myOrders.size());
@ -84,19 +88,45 @@ public class OrderController {
@ModelAttribute("event")
CustomCart initializeCart() {
return new CustomCart(OrderType.SOMETHING_ELSE, LocalDateTime.now().plusDays(7),
LocalDateTime.now().plusDays(7));
return new CustomCart(OrderType.EVENT_CATERING, LocalDateTime.now().plusDays(7),
LocalDateTime.now().plusDays(7).plusHours(1));
}
@GetMapping("/event")
@PreAuthorize("hasRole('CUSTOMER')")
public String event(Model model, @ModelAttribute("event") CustomCart cart) {
model.addAttribute("items", cart.stream().collect(Collectors.toList()));
model.addAttribute("cartRentables", cart.stream().filter(item -> item.getProduct() instanceof Rentable).toList());
model.addAttribute("cartConsumables", cart.stream().filter(item -> item.getProduct() instanceof Consumable).toList());
model.addAttribute("totalPrice", cart.getPrice());
model.addAttribute("invItems", inventory.findAll().stream().collect(Collectors.toList()));
Set<Employee> myStaff = new HashSet<>();
staffManagement.findAll().forEach(myStaff::add);
model.addAttribute("allStaff", myStaff);
Map<Consumable, Quantity> invConsumables = new HashMap<>();
catalog.findConsumablesByCategoriesContains(cart.getOrderType().toString()).forEach(consumable ->
invConsumables.put(
consumable,
inventory.findByProduct(consumable).get().getQuantity())
);
model.addAttribute("invConsumables", invConsumables);
model.addAttribute(
"invRentables",
catalog.findRentablesByCategoriesContains(cart.getOrderType().toString())
.stream()
.collect(Collectors.toMap(rentable -> rentable, rentable -> findFreeAmountInInterval(
rentable,
cart.getStart(),
cart.getFinish(),
inventory,
customOrderRepository))));
model.addAttribute("allStaff", staffManagement.findAll().stream().toList());
model.addAttribute("duration", cart.getDurationInHours());
model.addAttribute("minDate", LocalDate.now().plusDays(7));
return "event";
}
@ -140,14 +170,24 @@ public class OrderController {
@PostMapping("/event/addProduct")
@PreAuthorize("hasRole('CUSTOMER')")
public String addProduct(@RequestParam("pid") Product product, @RequestParam("number") int number, @ModelAttribute("event") CustomCart cart) {
Quantity amount = Quantity.of(number > 0 ? number : 1);
Quantity invAmount = inventory.findByProduct(product).get().getQuantity(); // TODO ERROR HANDLING
Quantity cartQuantity = cart.getQuantity(product);
Quantity available;
if (product instanceof Rentable rentable) {
available = findFreeAmountInInterval(
rentable,
cart.getStart(),
cart.getFinish(),
inventory,
customOrderRepository);
} else {
available = inventory.findByProduct(product).get().getQuantity();
}
// check for possible miss-inputs
if (amount.add(cartQuantity).isGreaterThan(invAmount)) {
cart.addOrUpdateItem(product, cartQuantity.negate().add(invAmount));
if (amount.add(cartQuantity).isGreaterThan(available)) {
cart.addOrUpdateItem(product, cartQuantity.negate().add(available));
} else {
cart.addOrUpdateItem(product, amount);
}
@ -174,7 +214,7 @@ public class OrderController {
LocalDateTime startTime = LocalDateTime.of(start, LocalTime.of(unwrappedStartHour, 0));
LocalDateTime finishTime = LocalDateTime.of(finish,
LocalTime.of(unwrappedFinishHour <= unwrappedStartHour
&& !finish.isAfter(start) ? unwrappedStartHour+1 : unwrappedFinishHour, 0));
&& !finish.isAfter(start) ? (unwrappedStartHour+1) % 24 : unwrappedFinishHour, 0));
cart.setStart(startTime);
cart.setFinish(finishTime);
@ -193,7 +233,8 @@ public class OrderController {
CustomOrder myOrder = new CustomOrder(account.getId(), cart);
cart.addItemsTo(myOrder);
cart.addStaffTo(myOrder);
orderManagement.payOrder(myOrder); // TODO: change this later
cart.addRentablesToOrder(myOrder, inventory);
orderManagement.payOrder(myOrder);
orderManagement.completeOrder(myOrder);
cart.clear();
@ -203,27 +244,26 @@ public class OrderController {
@PostMapping("/event/changeOrderType")
@PreAuthorize("hasRole('CUSTOMER')")
public String changeOrderType(@RequestParam(name = "type") Optional<String> optionalOrderType, @ModelAttribute("event") CustomCart cart) {
String orderType = optionalOrderType.orElse("FOO");
switch (orderType) {
case "RaK":
cart.setOrderType(OrderType.RENT_A_COOK);
break;
case "EK":
public String changeOrderType(@RequestParam(name = "type") String orderType, @ModelAttribute("event") CustomCart cart) {
try {
cart.setOrderType(OrderType.valueOf(orderType));
} catch (IllegalArgumentException e) {
cart.setOrderType(OrderType.EVENT_CATERING);
break;
case "SN":
cart.setOrderType(OrderType.SUSHI_NIGHT);
break;
case "MB":
cart.setOrderType(OrderType.MOBILE_BREAKFAST);
break;
default:
cart.setOrderType(OrderType.SOMETHING_ELSE);
}
return "redirect:/event";
}
public static Quantity findFreeAmountInInterval(Rentable product, LocalDateTime start, LocalDateTime finish, UniqueInventory<UniqueInventoryItem> inventory, CustomOrderRepository customOrderRepository) {
return inventory.findByProduct(product)
.map(item -> item.getQuantity().subtract(
customOrderRepository.findOrdersByInterval(start, finish)
.flatMap(order -> order.getOrderLines(product).stream())
.map(OrderLine::getQuantity).stream()
.reduce(Quantity.NONE, Quantity::add)))
.orElse(Quantity.NONE);
}
@GetMapping("/orders/calender")
public String calender(Model model) {
ArrayList<ArrayList<String>> datesOfMonth = new ArrayList<ArrayList<String>>(28);
@ -232,7 +272,7 @@ public class OrderController {
LocalDate startDate = startDateTime.toLocalDate();
LocalDate endDate = endDateTime.toLocalDate();
// create all dates of the calender
// create all dates of the calendar
for (LocalDate date = startDate; !date.isAfter(endDate); date = date.plusDays(1)) {
ArrayList<String> x = new ArrayList<String>(2);
x.add(Integer.toString(date.getDayOfMonth()));
@ -240,19 +280,8 @@ public class OrderController {
datesOfMonth.add(x);
}
/*
* FIXME: Do not load all orders into java,
* instead query orderManagement better to only return orders overlapping with interval [startDate,endDate]
* by using "query creation" of the jpa
* e.g. this.orderManagement.findByFinishDateIsNotBeforeAndStartDateIsNotAfter(LocalDateTime startDateTime, LocalDateTime endDateTime)
*/
Streamable<CustomOrder> x = this.orderManagement.findAll(Pageable.unpaged()).filter(e ->
!e.getFinish().toLocalDate().isBefore(startDate) && // end is not before today
!e.getStart().toLocalDate().isAfter(endDate)
);
// add each order overlapping with the calendar to the days it overlaps with
for (CustomOrder order : x) {
for (CustomOrder order : customOrderRepository.findOrdersByInterval(startDateTime, endDateTime)) {
int start_index_inclusive = Math.max((int) startDate.until(order.getStart().toLocalDate(), ChronoUnit.DAYS),0);
int end_index_exclusive = Math.min((int) startDate.until(order.getFinish().toLocalDate(), ChronoUnit.DAYS), 27) + 1;
String order_id = Objects.requireNonNull(order.getId()).toString();

View file

@ -3,7 +3,16 @@ package catering.order;
public enum OrderType {
RENT_A_COOK,
EVENT_CATERING,
SUSHI_NIGHT,
MOBILE_BREAKFAST,
SOMETHING_ELSE
PARTY_SERVICE;
public String toHumanReadable() {
return switch (this) {
case RENT_A_COOK -> "Miete einen Koch";
case EVENT_CATERING -> "Veranstaltungsbewirtung";
case MOBILE_BREAKFAST -> "Mobiles Frühstück";
case PARTY_SERVICE -> "Sausedienst";
default -> null;
};
}
}

View file

@ -117,12 +117,6 @@ public class CatalogController {
@PostMapping("/catalog_editor/chooseEvent")
public String chooseEvent(String events) {
switch (events) {
case "event_catering":
formCatalogEntry.setEventType(OrderType.EVENT_CATERING);
break;
case "sushi_night":
formCatalogEntry.setEventType(OrderType.SUSHI_NIGHT);
break;
case "mobile_breakfast":
formCatalogEntry.setEventType(OrderType.MOBILE_BREAKFAST);
break;

View file

@ -13,7 +13,6 @@
<form th:object="${formCatalogEntry}" method="post" th:action="@{/catalog_editor/chooseEvent}">
<select class="form-select w-auto d-inline-block" name="events" id="events">
<option value="event_catering">Eventcatering</option>
<option value="sushi_night">Sushi Night</option>
<option value="mobile_breakfast">Mobile Breakfast</option>
<option value="rent_a_cook">Rent-a-Cook</option>
</select>

View file

@ -6,29 +6,21 @@
<body>
<div layout:fragment="content">
<form th:action="@{/event/changeOrderType}" method="post">
<form class="my-4" th:action="@{/event/changeOrderType}" method="post">
<select class="form-select w-auto d-inline-block" name="type">
<option disabled="disabled" selected value="NULL" th:text="${event.getOrderType()}"/>
<option th:value="'SE'" th:text="'Something else'"/>
<option th:value="'RaK'" th:text="Rent-a-Cook"/>
<option th:value="'EK'" th:text="Eventcatering"/>
<option th:value="'SN'" th:text="'Sushi Night'"/>
<option th:value="'MB'" th:text="'Mobile Breakfase'"/>
<option th:selected="${event.getOrderType().toString() == enumValue.toString()}" th:each="enumValue : ${T(catering.order.OrderType).values()}" th:value="${enumValue.toString()}" th:text="${enumValue.toHumanReadable()}"/>
</select>
<button class="btn btn-primary" type="submit">Eventtypen ändern</button>
</form>
<!-- I NEED SPACE -->
<br>
<form th:action="@{/event/changeDate}" method="post">
<form class="my-4" th:action="@{/event/changeDate}" method="post">
<label th:text="'Wählen sie ihren gewünschten Zeitraum aus (min. 7 Tage im voraus):'"/><br>
<input type="date" th:value="${event.getStart().toLocalDate()}" th:min="${event.getStart().toLocalDate()}" name="startDate"/>
<input type="date" th:value="${event.getStart().toLocalDate()}" th:min="${minDate}" name="startDate"/>
<select class="i do not know" name="startHour">
<option disabled="disabled" selected value="NULL" th:text="${event.getStart().getHour()} + ' Uhr'"/>
<option th:each="i : ${#numbers.sequence(0, 23)}" th:value="${i}" th:text="${i} + ' Uhr'"></option>
</select>
<input type="date" th:value="${event.getFinish().toLocalDate()}" th:min="${event.getFinish().toLocalDate()}" name="finishDate"/>
<input type="date" th:value="${event.getFinish().toLocalDate()}" th:min="${minDate}" name="finishDate"/>
<select class="i do not know" name="finishHour">
<option disabled="disabled" selected value="NULL" th:text="${event.getFinish().getHour() + ' Uhr'}"/>
<option th:each="i : ${#numbers.sequence(0, 23)}" th:value="${i}" th:text="${i} + ' Uhr'"></option>
@ -36,20 +28,28 @@
<button class="btn btn-primary" type="submit">Wunschdatum überprüfen</button>
</form>
<!-- I NEED SPACE -->
<br>
<table class="table">
<table class="table my-4">
<tr>
<th>Produkt</th>
<th>Anzahl</th>
<th>Preis</th>
<th></th>
</tr>
<tr th:each="item : ${items}">
<td th:text="${item.getProductName()}">Sake Nigiri</td>
<td th:text="${item.getQuantity()}">200</td>
<td th:text="${item.getPrice().getNumber().doubleValue()} +'€'">10€</td>
<tr th:each="item : ${cartConsumables}">
<td th:text="${item.getProductName()}"></td>
<td th:text="${item.getQuantity()}"></td>
<td th:text="${item.getPrice().getNumber().doubleValue()} +'€'"></td>
<td>
<form method="post" th:action="@{/event/removeProduct}">
<input type="hidden" th:value="${item.getId()}" name="itemId"/>
<button class="btn btn-danger" type="submit" >Produkt entfernen</button>
</form>
</td>
</tr>
<tr th:each="item : ${cartRentables}">
<td th:text="${item.getProductName()}"></td>
<td th:text="${item.getQuantity()}"></td>
<td th:text="${item.getProduct().getPriceForTime(duration).multiply(item.getQuantity().getAmount())} +'€'"></td>
<td>
<form method="post" th:action="@{/event/removeProduct}">
<input type="hidden" th:value="${item.getId()}" name="itemId"/>
@ -62,9 +62,9 @@
<th>Preis</th>
<th></th>
<tr th:each="employee : ${event.getStaff()}">
<td th:text="${employee.getName()}">Name</td>
<td th:text="${employee.getJob()}">Job</td>
<td th:text="${event.getDurationInHoursTimesRate(12.0)} + '€'">Preis</td> <!--TODO: get from employee-->
<td th:text="${employee.getName()}"></td>
<td th:text="${employee.getJob()}"></td>
<td th:text="${event.getDurationInHours() * employee.getWage()} + '€'"></td>
<td>
<form method="post" th:action="@{/event/removeEmployee}">
<input type="hidden" th:value="${employee.getId()}" name="sid"/>
@ -74,41 +74,63 @@
</tr>
</table>
<span th:text="'Gesamt: ' + ${totalPrice}">Price</span>
<span th:text="'Gesamt: ' + ${totalPrice.getNumber() + '€'}"></span>
<form method="post" th:action="@{/event/checkout}">
<form class="py-4" method="post" th:action="@{/event/checkout}">
<button class="btn btn-primary" type="submit">Kostenpflichtig bestellen</button>
</form>
<!-- I NEED SPACE -->
<br>
<br>
<h4>Verbrauchsgegenstand hinzufügen</h4>
<h4>Produkt hinzufügen</h4>
<table class="table">
<table class="table my-4">
<tr>
<th>Name</th>
<th>Preis/Stück</th>
<th>Verfügbar</th>
<th>Menge</th>
</tr>
<tr th:each="item : ${invItems}">
<td th:text="${item.getProduct().getName()}">Name</td>
<td th:text="${item.getProduct().getPrice()}">Preis</td>
<td th:text="${item.getQuantity()}">Verfügbar</td>
<tr th:each="item : ${invConsumables.entrySet()}">
<td th:text="${item.getKey().getName()}"></td>
<td th:text="${item.getKey().getPrice()}"></td>
<td th:text="${item.getValue()}"></td>
<td>
<form th:action="@{/event/addProduct}" method="post">
<input id="number" type="number" name="number" min="1" th:max="${item.getQuantity()}" value="1"/>
<input type="hidden" name="pid" th:value="${item.getProduct().getId()}"/>
<input id="number" type="number" name="number" min="1" th:max="${item.getValue()}" value="1"/>
<input type="hidden" name="pid" th:value="${item.getKey().getId()}"/>
<input class="btn btn-primary" type="submit" th:value="Hinzufügen"/>
</form>
</td>
</tr>
</table>
<h4>Mietgegenstand hinzufügen</h4>
<table class="table my-4">
<tr>
<th>Name</th>
<th>Preis/Stück (Pauschale)</th>
<th>Preis/Stück/Stunde</th>
<th>Verfügbar</th>
<th>Menge</th>
</tr>
<tr th:each="item : ${invRentables.entrySet()}">
<td th:text="${item.getKey().getName()}"></td>
<td th:text="${item.getKey().getPrice()}"></td>
<td th:text="${item.getKey().getPrice()}"></td>
<td th:text="${item.getValue()}"></td>
<td>
<form th:action="@{/event/addProduct}" method="post">
<input id="number" type="number" name="number" min="1" th:max="${item.getValue()}" value="1"/>
<input type="hidden" name="pid" th:value="${item.getKey().getId()}"/>
<input class="btn btn-primary" type="submit" th:value="Hinzufügen"/>
</form>
</td>
</tr>
</table>
<h4>Angestellte hinzufügen</h4>
<table class="table">
<table class="table my-4">
<tr>
<th>Name</th>
<th>Typ</th>
@ -117,10 +139,10 @@
<td></td>
</tr>
<tr th:each="employee : ${allStaff}">
<td th:text="${employee.getName()}">Name</td>
<td th:text="${employee.getJob()}">Job</td>
<td th:text="'12€/h'">12€</td> <!--TODO: get from employee-->
<td style="color: green" th:text="Verfügbar">Verfügbar</td> <!--TODO: get from employee-->
<td th:text="${employee.getName()}"></td>
<td th:text="${employee.getJob()}"></td>
<td th:text="${employee.getWage()}"></td>
<td style="color: green" th:text="Verfügbar"></td>
<td>
<form th:action="@{/event/addEmployee}" method="post">
<input type="hidden" name="sid" th:value="${employee.getId()}"/>
@ -130,4 +152,5 @@
</tr>
</table>
</div>
</body>
</html>

View file

@ -23,18 +23,19 @@
<td th:text="${order.getUserAccountIdentifier()}"/>
<td>
<div>
<a href="#productDetails" th:text="${order.getOrderType()}"/>
<strong class="text-danger" th:text="${order.getOrderType().toHumanReadable()}"/>
<ul th:each="orderLine : ${order.getOrderLines()}">
<li>
<b th:text="${orderLine.getProductName()}"/><br>
<th:block th:text="'Menge: ' + ${orderLine.getQuantity()}"/><br>
<th:block th:text="'Preis: ' + ${orderLine.getPrice().getNumber().doubleValue() + ' €'}"/>
<th:block th:text="'Pauschale: ' + ${orderLine.getPrice().getNumber().doubleValue()} + '€'"/><br>
<th:block th:text="'Mietkosten: ' + ${order.getChargeLines(orderLine).getTotal().getNumber()} + '€'"/>
</li>
</ul>
<ul th:each="employee : ${order.getStaff()}">
<li>
<b th:text="${employee.getName()} + ', ' + ${employee.getJob()}"/><br>
<th:block th:text="'Preis: 12€/h'"/>
<th:block th:text="'Preis: ' + ${order.getDurationInHours() * employee.getWage()}"/>
</li>
</ul>
</div>
@ -51,13 +52,10 @@
<p style="color: red" th:if="${order.getOrderStatus()} == ${cancelled}" th:text="STORNIERT"/>
<p style="color: green" th:if="${order.getOrderStatus()} == ${completed}" th:text="ABGESCHLOSSEN"/>
</td> <!--von Admin bearbeitbar-->
<td>
<th:block th:text="${order.getTotal().getNumber().doubleValue()}"/>
<th:block th:text="€"/>
</td>
<td th:text="${order.getTotal().getNumber().doubleValue()} + '€'"/>
<td sec:authorize="hasRole('ADMIN')" th:if="${order.getOrderStatus()} != ${cancelled}">
<form method="post" th:action="@{/allOrders/remove}">
<input type="hidden" name="orderID" value="0" th:value="${order.getId()}"/> <!-- FIXME BROKEN -->
<input type="hidden" name="orderID" value="0" th:value="${order.getId()}"/>
<input class="btn btn-danger" type="submit" value="remove" th:value="Stornieren"/>
</form>
</td>

View file

@ -142,7 +142,7 @@ public class OrderControllerIntegrationTests {
void userPlansEvent() throws Exception {
mvc.perform(get("/event"))
.andExpect(status().isOk())
.andExpect(model().attributeExists("invItems"));
.andExpect(model().attributeExists("invRentables"));
Product product = inventory.findAll().stream().findFirst().get().getProduct();

View file

@ -0,0 +1,112 @@
package catering.order;
import catering.catalog.Rentable;
import org.junit.jupiter.api.*;
import org.salespointframework.inventory.UniqueInventory;
import org.salespointframework.inventory.UniqueInventoryItem;
import org.salespointframework.order.OrderManagement;
import org.salespointframework.quantity.Quantity;
import org.salespointframework.useraccount.UserAccount;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Pageable;
import java.time.LocalDateTime;
import static org.assertj.core.api.Assertions.*;
@SpringBootTest
public class OrderControllerUnitTests {
@Autowired
UniqueInventory<UniqueInventoryItem> inventory;
@Autowired
OrderManagement<CustomOrder> orderManagement;
@Autowired
CustomOrderRepository customOrderRepository;
Rentable myProduct;
@BeforeEach
void setup() {
// because of FUUUUUUUN
if (!orderManagement.findAll(Pageable.unpaged()).isEmpty()) {
return;
}
// #1
CustomCart myCart = new CustomCart(
OrderType.EVENT_CATERING,
LocalDateTime.of(2023, 12, 11, 9, 0),
LocalDateTime.of(2023, 12, 13, 22, 0)
);
myProduct = (Rentable) inventory.findAll()
.filter(item -> item.getProduct().getName().equals("Kerze Rot"))
.stream().findFirst().get().getProduct();
myCart.addOrUpdateItem(myProduct, 3);
CustomOrder myOrder = new CustomOrder(UserAccount.UserAccountIdentifier.of("12345"), myCart);
myCart.addItemsTo(myOrder);
orderManagement.payOrder(myOrder);
orderManagement.completeOrder(myOrder);
// #2
myCart = new CustomCart(
OrderType.EVENT_CATERING,
LocalDateTime.of(2023, 12, 13, 9, 0),
LocalDateTime.of(2023, 12, 15, 22, 0)
);
myCart.addOrUpdateItem(myProduct, 4);
myOrder = new CustomOrder(UserAccount.UserAccountIdentifier.of("12345"), myCart);
myCart.addItemsTo(myOrder);
orderManagement.payOrder(myOrder);
orderManagement.completeOrder(myOrder);
}
@Test
@Order(1)
void thisShouldNeverFail() {
assertThat(orderManagement.findAll(Pageable.unpaged()).stream().count()).isEqualTo(2L);
}
@Test
@Order(2)
void correctSetup() {
assertThat(orderManagement.findAll(Pageable.unpaged()).stream().count()).isEqualTo(2L);
}
@Test
@Order(3)
void ordersByInterval() {
assertThat(customOrderRepository.findOrdersByInterval(
LocalDateTime.of(2023, 12, 2, 0, 0),
LocalDateTime.of(2024, 1, 1, 0, 0)
).stream().toList()).hasSize(2);
assertThat(customOrderRepository.findOrdersByInterval(
LocalDateTime.of(2023, 12, 11, 0, 0),
LocalDateTime.of(2023, 12, 11, 23, 0)
).stream().toList()).hasSize(1);
}
@Test
@Order(4)
@Disabled // because Spring does shit that I don't understand
void freeAmountInInterval() {
myProduct = (Rentable) inventory.findAll()
.filter(item -> item.getProduct().getName().equals("Kerze Rot"))
.stream().findFirst().get().getProduct();
assertThat(OrderController.findFreeAmountInInterval(
myProduct,
LocalDateTime.of(2023, 12, 11, 0, 0),
LocalDateTime.of(2023, 12, 11, 23, 0),
inventory,
customOrderRepository)
).isEqualTo(Quantity.of(7));
}
}

View file

@ -51,7 +51,7 @@ public class OrderUnitTests {
// test if ChargeLine is correct
assertThat(order.getChargeLines().stream().count()).isEqualTo(1);
assertThat(order.getChargeLines().getTotal()).isEqualTo(Money.of(11*12, "EUR"));
assertThat(order.getChargeLines().getTotal()).isEqualTo(Money.of(11*employee.getWage(), "EUR"));
}
@Test
@ -70,17 +70,17 @@ public class OrderUnitTests {
assertThat(order.getChargeLines().stream().count()).isEqualTo(1);
// test if costs are correct
assertThat(order.getChargeLines().getTotal()).isEqualTo(Money.of(11*12, "EUR"));
assertThat(order.getChargeLines().getTotal()).isEqualTo(Money.of(11*employee.getWage(), "EUR"));
assertThat(order.getOrderLines().getTotal()).isEqualTo(Money.of(10.0*0.6, "EUR"));
assertThat(order.getTotal()).isEqualTo(Money.of(11.0 * 12.0 + 10.0 * 0.6, "EUR"));
assertThat(order.getTotal()).isEqualTo(Money.of(11.0 * employee.getWage() + 10.0 * 0.6, "EUR"));
// test for duplication
order.addEmployee(employee);
assertThat(order.getStaff()).hasSize(1);
assertThat(order.getOrderLines().stream().count()).isEqualTo(1);
assertThat(order.getChargeLines().stream().count()).isEqualTo(1);
assertThat(order.getChargeLines().getTotal()).isEqualTo(Money.of(11*12, "EUR"));
assertThat(order.getChargeLines().getTotal()).isEqualTo(Money.of(11*employee.getWage(), "EUR"));
assertThat(order.getOrderLines().getTotal()).isEqualTo(Money.of(10.0*0.6, "EUR"));
assertThat(order.getTotal()).isEqualTo(Money.of(11.0 * 12.0 + 10.0 * 0.6, "EUR"));
assertThat(order.getTotal()).isEqualTo(Money.of(11.0 * employee.getWage() + 10.0 * 0.6, "EUR"));
}
}

View file

@ -18,6 +18,7 @@ import catering.order.CustomOrder;
import catering.order.OrderType;
import catering.users.User;
import catering.users.UserManagement;
import org.springframework.test.annotation.DirtiesContext;
@SpringBootTest
class StaffManagmentIntegratonTest {
@ -79,6 +80,7 @@ class StaffManagmentIntegratonTest {
}
@Test
@DirtiesContext
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));