Filter and distinguish products in orderCatalog

This commit is contained in:
Erik Hohlfeld 2024-01-12 16:21:54 +01:00 committed by Mathis
parent c94a4d4adb
commit 5aefd4e05f
17 changed files with 433 additions and 142 deletions

View file

@ -22,7 +22,7 @@ Files: src/main/asciidoc/models/analysis/*.svg
src/main/asciidoc/models/design/*.svg src/main/asciidoc/models/design/*.svg
src/main/asciidoc/models/mockups/*.svg src/main/asciidoc/models/mockups/*.svg
src/main/resources/banner.txt src/main/resources/banner.txt
Copyright: 2023 swt23w23 Copyright: 2023-2024 swt23w23
License: AGPL-3.0-or-later License: AGPL-3.0-or-later
# Small and/or autogenenerated files that cant be copyrighted # Small and/or autogenenerated files that cant be copyrighted

View file

@ -267,6 +267,7 @@ image:models/design/orderCatalog.svg[class design diagram - OrderCatalog]
|OrderCatalogController |A Spring MVC Controller to handle the event catalog |OrderCatalogController |A Spring MVC Controller to handle the event catalog
|OrderCatalogEntry |A class to save an event pack created by the administrator |OrderCatalogEntry |A class to save an event pack created by the administrator
|OrderCatalogEntryRepository |A repository to save all event packs in the form of OrderCatalogEntry |OrderCatalogEntryRepository |A repository to save all event packs in the form of OrderCatalogEntry
|CartService |A service class to make sure that Order and OrderCatalog use the same CustomCart object
|=== |===
=== Order === Order

View file

@ -1,5 +1,5 @@
' SPDX-License-Identifier: AGPL-3.0-or-later ' SPDX-License-Identifier: AGPL-3.0-or-later
' SPDX-FileCopyrightText: 2023 swt23w23 ' SPDX-FileCopyrightText: 2023-2024 swt23w23
@startuml @startuml
skinparam linetype ortho skinparam linetype ortho
skinparam groupInheritance 2 skinparam groupInheritance 2
@ -13,6 +13,7 @@ package Salespoint {
class Quantity class Quantity
class UserAccount class UserAccount
class Product class Product
class Inventory
} }
package catering.order { package catering.order {
@ -23,34 +24,44 @@ package catering.orderCatalog {
class OrderCatalogController { class OrderCatalogController {
+ OrderCatalogController() + OrderCatalogController()
+ catalog(model : Model) : String + catalog(model : Model) : String
+ configureCatalog(model : Model) : String + editCatalog(model : Model) : String
+ catalogAdd(eventType : OrderCatalogEntry.EventType, minimumTimePeriod : int, totalCost : int, model : Model) : String + addCatalogEntry(eventType : OrderCatalogEntry.EventType, minimumTimePeriod : int, totalCost : int, model : Model) : String
+ removeEntry(catalogEntryID : int) : String + removeEntry(catalogEntryID : int) : String
+ addProduct(name : String, amount : int, cost : double) : String + addProduct(name : String, amount : int, cost : double) : String
+ removeProduct(id : String, model : Model) : String + removeProduct(id : String, model : Model) : String
+ addTime(minimumTimePeriod : int, eventType : OrderCatalogEntry.EventType, products : Collection<Salespoint.Product>, model : Model) : String
+ chooseEvent(events : String) : String + chooseEvent(events : String) : String
+ addToCart(catalogEntryID : long) : String
} }
class OrderCatalogEntry { class OrderCatalogEntry {
- id : Long
- eventType : OrderType
- products : Map<Salespoint.Product, Salespoint.Quantity>
- totalCost: double
+ OrderCatalogEntry() + OrderCatalogEntry()
+ getId() : int + getId() : long
+ getEventType() : EventType + getEventType() : OrderType
+ getProducts() : Collection<Salespoint.Product> + getProducts() : Map<Salespoint.Product, Salespoint.Quantity>
+ getMinimumTimePeriod() : int + getTotalCost() : double
+ getTotalCost() : int + setEventType(eventType : OrderType) : void
+ setEventType(eventType : EventType) : void + setTotalCost(totalCost : double) : void
+ setMinimumTimePeriod(timePeriod : int) : void + addProduct(product : Salespoint.Product, count : Salespoint.Quantity) : void
+ setTotalCost(totalCost : int) : void + setProducts(Map<Salespoint.Product, Salespoint.Quantity>) : void
+ addProduct(name : String, count : Integer) : void
} }
class OrderCatalogEntryRepository { interface OrderCatalogEntryRepository {
+ OrderCatalogEntryRepository() + OrderCatalogEntryRepository()
+ addCatalogEntry(catalogEntry : OrderCatalogEntry) : boolean + addCatalogEntry(catalogEntry : OrderCatalogEntry) : boolean
+ removeCatalogEntry(catalogEntryID : int) : boolean + removeCatalogEntry(catalogEntryID : int) : boolean
+ getCatalogEntries() : Set<OrderCatalogEntries> + getCatalogEntries() : Set<OrderCatalogEntries>
} }
class CartService {
- CustomCart cart
+ CartService()
+ getCart() : CustomCart
+ setCart(CustomCart cart) : void
}
} }
OrderCatalogEntryRepository o---> OrderCatalogEntry OrderCatalogEntryRepository o---> OrderCatalogEntry
@ -62,5 +73,7 @@ OrderCatalogController ...> Salespoint.Cash
OrderCatalogController ...> Salespoint.Quantity OrderCatalogController ...> Salespoint.Quantity
OrderCatalogController ...> Salespoint.UserAccount OrderCatalogController ...> Salespoint.UserAccount
OrderCatalogEntry ...> catering.order.EventType OrderCatalogEntry ...> catering.order.EventType
CartService ...> catering.order.OrderController
OrderCatalogController ...> Salespoint.Inventory
@enduml @enduml

Binary file not shown.

View file

@ -2,6 +2,7 @@
// SPDX-FileCopyrightText: 2023-2024 swt23w23 // SPDX-FileCopyrightText: 2023-2024 swt23w23
package catering.inventory; package catering.inventory;
import catering.orderCatalog.CustomCatalogEntryRepository;
import org.salespointframework.catalog.Product; import org.salespointframework.catalog.Product;
import org.salespointframework.inventory.UniqueInventory; import org.salespointframework.inventory.UniqueInventory;
import org.salespointframework.inventory.UniqueInventoryItem; import org.salespointframework.inventory.UniqueInventoryItem;
@ -50,12 +51,14 @@ import jakarta.validation.Valid;
class InventoryController { class InventoryController {
private final UniqueInventory<UniqueInventoryItem> inventory; private final UniqueInventory<UniqueInventoryItem> inventory;
private final CateringCatalog cateringCatalog; private final CateringCatalog cateringCatalog;
private final CustomCatalogEntryRepository catalogEntryRepository;
InventoryController(UniqueInventory<UniqueInventoryItem> inventory, CateringCatalog cateringCatalog) { InventoryController(UniqueInventory<UniqueInventoryItem> inventory, CateringCatalog cateringCatalog, CustomCatalogEntryRepository catalogEntryRepository) {
Assert.notNull(inventory, "Inventory must not be null!"); Assert.notNull(inventory, "Inventory must not be null!");
Assert.notNull(inventory, "CateringCatalog must not be null!"); Assert.notNull(inventory, "CateringCatalog must not be null!");
this.inventory = inventory; this.inventory = inventory;
this.cateringCatalog = cateringCatalog; this.cateringCatalog = cateringCatalog;
this.catalogEntryRepository = catalogEntryRepository;
} }
/** /**
@ -261,6 +264,7 @@ class InventoryController {
@GetMapping("/inventory/delete/{pid}") @GetMapping("/inventory/delete/{pid}")
String delete(@PathVariable Product pid) { String delete(@PathVariable Product pid) {
UniqueInventoryItem item = inventory.findByProduct(pid).get(); UniqueInventoryItem item = inventory.findByProduct(pid).get();
catalogEntryRepository.deleteAll(catalogEntryRepository.findByProduct(pid));
inventory.delete(item); inventory.delete(item);
cateringCatalog.delete(pid); cateringCatalog.delete(pid);
return "redirect:/inventory"; return "redirect:/inventory";

View file

@ -5,6 +5,7 @@ package catering.order;
import catering.catalog.CateringCatalog; import catering.catalog.CateringCatalog;
import catering.catalog.Consumable; import catering.catalog.Consumable;
import catering.catalog.Rentable; import catering.catalog.Rentable;
import catering.orderCatalog.CartService;
import catering.staff.Employee; import catering.staff.Employee;
import catering.staff.JobType; import catering.staff.JobType;
import catering.staff.StaffManagement; import catering.staff.StaffManagement;
@ -17,6 +18,7 @@ import org.salespointframework.quantity.Quantity;
import org.salespointframework.useraccount.Role; import org.salespointframework.useraccount.Role;
import org.salespointframework.useraccount.UserAccount; import org.salespointframework.useraccount.UserAccount;
import org.salespointframework.useraccount.web.LoggedIn; import org.salespointframework.useraccount.web.LoggedIn;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -44,16 +46,21 @@ public class OrderController {
private final CateringCatalog catalog; private final CateringCatalog catalog;
private final StaffManagement staffManagement; private final StaffManagement staffManagement;
@Autowired
private CartService cartService;
public OrderController(OrderManagement<CustomOrder> orderManagement, public OrderController(OrderManagement<CustomOrder> orderManagement,
CustomOrderRepository customOrderRepository, CustomOrderRepository customOrderRepository,
UniqueInventory<UniqueInventoryItem> inventory, UniqueInventory<UniqueInventoryItem> inventory,
CateringCatalog catalog, CateringCatalog catalog,
StaffManagement staffManagement) { StaffManagement staffManagement,
CartService cartService) {
this.orderManagement = orderManagement; this.orderManagement = orderManagement;
this.customOrderRepository = customOrderRepository; this.customOrderRepository = customOrderRepository;
this.catalog = catalog; this.catalog = catalog;
this.inventory = inventory; this.inventory = inventory;
this.staffManagement = staffManagement; this.staffManagement = staffManagement;
this.cartService = cartService;
} }
@GetMapping("/myOrders") @GetMapping("/myOrders")
@ -109,8 +116,7 @@ public class OrderController {
@ModelAttribute("event") @ModelAttribute("event")
CustomCart initializeCart() { CustomCart initializeCart() {
return new CustomCart(OrderType.EVENT_CATERING, LocalDateTime.now().plusDays(7), return cartService.getCart();
LocalDateTime.now().plusDays(7).plusHours(1));
} }
@GetMapping("/event") @GetMapping("/event")

View file

@ -0,0 +1,29 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-FileCopyrightText: 2023-2024 swt23w23
package catering.orderCatalog;
import catering.order.CustomCart;
import catering.order.OrderType;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
@Service
@Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class CartService {
private CustomCart cart;
public CartService() {
this.cart = new CustomCart(OrderType.EVENT_CATERING, LocalDateTime.now().plusDays(7),
LocalDateTime.now().plusDays(7).plusHours(1));
}
public CustomCart getCart() {
return cart;
}
public void setCart(CustomCart cart) {
this.cart = cart;
}
}

View file

@ -3,10 +3,14 @@
package catering.orderCatalog; package catering.orderCatalog;
import catering.order.OrderType; import catering.order.OrderType;
import catering.staff.Employee;
import catering.staff.StaffManagement;
import org.javamoney.moneta.Money;
import org.salespointframework.catalog.Product; import org.salespointframework.catalog.Product;
import org.salespointframework.inventory.UniqueInventory; import org.salespointframework.inventory.UniqueInventory;
import org.salespointframework.inventory.UniqueInventoryItem; import org.salespointframework.inventory.UniqueInventoryItem;
import org.salespointframework.quantity.Quantity; import org.salespointframework.quantity.Quantity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@ -23,16 +27,20 @@ public class CatalogController {
private CustomCatalogEntry formCatalogEntry; private CustomCatalogEntry formCatalogEntry;
private final UniqueInventory<UniqueInventoryItem> inventory; private final UniqueInventory<UniqueInventoryItem> inventory;
@Autowired
private CartService cartService;
public CatalogController(CustomCatalogEntryRepository catalogEntryRepository, public CatalogController(CustomCatalogEntryRepository catalogEntryRepository,
UniqueInventory<UniqueInventoryItem> inventory) { UniqueInventory<UniqueInventoryItem> inventory,
CartService cartService) {
productMap = new HashMap<>(); productMap = new HashMap<>();
formCatalogEntry = new CustomCatalogEntry( formCatalogEntry = new CustomCatalogEntry(
OrderType.EVENT_CATERING, OrderType.EVENT_CATERING,
new HashMap<String, Quantity>(), new HashMap<Product, Quantity>(),
0, Money.of(0, "EUR"));
0);
this.catalogEntryRepository = catalogEntryRepository; this.catalogEntryRepository = catalogEntryRepository;
this.inventory = inventory; this.inventory = inventory;
this.cartService = cartService;
} }
@GetMapping("/catalog") @GetMapping("/catalog")
@ -42,34 +50,35 @@ public class CatalogController {
} }
@GetMapping("/catalog_editor") @GetMapping("/catalog_editor")
public String editCatalog(Model model) {; public String editCatalog(Model model) {
model.addAttribute("productMap", productMap); model.addAttribute("productMap", productMap);
model.addAttribute("formCatalogEntry", formCatalogEntry); model.addAttribute("formCatalogEntry", formCatalogEntry);
model.addAttribute("inventory", inventory.findAll().stream().collect(Collectors.toList())); model.addAttribute("inventory", inventory.findAll().stream()
.filter(i -> i.getProduct()
.getCategories()
.stream().anyMatch(c -> c.equals(formCatalogEntry.getEventType().toString())))
.collect(Collectors.toList()));
return "catalog_editor"; return "catalog_editor";
} }
@PostMapping("/catalog_editor/addCatalogEntry") @PostMapping("/catalog_editor/addCatalogEntry")
public String addCatalogEntry(@RequestParam OrderType eventType, public String addCatalogEntry(@RequestParam OrderType eventType,
@RequestParam int minimumTimePeriod, @RequestParam double totalCost,
@RequestParam int totalCost, Model model) {
Model model) { Map<Product, Quantity> products = new HashMap<Product, Quantity>();
Map<String, Quantity> products = new HashMap<String, Quantity>();
for (Product product : productMap.keySet()) { for (Product product : productMap.keySet()) {
products.put(product.getName(), productMap.get(product)); products.put(product, productMap.get(product));
} }
catalogEntryRepository.save(new CustomCatalogEntry( catalogEntryRepository.save(new CustomCatalogEntry(
eventType, eventType,
products, products,
minimumTimePeriod, Money.of(totalCost, "EUR")));
totalCost));
this.formCatalogEntry = new CustomCatalogEntry( this.formCatalogEntry = new CustomCatalogEntry(
OrderType.EVENT_CATERING, OrderType.EVENT_CATERING,
new HashMap<String, Quantity>(), new HashMap<Product, Quantity>(),
0, Money.of(0, "EUR"));
0);
productMap.clear(); productMap.clear();
model.addAttribute("productMap", productMap); model.addAttribute("productMap", productMap);
model.addAttribute("formCatalogEntry", formCatalogEntry); model.addAttribute("formCatalogEntry", formCatalogEntry);
@ -86,10 +95,10 @@ public class CatalogController {
@PostMapping("/catalog_editor/addProduct") @PostMapping("/catalog_editor/addProduct")
public String addProduct(@RequestParam("pid") Product product, @RequestParam("number") int number) { public String addProduct(@RequestParam("pid") Product product, @RequestParam("number") int number) {
Quantity amount = Quantity.of(number); Quantity amount = product.createQuantity(number);
productMap.compute(product, (key, existingQuantity) -> productMap.compute(product,
(existingQuantity == null) ? amount : existingQuantity.add(amount)); (key, existingQuantity) -> (existingQuantity == null) ? amount : existingQuantity.add(amount));
formCatalogEntry.addProduct(product.getName(), amount); formCatalogEntry.addProduct(product, amount);
return "redirect:/catalog_editor"; return "redirect:/catalog_editor";
} }
@ -107,31 +116,41 @@ public class CatalogController {
return "redirect:/catalog_editor"; return "redirect:/catalog_editor";
} }
@PostMapping("/catalog_editor/addTime")
public String addTime(@RequestParam int minimumTimePeriod,
@RequestParam OrderType eventType,
@RequestParam Map<String, Integer> products,
Model model) {
formCatalogEntry.setMinimumTimePeriod(minimumTimePeriod);
model.addAttribute("formCatalogEntry", formCatalogEntry);
return "redirect:/catalog_editor";
}
@PostMapping("/catalog_editor/chooseEvent") @PostMapping("/catalog_editor/chooseEvent")
public String chooseEvent(String events) { public String chooseEvent(String eventType) {
switch (events) { try {
case "mobile_breakfast": formCatalogEntry.setEventType(OrderType.valueOf(eventType));
formCatalogEntry.setEventType(OrderType.MOBILE_BREAKFAST); } catch (IllegalArgumentException e) {
break;
case "rent_a_cook":
formCatalogEntry.setEventType(OrderType.RENT_A_COOK);
break;
default:
formCatalogEntry.setEventType(OrderType.EVENT_CATERING); formCatalogEntry.setEventType(OrderType.EVENT_CATERING);
break; }
} productMap.clear();
return "redirect:/catalog_editor"; return "redirect:/catalog_editor";
} }
@PostMapping("/catalog/addToCart")
public String addToCart(@RequestParam long catalogEntryID) {
if (cartService.getCart() == null) {
return "redirect:/event";
}
CustomCatalogEntry entry = catalogEntryRepository.findById(catalogEntryID).get();
for (Product product : entry.getProducts().keySet()) {
// Checks if inventory has sufficient Quantity of a Product
Quantity cartQuantity = cartService.getCart().getQuantity(product);
if (entry.getProducts().get(product).add(cartQuantity).isGreaterThan(
inventory.findByProduct(product).get().getQuantity())) {
cartService.getCart().addOrUpdateItem(
product,
inventory.findByProduct(product).get().getQuantity().subtract(cartQuantity));
} else {
cartService.getCart().addOrUpdateItem(
product,
entry.getProducts().get(product));
}
}
cartService.getCart().setOrderType(entry.getEventType());
return "redirect:/event";
}
} }

View file

@ -4,9 +4,11 @@ package catering.orderCatalog;
import catering.order.OrderType; import catering.order.OrderType;
import jakarta.persistence.*; import jakarta.persistence.*;
import org.javamoney.moneta.Money;
import org.salespointframework.catalog.Product;
import org.salespointframework.quantity.Quantity; import org.salespointframework.quantity.Quantity;
import java.util.Iterator; import javax.money.MonetaryAmount;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
@ -19,14 +21,12 @@ public class CustomCatalogEntry {
private OrderType eventType; private OrderType eventType;
@ElementCollection @ElementCollection
private Map<String, Quantity> products; private Map<Product, Quantity> products;
private int minimumTimePeriod; // Declared as int for simplification of the prototype. Only whole hours. private MonetaryAmount totalCost;
private int totalCost; // Should actually accumulate the price of all items.
public CustomCatalogEntry(OrderType eventType, Map<String, Quantity> products, int minimumTimePeriod, int totalCost) { public CustomCatalogEntry(OrderType eventType, Map<Product, Quantity> products, MonetaryAmount totalCost) {
this.eventType = eventType; this.eventType = eventType;
this.products = products; this.products = products;
this.minimumTimePeriod = minimumTimePeriod;
this.totalCost = totalCost; this.totalCost = totalCost;
} }
@ -42,21 +42,16 @@ public class CustomCatalogEntry {
return eventType; return eventType;
} }
public Map<String, Quantity> getProducts() { public Map<Product, Quantity> getProducts() {
return products; return products;
} }
public int getMinimumTimePeriod() { public MonetaryAmount getTotalCost() {
return minimumTimePeriod; MonetaryAmount totalCost = products.entrySet().stream()
} .map(e -> e.getKey().getPrice().multiply(e.getValue().getAmount().doubleValue()))
.reduce(Money.of(0, "EUR"), MonetaryAmount::add);
public int getTotalCost() { setTotalCost(totalCost);
totalCost = 0;
Iterator<Quantity> iterator = products.values().iterator();
while (iterator.hasNext()) {
Quantity currentQuantity = iterator.next();
totalCost += currentQuantity.getAmount().intValue();
}
return totalCost; return totalCost;
} }
@ -64,18 +59,19 @@ public class CustomCatalogEntry {
this.eventType = eventType; this.eventType = eventType;
} }
public void addProduct(String name, Quantity count) { public void addProduct(Product product, Quantity count) {
this.products.put(name, count); if (products.containsKey(product)) {
this.products.put(product, products.get(product).add(count));
} else {
this.products.put(product, count);
}
} }
public void setProducts(Map<String, Quantity> products) { public void setProducts(Map<Product, Quantity> products) {
this.products = products; this.products = products;
} }
public void setMinimumTimePeriod(int timePeriod) {
this.minimumTimePeriod = timePeriod;
}
public void setTotalCost(int totalCost) { public void setTotalCost(MonetaryAmount totalCost) {
this.totalCost = totalCost; this.totalCost = totalCost;
} }

View file

@ -1,5 +1,5 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 swt23w23 // SPDX-FileCopyrightText: 2023-2024 swt23w23
package catering.orderCatalog; package catering.orderCatalog;
import catering.order.OrderType; import catering.order.OrderType;
@ -7,43 +7,40 @@ import org.salespointframework.catalog.Product;
import org.salespointframework.core.DataInitializer; import org.salespointframework.core.DataInitializer;
import org.salespointframework.quantity.Quantity; import org.salespointframework.quantity.Quantity;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import org.salespointframework.inventory.UniqueInventory;
import org.salespointframework.inventory.UniqueInventoryItem;
import javax.money.CurrencyUnit;
import javax.money.Monetary;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import org.javamoney.moneta.Money;
@Component @Component
public class CustomCatalogEntryDataInitializer implements DataInitializer { public class CustomCatalogEntryDataInitializer implements DataInitializer {
private CustomCatalogEntryRepository catalogEntryRepository; private CustomCatalogEntryRepository catalogEntryRepository;
private final UniqueInventory<UniqueInventoryItem> inventory;
public CustomCatalogEntryDataInitializer(CustomCatalogEntryRepository catalogEntryRepository) { public CustomCatalogEntryDataInitializer(CustomCatalogEntryRepository catalogEntryRepository, UniqueInventory<UniqueInventoryItem> inventory) {
this.catalogEntryRepository = catalogEntryRepository; this.catalogEntryRepository = catalogEntryRepository;
this.inventory = inventory;
} }
@Override @Override
public void initialize() { public void initialize() {
Map<Product, Quantity> products = new HashMap<>(); Map<Product, Quantity> products = new HashMap<>();
CurrencyUnit currencyUnit = Monetary.getCurrency("EUR"); Product product1 = inventory.findAll().stream().toList().get(0).getProduct();
products.put(new Product("Brötchen", Monetary.getDefaultAmountFactory() Product product2 = inventory.findAll().stream().toList().get(1).getProduct();
.setCurrency(currencyUnit) Product product3 = inventory.findAll().stream().toList().get(2).getProduct();
.setNumber(1)
.create()), Quantity.of(30)); products.put(product1, product1.createQuantity(30));
products.put(new Product("Kerze", Monetary.getDefaultAmountFactory() products.put(product2, product2.createQuantity(15));
.setCurrency(currencyUnit) products.put(product3, product3.createQuantity(1));
.setNumber(2)
.create()), Quantity.of(20));
Map<String, Quantity> productsFormatted = new HashMap<String, Quantity>();
for (Product product : products.keySet()) {
productsFormatted.put(product.getName(), products.get(product));
}
catalogEntryRepository.save( catalogEntryRepository.save(
new CustomCatalogEntry( new CustomCatalogEntry(
OrderType.EVENT_CATERING, OrderType.EVENT_CATERING,
productsFormatted, products,
4, Money.of(500, "EUR")));
500));
} }
} }

View file

@ -1,11 +1,16 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
// SPDX-FileCopyrightText: 2023 swt23w23 // SPDX-FileCopyrightText: 2023-2024 swt23w23
package catering.orderCatalog; package catering.orderCatalog;
import org.salespointframework.catalog.Product;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.util.Streamable; import org.springframework.data.util.Streamable;
interface CustomCatalogEntryRepository extends CrudRepository<CustomCatalogEntry, Long> { public interface CustomCatalogEntryRepository extends CrudRepository<CustomCatalogEntry, Long> {
@Override @Override
Streamable<CustomCatalogEntry> findAll(); Streamable<CustomCatalogEntry> findAll();
@Query("SELECT entry FROM CustomCatalogEntry entry JOIN entry.products p WHERE KEY(p) = :product")
Streamable<CustomCatalogEntry> findByProduct(Product product);
} }

View file

@ -14,22 +14,16 @@ SPDX-FileCopyrightText: 2023-2024 swt23w23
<div> <div>
<table class="table"> <table class="table">
<tr> <tr>
<th></th> <th>Eventtyp</th>
<th>Name</th>
<th>Mindestzeitraum</th>
<th>Basisausstattung</th> <th>Basisausstattung</th>
<th>Preis ab</th> <th>Preis ab</th>
<th></th> <th></th>
</tr> </tr>
<tr th:each="catalogEntry : ${catalogEntries}"> <tr th:each="catalogEntry : ${catalogEntries}">
<td> <td th:text="${catalogEntry.eventType.toHumanReadable()}">
**BILD**
</td>
<td th:text="${catalogEntry.getEventType()}">
<td th:text="${catalogEntry.getMinimumTimePeriod()} + ' h'">
<td> <td>
<ul th:each="product : ${catalogEntry.getProducts()}"> <ul th:each="product : ${catalogEntry.getProducts()}">
<li th:text="${product.getKey().toString()} + ': ' + ${product.getValue().toString()}"/> <li th:text="${product.getKey().getName()} + ': ' + ${product.getValue().toString()}"/>
</ul> </ul>
</td> </td>
<td th:text="${catalogEntry.getTotalCost()}"> <td th:text="${catalogEntry.getTotalCost()}">
@ -39,6 +33,13 @@ SPDX-FileCopyrightText: 2023-2024 swt23w23
<button class="btn btn-danger" type="submit">Entfernen</button> <button class="btn btn-danger" type="submit">Entfernen</button>
</form> </form>
</td> </td>
<td>
<form method="post" sec:authorize="hasRole('CUSTOMER')" th:action="@{/catalog/addToCart}">
<input type="hidden" name="catalogEntryID" th:value="${catalogEntry.getId()}">
<button class="btn btn-primary" type="submit">Zum Eventplaner hinzufügen</button>
</form>
</td>
<td></td>
</tr> </tr>
</table> </table>
</div> </div>

View file

@ -13,27 +13,15 @@ SPDX-FileCopyrightText: 2023-2024 swt23w23
<input type="hidden" name="addCatalog"> <input type="hidden" name="addCatalog">
<div class="content"> <div class="content">
<h2>Dienstleistung</h2> <h2>Dienstleistung</h2>
<label class="form-label" for="events"></label> <form class="my-4" th:object="${formCatalogEntry}" method="post" th:action="@{/catalog_editor/chooseEvent}">
<form th:object="${formCatalogEntry}" method="post" th:action="@{/catalog_editor/chooseEvent}"> <select class="form-select w-auto d-inline-block" name="eventType">
<select class="form-select w-auto d-inline-block" name="events" id="events"> <option th:selected="${formCatalogEntry.getEventType().toString() == enumValue.toString()}" th:each="enumValue : ${T(catering.order.OrderType).values()}" th:value="${enumValue.toString()}" th:text="${enumValue.toHumanReadable()}"/>
<option value="event_catering">Eventcatering</option>
<option value="mobile_breakfast">Mobile Breakfast</option>
<option value="rent_a_cook">Rent-a-Cook</option>
</select> </select>
<button class="btn btn-secondary" type="submit">Event auswählen</button> <button class="btn btn-secondary" type="submit">Event auswählen</button>
</form> </form>
Eventbasispreis: EUR 500
<h2>Mindestzeitraum</h2>
<form th:object="${formCatalogEntry}" method="post" th:action="@{/catalog_editor/addTime}">
<label class="form-label">Mindestzeitraum:</label>
<input type="hidden" th:field="*{eventType}">
<input class="form-control w-auto d-inline-block" th:field="*{minimumTimePeriod}" type="number" min="0" step="1" value="1"/>
<button class="btn btn-secondary" type="submit">Zum Model hinzufügen</button>
</form>
<h2>Basisausstattung</h2> <h2>Basisausstattung</h2>
<table class="table"> <table class="table my-4">
<tr> <tr>
<th>Produktname</th> <th>Produktname</th>
<th>Menge</th> <th>Menge</th>
@ -51,15 +39,37 @@ SPDX-FileCopyrightText: 2023-2024 swt23w23
</td> </td>
</tr> </tr>
</table> </table>
<h2>Produktauswahl</h2> <h2 class="mb-4">Produktauswahl</h2>
<table class="table"> <h3>Verbrauchsgegenstände</h3>
<table class="table my-4">
<tr> <tr>
<th>Produktname</th> <th>Produktname</th>
<th>Kosten</th> <th>Kosten</th>
<th>Verfügbar</th> <th>Verfügbar</th>
<th>Menge</th> <th>Menge</th>
</tr> </tr>
<tr th:each="item : ${inventory}"> <tr th:each="item : ${inventory}" th:if="${item.getProduct().getClass().simpleName == 'Consumable'}">
<td th:text="${item.getProduct().getName()}">Name</td>
<td th:text="${item.getProduct().getPrice()}">Preis</td>
<td th:text="${item.getQuantity()}">Verfügbar</td>
<td>
<form th:action="@{/catalog_editor/addProduct}" method="post">
<input id="number" type="number" name="number" min="1" value="1"/>
<input type="hidden" name="pid" th:value="${item.getProduct().getId()}"/>
<input class="btn btn-primary" type="submit" th:value="Hinzufügen"/>
</form>
</td>
</tr>
</table>
<h3>Mietgegenstände</h3>
<table class="table my-4">
<tr>
<th>Produktname</th>
<th>Kosten</th>
<th>Verfügbar</th>
<th>Menge</th>
</tr>
<tr th:each="item : ${inventory}" th:if="${item.getProduct().getClass().simpleName == 'Rentable'}">
<td th:text="${item.getProduct().getName()}">Name</td> <td th:text="${item.getProduct().getName()}">Name</td>
<td th:text="${item.getProduct().getPrice()}">Preis</td> <td th:text="${item.getProduct().getPrice()}">Preis</td>
<td th:text="${item.getQuantity()}">Verfügbar</td> <td th:text="${item.getQuantity()}">Verfügbar</td>
@ -72,16 +82,14 @@ SPDX-FileCopyrightText: 2023-2024 swt23w23
</td> </td>
</tr> </tr>
</table> </table>
<br>
<h3>Momentane Auswahl</h3> <h3>Momentane Auswahl</h3>
Eventtyp: <span th:text="${formCatalogEntry.getEventType()}"></span><br> Eventtyp: <span th:text="${formCatalogEntry.getEventType().toHumanReadable()}"></span>
Mindestzeitraum: <span th:text="${formCatalogEntry.getMinimumTimePeriod()} + ' h'"></span><br> <br>
<span th:text="'Gesamtpreis: ' + ${formCatalogEntry.getTotalCost()}">Gesamtpreis</span> <span th:text="'Gesamtpreis: ' + ${formCatalogEntry.getTotalCost()}">Gesamtpreis</span>
<form method="post" th:action="@{/catalog_editor/addCatalogEntry}" th:object="${formCatalogEntry}"> <form method="post" th:action="@{/catalog_editor/addCatalogEntry}" th:object="${formCatalogEntry}">
<input type="hidden" name="eventType" th:value="${formCatalogEntry.getEventType()}"> <input type="hidden" name="eventType" th:value="${formCatalogEntry.getEventType()}">
<input type="hidden" name="products" th:value="${productSet}"> <input type="hidden" name="products" th:value="${productSet}">
<input type="hidden" name="minimumTimePeriod" th:value="${formCatalogEntry.getMinimumTimePeriod}"> <input type="hidden" name="totalCost" th:value="${formCatalogEntry.getTotalCost().getNumber().doubleValueExact()}">
<input type="hidden" name="totalCost" th:value="${formCatalogEntry.getTotalCost()}">
<button class="btn btn-primary" type="submit">Zum Katalog hinzufügen</button> <button class="btn btn-primary" type="submit">Zum Katalog hinzufügen</button>
</form> </form>
</div> </div>

View file

@ -25,6 +25,7 @@ import java.math.BigDecimal;
import java.util.Optional; import java.util.Optional;
import java.util.Set; import java.util.Set;
import catering.orderCatalog.CustomCatalogEntryRepository;
import org.javamoney.moneta.Money; import org.javamoney.moneta.Money;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -60,6 +61,9 @@ class InventoryControllerIntegrationTests {
@Autowired @Autowired
CateringCatalog catalog; CateringCatalog catalog;
@Autowired
private CustomCatalogEntryRepository catalogEntryRepository;
UniqueInventoryItem anyConsumableItem; UniqueInventoryItem anyConsumableItem;
UniqueInventoryItem anyRentableItem; UniqueInventoryItem anyRentableItem;
Consumable anyConsumable; Consumable anyConsumable;
@ -101,6 +105,7 @@ class InventoryControllerIntegrationTests {
@BeforeEach @BeforeEach
void populateAnyInventoryItem() { void populateAnyInventoryItem() {
catalogEntryRepository.deleteAll();
inventory.deleteAll(); inventory.deleteAll();
catalog.deleteAll(); catalog.deleteAll();

View file

@ -0,0 +1,98 @@
package catering.orderCatalog;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
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.result.MockMvcResultMatchers.model;
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.view;
import java.util.HashMap;
import java.util.Map;
import org.javamoney.moneta.Money;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.salespointframework.catalog.Product;
import org.salespointframework.inventory.UniqueInventory;
import org.salespointframework.inventory.UniqueInventoryItem;
import org.salespointframework.quantity.Quantity;
import org.salespointframework.useraccount.Password;
import org.salespointframework.useraccount.Role;
import org.salespointframework.useraccount.UserAccountManagement;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
import catering.order.OrderType;
@SpringBootTest
@AutoConfigureMockMvc
public class CatalogControllerIntegrationTests {
@Autowired
private MockMvc mockMvc;
@Autowired
private CatalogController catalogController;
@Autowired
UserAccountManagement userAccountManagement;
@Autowired
private CustomCatalogEntryRepository catalogEntryRepository;
@Autowired
private UniqueInventory<UniqueInventoryItem> inventory;
@BeforeEach
void setUp() {
if (userAccountManagement.findByUsername("anna").isEmpty()) {
userAccountManagement.create("anna",
Password.UnencryptedPassword.of("12345"), Role.of("CUSTOMER"));
}
if (catalogEntryRepository.findAll().isEmpty()) {
Map<Product, Quantity> products = new HashMap<>();
products.put(inventory.findAll().stream().toList().get(0).getProduct(), Quantity.of(1));
CustomCatalogEntry testCatalogEntry = new CustomCatalogEntry(
OrderType.RENT_A_COOK,
products,
Money.of(3000, "EUR"));
catalogEntryRepository.save(testCatalogEntry);
}
}
@Test
void testCustomerViewsCatalog() throws Exception {
this.mockMvc.perform(get("/catalog").with(user("anna").roles("CUSTOMER")))
.andExpect(status().isOk())
.andExpect(view().name("catalog"))
.andExpect(model().attributeExists("catalogEntries"));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void testAddCatalogEntry() throws Exception {
this.mockMvc.perform(post("/catalog_editor/addCatalogEntry")
.param("eventType", "MOBILE_BREAKFAST")
.param("totalCost", "5000"))
.andExpect(redirectedUrl("/catalog"));
}
@Test
@WithMockUser(username = "admin", roles = "ADMIN")
void testRemoveCatalogEntry() throws Exception {
this.mockMvc.perform(post("/catalog/remove")
.param("catalogEntryID",
String.valueOf(catalogEntryRepository.findAll()
.stream().toList()
.get(0)
.getId())))
.andExpect(redirectedUrl("/catalog"));
}
}

View file

@ -0,0 +1,48 @@
package catering.orderCatalog;
import static org.assertj.core.api.Assertions.assertThat;
import java.util.HashMap;
import java.util.Map;
import org.javamoney.moneta.Money;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.salespointframework.catalog.Product;
import org.salespointframework.inventory.UniqueInventory;
import org.salespointframework.inventory.UniqueInventoryItem;
import org.salespointframework.quantity.Quantity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import catering.order.OrderType;
@SpringBootTest
public class CatalogControllerUnitTests {
@Autowired
CustomCatalogEntryRepository catalogEntryRepository;
@Autowired
UniqueInventory<UniqueInventoryItem> inventory;
@BeforeEach
void setup() {
Product product1 = inventory.findAll().stream().toList().get(0).getProduct();
Map<Product, Quantity> products = new HashMap<Product, Quantity>();
products.put(product1, Quantity.of(1));
CustomCatalogEntry catalogEntry1 = new CustomCatalogEntry(
OrderType.EVENT_CATERING,
products,
Money.of(0, "EUR"));
catalogEntryRepository.save(catalogEntry1);
}
@Test
void countOfEntries() {
// Includes entry of data initializer
assertThat(catalogEntryRepository.findAll().stream().count()).isEqualTo(2);
}
}

View file

@ -0,0 +1,61 @@
package catering.orderCatalog;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.HashMap;
import java.util.Map;
import org.javamoney.moneta.Money;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.salespointframework.catalog.Product;
import org.salespointframework.inventory.UniqueInventory;
import org.salespointframework.inventory.UniqueInventoryItem;
import org.salespointframework.quantity.Quantity;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import catering.order.OrderType;
@SpringBootTest
public class CatalogUnitTests {
@Autowired
CustomCatalogEntryRepository catalogEntryRepository;
@Autowired
UniqueInventory<UniqueInventoryItem> inventory;
private CustomCatalogEntry catalogEntry;
@BeforeEach
void setUp() {
Product product1 = inventory.findAll().stream().toList().get(0).getProduct();
Map<Product, Quantity> products = new HashMap<Product, Quantity>();
products.put(product1, Quantity.of(1));
this.catalogEntry = new CustomCatalogEntry(
OrderType.EVENT_CATERING,
products,
Money.of(0, "EUR"));
}
@Test
void testAddProduct() {
catalogEntry.addProduct(inventory.findAll().stream().toList().get(0).getProduct(), Quantity.of(1));
assertTrue(catalogEntry.getProducts().containsValue(Quantity.of(2)));
}
@Test
void testSetEventType() {
catalogEntry.setEventType(OrderType.PARTY_SERVICE);
assertTrue(catalogEntry.getEventType().equals(OrderType.PARTY_SERVICE));
}
// Getter is being tested because it calculates totalCost before
@Test
void testGetTotalCost() {
assertTrue(inventory.findAll().stream().toList().get(0).getProduct().getPrice()
.equals(catalogEntry.getTotalCost()));
}
}