mirror of
https://github.com/st-tu-dresden-praktikum/swt23w23
synced 2024-07-19 21:04:36 +02:00
Adapt inventory to new catalog interface
This also does a major restructuring of the inventory mutate form. Some things still are not as they should be, but it mostly works like before. They can be fixed later. Co-authored-by: Theo Reichert <theo.reichert@mailbox.tu-dresden.de>
This commit is contained in:
parent
a4099f1de0
commit
2dff2842fc
|
@ -18,8 +18,7 @@ package Salespoint {
|
||||||
|
|
||||||
package catering {
|
package catering {
|
||||||
package catalog {
|
package catalog {
|
||||||
interface ConsumableCatalog
|
interface CateringCatalog
|
||||||
interface RentableCatalog
|
|
||||||
}
|
}
|
||||||
|
|
||||||
package inventory {
|
package inventory {
|
||||||
|
@ -27,16 +26,21 @@ package catering {
|
||||||
+ InventoryController(inventory : UniqueInventory)
|
+ InventoryController(inventory : UniqueInventory)
|
||||||
+ list(model : Model) : String
|
+ list(model : Model) : String
|
||||||
+ edit(model : Model, pid : Product) : String
|
+ edit(model : Model, pid : Product) : String
|
||||||
+ edit(model : Model, form : InventoryMutateForm) : String
|
+ edit(model : Model, pid : Product, form : InventoryMutateForm) : String
|
||||||
|
+ editConsumable(form: ConsumableMutateForm, result: Errors, pid : Product, model : Model) : String
|
||||||
|
+ editRentable(form: RentableMutateForm, result: Errors, pid : Product, model : Model) : String
|
||||||
+ edit(form : InventoryMutateForm, result : Errors, pid : Product, model : Model) : String
|
+ edit(form : InventoryMutateForm, result : Errors, pid : Product, model : Model) : String
|
||||||
+ add(model : Model) : String
|
+ add(model : Model, type : String) : String
|
||||||
+ add(model : Model, form : InventoryMutateForm) : String
|
+ add(model : Model, form : InventoryMutateForm) : String
|
||||||
|
+ addConsumable(form : ConsumableMutateForm, result : Errors, model : Model) : String
|
||||||
|
+ addRentable(form : RentableMutateForm, result : Errors, model : Model) : String
|
||||||
+ add(form : InventoryMutateForm, result : Errors, model : Model) : String
|
+ add(form : InventoryMutateForm, result : Errors, model : Model) : String
|
||||||
+ delete(pid : Product) : String
|
+ delete(pid : Product) : String
|
||||||
}
|
}
|
||||||
InventoryController --> "1" catering.catalog.ConsumableCatalog : "-consumableCatalog"
|
InventoryController --> "1" catering.catalog.CateringCatalog : "-cateringCatalog"
|
||||||
InventoryController --> "1" catering.catalog.RentableCatalog : "-rentableCatalog"
|
|
||||||
InventoryController ..> InventoryMutateForm
|
InventoryController ..> InventoryMutateForm
|
||||||
|
InventoryController ..> ConsumableMutateForm
|
||||||
|
InventoryController ..> RentableMutateForm
|
||||||
InventoryController .u.> Salespoint.Product
|
InventoryController .u.> Salespoint.Product
|
||||||
InventoryController -u-> "1" Salespoint.UniqueInventory : "-inventory"
|
InventoryController -u-> "1" Salespoint.UniqueInventory : "-inventory"
|
||||||
InventoryController .u.> Salespoint.UniqueInventoryItem
|
InventoryController .u.> Salespoint.UniqueInventoryItem
|
||||||
|
@ -48,13 +52,28 @@ package catering {
|
||||||
+ InventoryInitializer(inventory : UniqueInventory, catalog : CateringCatalog)
|
+ InventoryInitializer(inventory : UniqueInventory, catalog : CateringCatalog)
|
||||||
+ initialize() : void
|
+ initialize() : void
|
||||||
}
|
}
|
||||||
InventoryInitializer --> "1" catering.catalog.ConsumableCatalog : "-consumableCatalog"
|
InventoryInitializer --> "1" catering.catalog.CateringCatalog : "-cateringCatalog"
|
||||||
InventoryInitializer --> "1" catering.catalog.RentableCatalog : "-rentableCatalog"
|
|
||||||
InventoryInitializer .u.|> Salespoint.DataInitializer
|
InventoryInitializer .u.|> Salespoint.DataInitializer
|
||||||
InventoryInitializer .u.> Salespoint.Quantity
|
InventoryInitializer .u.> Salespoint.Quantity
|
||||||
InventoryInitializer -u-> "1" Salespoint.UniqueInventory : "-inventory"
|
InventoryInitializer -u-> "1" Salespoint.UniqueInventory : "-inventory"
|
||||||
InventoryInitializer .u.> Salespoint.UniqueInventoryItem
|
InventoryInitializer .u.> Salespoint.UniqueInventoryItem
|
||||||
InventoryInitializer .u.> Spring.Assert
|
InventoryInitializer .u.> Spring.Assert
|
||||||
|
|
||||||
|
class InventoryMutateForm
|
||||||
|
class ConsumableMutateForm
|
||||||
|
class RentableMutateForm
|
||||||
|
ConsumableMutateForm <|-- InventoryMutateForm
|
||||||
|
RentableMutateForm <|-- InventoryMutateForm
|
||||||
|
|
||||||
|
InventoryMutateForm ..> Salespoint.Quantity
|
||||||
|
|
||||||
|
InventoryMutateForm ..> Salespoint.Product
|
||||||
|
ConsumableMutateForm ..> Salespoint.Product
|
||||||
|
RentableMutateForm ..> Salespoint.Product
|
||||||
|
|
||||||
|
InventoryMutateForm ..> Salespoint.UniqueInventoryItem
|
||||||
|
ConsumableMutateForm ..> Salespoint.UniqueInventoryItem
|
||||||
|
RentableMutateForm ..> Salespoint.UniqueInventoryItem
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@enduml
|
@enduml
|
||||||
|
|
BIN
src/main/asciidoc/models/design/inventory.svg
(Stored with Git LFS)
BIN
src/main/asciidoc/models/design/inventory.svg
(Stored with Git LFS)
Binary file not shown.
|
@ -18,12 +18,17 @@ package catering.catalog;
|
||||||
|
|
||||||
import static org.salespointframework.core.Currencies.EURO;
|
import static org.salespointframework.core.Currencies.EURO;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.javamoney.moneta.Money;
|
import org.javamoney.moneta.Money;
|
||||||
import org.salespointframework.core.DataInitializer;
|
import org.salespointframework.core.DataInitializer;
|
||||||
import org.springframework.core.annotation.Order;
|
import org.springframework.core.annotation.Order;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import catering.order.OrderType;
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
@Order(20)
|
@Order(20)
|
||||||
class CatalogDataInitializer implements DataInitializer {
|
class CatalogDataInitializer implements DataInitializer {
|
||||||
|
@ -41,9 +46,21 @@ class CatalogDataInitializer implements DataInitializer {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
cateringCatalog.save(new CatalogDummy("Brötchen Vollkorn", CatalogDummyType.CONSUMABLE, Money.of(1, EURO), Money.of(0.5, EURO),
|
cateringCatalog.save(new Consumable(
|
||||||
Money.of(0.75, EURO)));
|
"Brötchen Vollkorn",
|
||||||
cateringCatalog.save(new CatalogDummy("Kerze Rot", CatalogDummyType.CONSUMABLE, Money.of(2, EURO), Money.of(1.5, EURO)));
|
Money.of(1, EURO),
|
||||||
cateringCatalog.save(new CatalogDummy("Brotschneidemaschine Power X 3000", CatalogDummyType.RENTABLE, Money.of(25, EURO), Money.of(10000, EURO)));
|
Money.of(0.5, EURO),
|
||||||
|
Optional.of(Money.of(0.75, EURO)),
|
||||||
|
Set.of(OrderType.EVENT_CATERING, OrderType.MOBILE_BREAKFAST)));
|
||||||
|
cateringCatalog.save(new Rentable(
|
||||||
|
"Kerze Rot",
|
||||||
|
Money.of(2, EURO),
|
||||||
|
Money.of(1.5, EURO),
|
||||||
|
Set.of(OrderType.EVENT_CATERING, OrderType.MOBILE_BREAKFAST)));
|
||||||
|
cateringCatalog.save(new Rentable(
|
||||||
|
"Brotschneidemaschine Power X 3000",
|
||||||
|
Money.of(25, EURO),
|
||||||
|
Money.of(10000, EURO),
|
||||||
|
Set.of(OrderType.EVENT_CATERING, OrderType.MOBILE_BREAKFAST)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,79 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2023 Simon Bruder
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package catering.catalog;
|
|
||||||
|
|
||||||
import javax.money.MonetaryAmount;
|
|
||||||
|
|
||||||
import org.salespointframework.catalog.Product;
|
|
||||||
import org.springframework.util.Assert;
|
|
||||||
|
|
||||||
import jakarta.persistence.Entity;
|
|
||||||
|
|
||||||
@Entity
|
|
||||||
public class CatalogDummy extends Product {
|
|
||||||
MonetaryAmount wholesalePrice;
|
|
||||||
// Optional<MonetaryAmount> promotionPrice;
|
|
||||||
// enterprise java goes brrr
|
|
||||||
MonetaryAmount promotionPrice;
|
|
||||||
CatalogDummyType type;
|
|
||||||
|
|
||||||
// enterprise java goes brrr
|
|
||||||
@SuppressWarnings({ "unused", "deprecation" })
|
|
||||||
private CatalogDummy() {
|
|
||||||
}
|
|
||||||
|
|
||||||
public CatalogDummy(String name, CatalogDummyType type, MonetaryAmount price, MonetaryAmount wholesalePrice,
|
|
||||||
MonetaryAmount promotionPrice) {
|
|
||||||
super(name, price);
|
|
||||||
Assert.notNull(type, "Type must not be null!");
|
|
||||||
Assert.notNull(wholesalePrice, "WholesalePricee must not be null!");
|
|
||||||
// PromotionPrice can be null.
|
|
||||||
this.type = type;
|
|
||||||
this.wholesalePrice = wholesalePrice;
|
|
||||||
this.promotionPrice = promotionPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CatalogDummy(String name, CatalogDummyType type, MonetaryAmount price, MonetaryAmount wholesalePrice) {
|
|
||||||
this(name, type, price, wholesalePrice, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public MonetaryAmount getWholesalePrice() {
|
|
||||||
return wholesalePrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setWholesalePrice(MonetaryAmount wholesalePrice) {
|
|
||||||
Assert.notNull(wholesalePrice, "WholesalePricee must not be null!");
|
|
||||||
this.wholesalePrice = wholesalePrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MonetaryAmount getPromotionPrice() {
|
|
||||||
return promotionPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPromotionPrice(MonetaryAmount promotionPrice) {
|
|
||||||
this.promotionPrice = promotionPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public CatalogDummyType getType() {
|
|
||||||
return this.type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setType(CatalogDummyType type) {
|
|
||||||
Assert.notNull(type, "Type must not be null!");
|
|
||||||
this.type = type;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (C) 2023 Simon Bruder
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU Affero General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU Affero General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
package catering.catalog;
|
|
||||||
|
|
||||||
public enum CatalogDummyType {
|
|
||||||
CONSUMABLE,
|
|
||||||
RENTABLE,
|
|
||||||
}
|
|
87
src/main/java/catering/inventory/ConsumableMutateForm.java
Normal file
87
src/main/java/catering/inventory/ConsumableMutateForm.java
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Simon Bruder
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package catering.inventory;
|
||||||
|
|
||||||
|
import static org.salespointframework.core.Currencies.EURO;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import javax.money.MonetaryAmount;
|
||||||
|
import javax.money.NumberValue;
|
||||||
|
|
||||||
|
import org.javamoney.moneta.Money;
|
||||||
|
import org.salespointframework.catalog.Product;
|
||||||
|
import org.salespointframework.inventory.UniqueInventoryItem;
|
||||||
|
|
||||||
|
import catering.catalog.Consumable;
|
||||||
|
import catering.order.OrderType;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.PositiveOrZero; // NonNegative in enterprise java
|
||||||
|
|
||||||
|
class ConsumableMutateForm extends InventoryMutateForm {
|
||||||
|
private @NotNull @PositiveOrZero Double wholesalePrice;
|
||||||
|
private @NotNull Optional<@PositiveOrZero Double> promotionPrice = Optional.empty();
|
||||||
|
|
||||||
|
public Double getWholesalePrice() {
|
||||||
|
return wholesalePrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<Double> getPromotionPrice() {
|
||||||
|
return promotionPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWholesalePrice(Double wholesalePrice) {
|
||||||
|
this.wholesalePrice = wholesalePrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPromotionPrice(Optional<Double> promotionPrice) {
|
||||||
|
this.promotionPrice = promotionPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Product toProduct() {
|
||||||
|
return new Consumable(
|
||||||
|
getName(),
|
||||||
|
Money.of(getRetailPrice(), EURO),
|
||||||
|
Money.of(getWholesalePrice(), EURO),
|
||||||
|
getPromotionPrice().map(price -> Money.of(price, EURO)),
|
||||||
|
// FIXME: categories for creation of consumables are hardcoded
|
||||||
|
Set.of(OrderType.EVENT_CATERING, OrderType.MOBILE_BREAKFAST));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConsumableMutateForm of(Consumable product, UniqueInventoryItem item) {
|
||||||
|
ConsumableMutateForm form = new ConsumableMutateForm();
|
||||||
|
form.setName(product.getName());
|
||||||
|
form.setQuantity(item.getQuantity());
|
||||||
|
form.setWholesalePrice(product.getWholesalePrice().getNumber().doubleValueExact());
|
||||||
|
form.setRetailPrice(product.getRetailPrice().getNumber().doubleValueExact());
|
||||||
|
form.setPromotionPrice(
|
||||||
|
product.getPromotionPrice().map(MonetaryAmount::getNumber).map(NumberValue::doubleValueExact));
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void modifyProductPrimitive(Product product) {
|
||||||
|
if (product instanceof Consumable consumable) {
|
||||||
|
consumable.setWholesalePrice(Money.of(getWholesalePrice(), EURO));
|
||||||
|
consumable.setPromotionPrice(getPromotionPrice().map(price -> Money.of(price, EURO)));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("ConsumableMutateForm can only modify instances of Consumable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,14 +16,6 @@
|
||||||
*/
|
*/
|
||||||
package catering.inventory;
|
package catering.inventory;
|
||||||
|
|
||||||
import static org.salespointframework.core.Currencies.EURO;
|
|
||||||
|
|
||||||
import java.util.Optional;
|
|
||||||
|
|
||||||
import javax.money.MonetaryAmount;
|
|
||||||
import javax.money.NumberValue;
|
|
||||||
|
|
||||||
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;
|
||||||
|
@ -36,11 +28,35 @@ 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;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
import org.springframework.web.bind.annotation.PostMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
import catering.catalog.CatalogDummy;
|
|
||||||
import catering.catalog.CateringCatalog;
|
import catering.catalog.CateringCatalog;
|
||||||
import jakarta.validation.Valid;
|
import jakarta.validation.Valid;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* TODO TL;DR: Use HandlerMethodArgumentResolver
|
||||||
|
*
|
||||||
|
* This class currently has many confusing methods that do a variety of things,
|
||||||
|
* but mostly work around polymorphic restrictions of Spring.
|
||||||
|
* Ideally, spring would allow a form to be polymorphic
|
||||||
|
* based on its attributes, if they are unique,
|
||||||
|
* or based on another parameter.
|
||||||
|
* From what I could find,
|
||||||
|
* this is not possible without implementing a custom HandlerMethodArgumentResolver.
|
||||||
|
* If there is time, the controller should be changed to use this,
|
||||||
|
* which should vastly simplify it.
|
||||||
|
*
|
||||||
|
* Adding will always require a type parameter,
|
||||||
|
* as the type is not known at first.
|
||||||
|
* However, currently there are two (or even three) POST handlers,
|
||||||
|
* as there are two different form types it must support
|
||||||
|
* (and an infinite amount of invalid types that it should ignore).
|
||||||
|
*
|
||||||
|
* Editing should not require passing any additional type parameter around
|
||||||
|
* as the type can be inferred from the product.
|
||||||
|
* Changing the type of a product should not be possible.
|
||||||
|
*/
|
||||||
|
|
||||||
@Controller
|
@Controller
|
||||||
class InventoryController {
|
class InventoryController {
|
||||||
private final UniqueInventory<UniqueInventoryItem> inventory;
|
private final UniqueInventory<UniqueInventoryItem> inventory;
|
||||||
|
@ -64,17 +80,10 @@ class InventoryController {
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@GetMapping("/inventory/edit/{pid}")
|
@GetMapping("/inventory/edit/{pid}")
|
||||||
String edit(Model model, @PathVariable Product pid) {
|
String edit(Model model, @PathVariable Product pid) {
|
||||||
CatalogDummy product = (CatalogDummy) pid;
|
|
||||||
UniqueInventoryItem item = inventory.findByProduct(pid).get();
|
UniqueInventoryItem item = inventory.findByProduct(pid).get();
|
||||||
return edit(model,
|
final InventoryMutateForm form = InventoryMutateForm.of(pid, item);
|
||||||
new InventoryMutateForm(product.getType(),
|
|
||||||
product.getName(),
|
return edit(model, form);
|
||||||
item.getQuantity(),
|
|
||||||
product.getWholesalePrice().getNumber().doubleValueExact(),
|
|
||||||
product.getPrice().getNumber().doubleValueExact(),
|
|
||||||
Optional.ofNullable(product.getPromotionPrice())
|
|
||||||
.map(MonetaryAmount::getNumber)
|
|
||||||
.map(NumberValue::doubleValueExact)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String edit(Model model, InventoryMutateForm form) {
|
String edit(Model model, InventoryMutateForm form) {
|
||||||
|
@ -85,23 +94,28 @@ class InventoryController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@PostMapping("/inventory/edit/{pid}")
|
@PostMapping(path = "/inventory/edit/{pid}", params = "type=Consumable")
|
||||||
String edit(@Valid @ModelAttribute("form") InventoryMutateForm form, Errors result, @PathVariable Product pid,
|
String editConsumable(@Valid @ModelAttribute("form") ConsumableMutateForm form, Errors result,
|
||||||
Model model) {
|
@PathVariable Product pid, Model model) {
|
||||||
|
return edit(form, result, pid, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@PostMapping(path = "/inventory/edit/{pid}", params = "type=Rentable")
|
||||||
|
String editRentable(@Valid @ModelAttribute("form") RentableMutateForm form, Errors result,
|
||||||
|
@PathVariable Product pid, Model model) {
|
||||||
|
return edit(form, result, pid, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
String edit(InventoryMutateForm form, Errors result, Product product, Model model) {
|
||||||
if (result.hasErrors()) {
|
if (result.hasErrors()) {
|
||||||
return edit(model, form);
|
return edit(model, form);
|
||||||
}
|
}
|
||||||
CatalogDummy product = (CatalogDummy) pid;
|
|
||||||
UniqueInventoryItem item = inventory.findByProduct(pid).get();
|
|
||||||
|
|
||||||
product.setName(form.getName());
|
|
||||||
product.setType(form.getType());
|
|
||||||
product.setPrice(Money.of(form.getRetailPrice(), EURO));
|
|
||||||
product.setWholesalePrice(Money.of(form.getWholesalePrice(), EURO));
|
|
||||||
product.setPromotionPrice(form.getPromotionPrice().map(price -> Money.of(price, EURO)).orElse(null));
|
|
||||||
|
|
||||||
|
form.modifyProduct(product);
|
||||||
product = cateringCatalog.save(product);
|
product = cateringCatalog.save(product);
|
||||||
|
|
||||||
|
UniqueInventoryItem item = inventory.findByProduct(product).get();
|
||||||
// no setQuantity in enterprise java
|
// no setQuantity in enterprise java
|
||||||
// (though returing a modified object is actually nice)
|
// (though returing a modified object is actually nice)
|
||||||
inventory.save(item.increaseQuantity(form.getQuantity().subtract(item.getQuantity())));
|
inventory.save(item.increaseQuantity(form.getQuantity().subtract(item.getQuantity())));
|
||||||
|
@ -109,9 +123,17 @@ class InventoryController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@GetMapping("/inventory/add")
|
@GetMapping(path = "/inventory/add")
|
||||||
String add(Model model) {
|
String add(Model model, @RequestParam String type) {
|
||||||
return add(model, InventoryMutateForm.empty());
|
switch (type) {
|
||||||
|
case "Consumable":
|
||||||
|
return add(model, new ConsumableMutateForm());
|
||||||
|
case "Rentable":
|
||||||
|
return add(model, new RentableMutateForm());
|
||||||
|
default:
|
||||||
|
// TODO better error handling
|
||||||
|
return "redirect:/inventory";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
String add(Model model, InventoryMutateForm form) {
|
String add(Model model, InventoryMutateForm form) {
|
||||||
|
@ -121,17 +143,22 @@ class InventoryController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PreAuthorize("hasRole('ADMIN')")
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
@PostMapping("/inventory/add")
|
@PostMapping(path = "/inventory/add", params = "type=Consumable")
|
||||||
String add(@Valid @ModelAttribute("form") InventoryMutateForm form, Errors result, Model model) {
|
String addConsumable(@Valid @ModelAttribute("form") ConsumableMutateForm form, Errors result, Model model) {
|
||||||
|
return add(form, result, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreAuthorize("hasRole('ADMIN')")
|
||||||
|
@PostMapping(path = "/inventory/add", params = "type=Rentable")
|
||||||
|
String addRentable(@Valid @ModelAttribute("form") ConsumableMutateForm form, Errors result, Model model) {
|
||||||
|
return add(form, result, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
String add(@Valid InventoryMutateForm form, Errors result, Model model) {
|
||||||
if (result.hasErrors()) {
|
if (result.hasErrors()) {
|
||||||
return add(model, form);
|
return add(model, form);
|
||||||
}
|
}
|
||||||
inventory.save(new UniqueInventoryItem(
|
inventory.save(new UniqueInventoryItem(cateringCatalog.save(form.toProduct()), form.getQuantity()));
|
||||||
cateringCatalog
|
|
||||||
.save(new CatalogDummy(form.getName(), form.getType(), Money.of(form.getRetailPrice(), EURO),
|
|
||||||
Money.of(form.getWholesalePrice(), EURO),
|
|
||||||
form.getPromotionPrice().map(price -> Money.of(price, EURO)).orElse(null))),
|
|
||||||
form.getQuantity()));
|
|
||||||
return "redirect:/inventory";
|
return "redirect:/inventory";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -140,7 +167,7 @@ class InventoryController {
|
||||||
String delete(@PathVariable Product pid) {
|
String delete(@PathVariable Product pid) {
|
||||||
UniqueInventoryItem item = inventory.findByProduct(pid).get();
|
UniqueInventoryItem item = inventory.findByProduct(pid).get();
|
||||||
inventory.delete(item);
|
inventory.delete(item);
|
||||||
cateringCatalog.delete((CatalogDummy) pid);
|
cateringCatalog.delete(pid);
|
||||||
return "redirect:/inventory";
|
return "redirect:/inventory";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,39 +16,33 @@
|
||||||
*/
|
*/
|
||||||
package catering.inventory;
|
package catering.inventory;
|
||||||
|
|
||||||
import java.util.Optional;
|
import static org.salespointframework.core.Currencies.EURO;
|
||||||
|
|
||||||
|
import org.javamoney.moneta.Money;
|
||||||
|
import org.salespointframework.catalog.Product;
|
||||||
|
import org.salespointframework.inventory.UniqueInventoryItem;
|
||||||
import org.salespointframework.quantity.Quantity;
|
import org.salespointframework.quantity.Quantity;
|
||||||
|
|
||||||
import catering.catalog.CatalogDummyType;
|
import catering.catalog.Consumable;
|
||||||
|
import catering.catalog.Rentable;
|
||||||
import jakarta.validation.constraints.NotEmpty;
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
import jakarta.validation.constraints.NotNull;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import jakarta.validation.constraints.PositiveOrZero; // NonNegative in enterprise java
|
import jakarta.validation.constraints.PositiveOrZero; // NonNegative in enterprise java
|
||||||
|
|
||||||
class InventoryMutateForm {
|
/**
|
||||||
private final @NotNull CatalogDummyType type;
|
* Abstract class for handling inventory mutations.
|
||||||
private final @NotEmpty String name;
|
*
|
||||||
private final @NotNull Quantity quantity;
|
* It has children for every product type.
|
||||||
private final @NotNull double wholesalePrice, retailPrice;
|
*
|
||||||
private final @NotNull Optional<Double> promotionPrice;
|
* The current implementation requires {@link #forProductType(Class)} and {@link #of(Product, UniqueInventoryItem)}
|
||||||
|
* to also be updated when a new child class is added.
|
||||||
|
*/
|
||||||
|
abstract class InventoryMutateForm {
|
||||||
|
private @NotEmpty String name;
|
||||||
|
private @NotNull Quantity quantity;
|
||||||
|
private @NotNull @PositiveOrZero Double retailPrice;
|
||||||
|
|
||||||
public InventoryMutateForm(@NotNull CatalogDummyType type, @NotEmpty String name,
|
public InventoryMutateForm() {
|
||||||
@NotNull Quantity quantity, @PositiveOrZero double wholesalePrice, @PositiveOrZero double retailPrice,
|
|
||||||
@PositiveOrZero Optional<Double> promotionPrice) {
|
|
||||||
this.type = type;
|
|
||||||
this.name = name;
|
|
||||||
this.quantity = quantity;
|
|
||||||
this.wholesalePrice = wholesalePrice;
|
|
||||||
this.retailPrice = retailPrice;
|
|
||||||
this.promotionPrice = promotionPrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static InventoryMutateForm empty() {
|
|
||||||
return new InventoryMutateForm(null, "", Quantity.of(0), 0, 0, Optional.empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
public CatalogDummyType getType() {
|
|
||||||
return type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
|
@ -59,15 +53,80 @@ class InventoryMutateForm {
|
||||||
return quantity;
|
return quantity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double getWholesalePrice() {
|
public Double getRetailPrice() {
|
||||||
return wholesalePrice;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double getRetailPrice() {
|
|
||||||
return retailPrice;
|
return retailPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<Double> getPromotionPrice() {
|
public void setName(String name) {
|
||||||
return promotionPrice;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setQuantity(Quantity quantity) {
|
||||||
|
this.quantity = quantity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRetailPrice(Double retailPrice) {
|
||||||
|
this.retailPrice = retailPrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an empty {@link InventoryMutateForm} for {@link Product}s of the given {@link Class}.
|
||||||
|
*
|
||||||
|
* @param <T> a child class of {@link Product}
|
||||||
|
* @param type the concrete {@link Product} the form should mutate
|
||||||
|
* @return an object of an {@link InventoryMutateForm} subclass
|
||||||
|
* @throws IllegalArgumentException if the {@literal type} is not supported
|
||||||
|
*/
|
||||||
|
public static <T extends Product> InventoryMutateForm forProductType(Class<T> type) {
|
||||||
|
// Java can’t switch over Class in JDK17 (without preview features)
|
||||||
|
// See https://openjdk.org/jeps/406 for improvement in higher versions.
|
||||||
|
if (type.equals(Consumable.class)) {
|
||||||
|
return new ConsumableMutateForm();
|
||||||
|
} else if (type.equals(Rentable.class)) {
|
||||||
|
return new RentableMutateForm();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"InventoryMutateForm::forProductType not supported for given types");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a populated {@link InventoryMutateForm} from a given {@link Product} and {@link UniqueInventoryItem}.
|
||||||
|
*
|
||||||
|
* @param product an instance of a {@link Product} subclass
|
||||||
|
* @param item an {@link UniqueInventoryItem} holding a {@link Quantity}
|
||||||
|
* @return an object of an {@link InventoryMutateForm} subclass
|
||||||
|
*/
|
||||||
|
public static InventoryMutateForm of(Product product, UniqueInventoryItem item) {
|
||||||
|
if (product instanceof Consumable consumable) {
|
||||||
|
return ConsumableMutateForm.of(consumable, item);
|
||||||
|
} else if (product instanceof Rentable rentable) {
|
||||||
|
return RentableMutateForm.of(rentable, item);
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("InventoryMutateForm::ofProductAndItem not supported for given types");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link Product} from a populated {@link InventoryMutateForm}.
|
||||||
|
*
|
||||||
|
* @return an instance of a {@link Product} subclass
|
||||||
|
*/
|
||||||
|
public abstract Product toProduct();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Modifies a given {@link Product} to match the values from the {@link InventoryMutateForm}.
|
||||||
|
*
|
||||||
|
* As the {@link Quantity} is stored inside of the {@link UniqueInventoryItem},
|
||||||
|
* it has to be updated manually.
|
||||||
|
*
|
||||||
|
* @param product the {@link Product} to be updated
|
||||||
|
*/
|
||||||
|
public void modifyProduct(Product product) {
|
||||||
|
product.setName(getName());
|
||||||
|
product.setPrice(Money.of(getRetailPrice(), EURO));
|
||||||
|
modifyProductPrimitive(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void modifyProductPrimitive(Product product);
|
||||||
}
|
}
|
||||||
|
|
70
src/main/java/catering/inventory/RentableMutateForm.java
Normal file
70
src/main/java/catering/inventory/RentableMutateForm.java
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2023 Simon Bruder
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Affero General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Affero General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
package catering.inventory;
|
||||||
|
|
||||||
|
import static org.salespointframework.core.Currencies.EURO;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.javamoney.moneta.Money;
|
||||||
|
import org.salespointframework.catalog.Product;
|
||||||
|
import org.salespointframework.inventory.UniqueInventoryItem;
|
||||||
|
|
||||||
|
import catering.catalog.Rentable;
|
||||||
|
import catering.order.OrderType;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import jakarta.validation.constraints.PositiveOrZero;
|
||||||
|
|
||||||
|
class RentableMutateForm extends InventoryMutateForm {
|
||||||
|
private @NotNull @PositiveOrZero Double wholesalePrice;
|
||||||
|
|
||||||
|
public Double getWholesalePrice() {
|
||||||
|
return wholesalePrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setWholesalePrice(Double wholesalePrice) {
|
||||||
|
this.wholesalePrice = wholesalePrice;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RentableMutateForm of(Rentable product, UniqueInventoryItem item) {
|
||||||
|
RentableMutateForm form = new RentableMutateForm();
|
||||||
|
form.setName(product.getName());
|
||||||
|
form.setQuantity(item.getQuantity());
|
||||||
|
form.setWholesalePrice(product.getWholesalePrice().getNumber().doubleValueExact());
|
||||||
|
form.setRetailPrice(product.getRetailPrice().getNumber().doubleValueExact());
|
||||||
|
return form;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Product toProduct() {
|
||||||
|
return new Rentable(
|
||||||
|
getName(),
|
||||||
|
Money.of(getRetailPrice(), EURO),
|
||||||
|
Money.of(getWholesalePrice(), EURO),
|
||||||
|
// FIXME: categories for creation of rentables are hardcoded
|
||||||
|
Set.of(OrderType.EVENT_CATERING, OrderType.MOBILE_BREAKFAST));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void modifyProductPrimitive(Product product) {
|
||||||
|
if (product instanceof Rentable rentable) {
|
||||||
|
rentable.setWholesalePrice(Money.of(getWholesalePrice(), EURO));
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("RentableMutateForm can only modify instances of Rentable");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,12 +7,9 @@
|
||||||
<div layout:fragment="content">
|
<div layout:fragment="content">
|
||||||
<h2 th:text="${'Produkt ' + (actionIsAdd ? 'anlegen' : 'bearbeiten')}"></h2>
|
<h2 th:text="${'Produkt ' + (actionIsAdd ? 'anlegen' : 'bearbeiten')}"></h2>
|
||||||
<form method="post" th:object="${form}">
|
<form method="post" th:object="${form}">
|
||||||
<div class="mb-3">
|
<div class="mb-3" th:if="${actionIsAdd}">
|
||||||
<label class="form-label" for="type">Typ</label>
|
<a th:href="@{/inventory/add?type=Consumable}" class="btn" th:classappend="${form.getClass().getSimpleName() == 'ConsumableMutateForm' ? 'btn-primary' : 'btn-secondary'}">Verbrauchsmaterial</a>
|
||||||
<div class="form-check form-check-inline" th:each="type : ${T(catering.catalog.CatalogDummyType).values()}">
|
<a th:href="@{/inventory/add?type=Rentable}" class="btn" th:classappend="${form.getClass().getSimpleName() == 'RentableMutateForm' ? 'btn-primary' : 'btn-secondary'}">Leihmaterial</a>
|
||||||
<input class="form-check-input" type="radio" th:field="*{type}" th:value="${type}" th:text="${type}" th:errorclass="is-invalid" required/>
|
|
||||||
<div th:if="${#fields.hasErrors('type')}" class="invalid-feedback">Ungültiger Typ.</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3">
|
||||||
<label class="form-label" for="name">Produktname</label>
|
<label class="form-label" for="name">Produktname</label>
|
||||||
|
@ -35,8 +32,7 @@
|
||||||
<input class="form-control" type="number" name="retailPrice" th:value="${#numbers.formatDecimal(form.retailPrice, 1, 2)}" th:errorclass="is-invalid" step="0.01" min="0" required/>
|
<input class="form-control" type="number" name="retailPrice" th:value="${#numbers.formatDecimal(form.retailPrice, 1, 2)}" th:errorclass="is-invalid" step="0.01" min="0" required/>
|
||||||
<div th:if="${#fields.hasErrors('retailPrice')}" class="invalid-feedback">Ungültiger Verkaufspreis.</div>
|
<div th:if="${#fields.hasErrors('retailPrice')}" class="invalid-feedback">Ungültiger Verkaufspreis.</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mb-3">
|
<div class="mb-3" th:if="${form.getClass().getSimpleName() == 'ConsumableMutateForm'}">
|
||||||
<!-- FIXME darf nur bei angeboten als teil von partyservice angezeigt werden -->
|
|
||||||
<label class="form-label" for="promotionPrice">Aktionspreis</label>
|
<label class="form-label" for="promotionPrice">Aktionspreis</label>
|
||||||
<input class="form-control" type="number" name="promotionPrice" th:value="${#numbers.formatDecimal(form.promotionPrice.orElse(null), 1, 2)}" th:errorclass="is-invalid" step="0.01" min="0"/>
|
<input class="form-control" type="number" name="promotionPrice" th:value="${#numbers.formatDecimal(form.promotionPrice.orElse(null), 1, 2)}" th:errorclass="is-invalid" step="0.01" min="0"/>
|
||||||
<div th:if="${#fields.hasErrors('promotionPrice')}" class="invalid-feedback">Ungültiger Aktionspreis.</div>
|
<div th:if="${#fields.hasErrors('promotionPrice')}" class="invalid-feedback">Ungültiger Aktionspreis.</div>
|
||||||
|
|
|
@ -24,17 +24,18 @@
|
||||||
<td th:text="${item.product.name}"></td>
|
<td th:text="${item.product.name}"></td>
|
||||||
<td th:text="${item.quantity}"></td>
|
<td th:text="${item.quantity}"></td>
|
||||||
<td th:text="${item.product.wholesalePrice}"></td>
|
<td th:text="${item.product.wholesalePrice}"></td>
|
||||||
<td th:if="${item.product.promotionPrice != null}"><del th:text="${item.product.price}"></del> <span th:text="${item.product.promotionPrice}"></span></td>
|
<td th:if="${item.product.getClass().getSimpleName() == 'Consumable' && item.product.promotionPrice.isPresent()}"><del th:text="${item.product.retailPrice}"></del> <span th:text="${item.product.promotionPrice.get()}"></span></td>
|
||||||
<td th:if="${item.product.promotionPrice == null}" th:text="${item.product.price}"></td>
|
<td th:if="${item.product.getClass().getSimpleName() != 'Consumable' || item.product.promotionPrice.isEmpty()}" th:text="${item.product.price}"></td>
|
||||||
<td th:text="${item.product.wholesalePrice.multiply(item.quantity.getAmount())}"></td>
|
<td th:text="${item.product.wholesalePrice.multiply(item.quantity.getAmount())}"></td>
|
||||||
<td>
|
<td>
|
||||||
<a th:href="@{/inventory/edit/{id}(id=${item.product.id})}"><button class="btn btn-warning">Bearbeiten</button></a>
|
<a th:href="@{/inventory/edit/{id}(id=${item.product.id},type=${item.product.getClass().getSimpleName()})}"><button class="btn btn-warning">Bearbeiten</button></a>
|
||||||
<a th:href="@{/inventory/delete/{id}(id=${item.product.id})}"><button class="btn btn-danger">Entfernen</button></a>
|
<a th:href="@{/inventory/delete/{id}(id=${item.product.id})}"><button class="btn btn-danger">Entfernen</button></a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<a href="/inventory/add"><button class="btn btn-primary">Artikel hinzufügen</button></a>
|
<a href="/inventory/add?type=Consumable"><button class="btn btn-primary">Verbrauchsmaterial hinzufügen</button></a>
|
||||||
|
<a href="/inventory/add?type=Rentable"><button class="btn btn-primary">Leihmaterial hinzufügen</button></a>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -19,8 +19,8 @@ package catering.inventory;
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.tuple;
|
import static org.assertj.core.api.Assertions.tuple;
|
||||||
import static org.hamcrest.CoreMatchers.containsString;
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.hamcrest.CoreMatchers.not;
|
|
||||||
import static org.hamcrest.CoreMatchers.endsWith;
|
import static org.hamcrest.CoreMatchers.endsWith;
|
||||||
|
import static org.hamcrest.CoreMatchers.not;
|
||||||
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;
|
||||||
|
@ -29,9 +29,12 @@ 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.util.Optional;
|
||||||
|
|
||||||
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;
|
||||||
|
import org.salespointframework.catalog.Product;
|
||||||
import org.salespointframework.catalog.Product.ProductIdentifier;
|
import org.salespointframework.catalog.Product.ProductIdentifier;
|
||||||
import org.salespointframework.inventory.UniqueInventory;
|
import org.salespointframework.inventory.UniqueInventory;
|
||||||
import org.salespointframework.inventory.UniqueInventoryItem;
|
import org.salespointframework.inventory.UniqueInventoryItem;
|
||||||
|
@ -44,8 +47,7 @@ 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.ResultActions;
|
import org.springframework.test.web.servlet.ResultActions;
|
||||||
|
|
||||||
import catering.catalog.CatalogDummy;
|
import catering.catalog.Consumable;
|
||||||
import catering.catalog.CatalogDummyType;
|
|
||||||
|
|
||||||
@AutoConfigureMockMvc
|
@AutoConfigureMockMvc
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
|
@ -57,13 +59,13 @@ class InventoryControllerIntegrationTests {
|
||||||
UniqueInventory<UniqueInventoryItem> inventory;
|
UniqueInventory<UniqueInventoryItem> inventory;
|
||||||
|
|
||||||
UniqueInventoryItem anyInventoryItem;
|
UniqueInventoryItem anyInventoryItem;
|
||||||
CatalogDummy anyProduct;
|
Product anyProduct;
|
||||||
ProductIdentifier anyPid;
|
ProductIdentifier anyPid;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void populateAnyInventoryItem() {
|
void populateAnyInventoryItem() {
|
||||||
anyInventoryItem = inventory.findAll().stream().findAny().get();
|
anyInventoryItem = inventory.findAll().stream().findAny().get();
|
||||||
anyProduct = (CatalogDummy) anyInventoryItem.getProduct();
|
anyProduct = anyInventoryItem.getProduct();
|
||||||
anyPid = anyProduct.getId();
|
anyPid = anyProduct.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -77,15 +79,15 @@ class InventoryControllerIntegrationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@WithMockUser(username = "admin", roles = "ADMIN")
|
@WithMockUser(username = "admin", roles = "ADMIN")
|
||||||
void adminCanAdd() throws Exception {
|
void adminCanAddConsumable() throws Exception {
|
||||||
mvc.perform(get("/inventory/add"))
|
mvc.perform(get("/inventory/add?type=Consumable"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(containsString("Produkt anlegen")));
|
.andExpect(content().string(containsString("Produkt anlegen")));
|
||||||
|
|
||||||
long itemCountBefore = inventory.findAll().stream().count();
|
long itemCountBefore = inventory.findAll().stream().count();
|
||||||
|
|
||||||
mvc.perform(post("/inventory/add")
|
mvc.perform(post("/inventory/add?type=Consumable")
|
||||||
.param("type", "CONSUMABLE")
|
.queryParam("type", Consumable.class.getSimpleName())
|
||||||
.param("name", "MOCK Schnitzel Wiener Art (vegan)")
|
.param("name", "MOCK Schnitzel Wiener Art (vegan)")
|
||||||
.param("quantity", "100")
|
.param("quantity", "100")
|
||||||
.param("wholesalePrice", "3.00")
|
.param("wholesalePrice", "3.00")
|
||||||
|
@ -97,12 +99,11 @@ class InventoryControllerIntegrationTests {
|
||||||
|
|
||||||
assertThat(itemCountAfter).isEqualTo(itemCountBefore + 1);
|
assertThat(itemCountAfter).isEqualTo(itemCountBefore + 1);
|
||||||
|
|
||||||
// TODO: this must be changed once the catalog split is done
|
assertThat(inventory.findAll().filter(ie -> ie.getProduct() instanceof Consumable).stream())
|
||||||
assertThat(inventory.findAll().stream())
|
.extracting("product.name", "quantity", "product.wholesalePrice", "product.retailPrice",
|
||||||
.extracting("product.type", "product.name", "quantity", "product.wholesalePrice", "product.price",
|
|
||||||
"product.promotionPrice")
|
"product.promotionPrice")
|
||||||
.contains(tuple(CatalogDummyType.CONSUMABLE, "MOCK Schnitzel Wiener Art (vegan)", Quantity.of(100),
|
.contains(tuple("MOCK Schnitzel Wiener Art (vegan)", Quantity.of(100),
|
||||||
Money.of(3, EURO), Money.of(7.5, EURO), Money.of(6.66, EURO)));
|
Money.of(3, EURO), Money.of(7.5, EURO), Optional.of(Money.of(6.66, EURO))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -117,54 +118,52 @@ class InventoryControllerIntegrationTests {
|
||||||
|
|
||||||
assertThat(itemCountAfter).isEqualTo(itemCountBefore - 1);
|
assertThat(itemCountAfter).isEqualTo(itemCountBefore - 1);
|
||||||
|
|
||||||
// TODO: this must be changed once the catalog split is done
|
|
||||||
assertThat(inventory.findAll().stream())
|
assertThat(inventory.findAll().stream())
|
||||||
.extracting("product.type", "product.name", "quantity")
|
.extracting("product.name", "quantity")
|
||||||
.doesNotContain(tuple(anyProduct.getType(), anyProduct.getName(), anyInventoryItem.getQuantity()));
|
.doesNotContain(tuple(anyProduct.getName(), anyInventoryItem.getQuantity()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@WithMockUser(username = "admin", roles = "ADMIN")
|
@WithMockUser(username = "admin", roles = "ADMIN")
|
||||||
void adminCanEditConsumable() throws Exception {
|
void adminCanEditConsumable() throws Exception {
|
||||||
// TODO: this must be changed once the catalog split is done
|
Consumable anyConsumable = inventory.findAll().stream()
|
||||||
CatalogDummy anyConsumable = inventory.findAll().stream()
|
|
||||||
.map(UniqueInventoryItem::getProduct)
|
.map(UniqueInventoryItem::getProduct)
|
||||||
.map(p -> (CatalogDummy) p)
|
.filter(Consumable.class::isInstance)
|
||||||
.filter(cd -> cd.getType().equals(CatalogDummyType.CONSUMABLE))
|
.map(Consumable.class::cast)
|
||||||
.findAny()
|
.findAny()
|
||||||
.get();
|
.get();
|
||||||
|
|
||||||
mvc.perform(get("/inventory/edit/" + anyConsumable.getId()))
|
mvc.perform(
|
||||||
|
get("/inventory/edit/" + anyConsumable.getId()))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(content().string(containsString("Produkt bearbeiten")));
|
.andExpect(content().string(containsString("Produkt bearbeiten")));
|
||||||
|
|
||||||
boolean hasPromotionPrice = anyConsumable.getPromotionPrice() != null;
|
|
||||||
|
|
||||||
mvc.perform(post("/inventory/edit/" + anyConsumable.getId())
|
mvc.perform(post("/inventory/edit/" + anyConsumable.getId())
|
||||||
|
.queryParam("type", Consumable.class.getSimpleName())
|
||||||
.param("type", "CONSUMABLE")
|
.param("type", "CONSUMABLE")
|
||||||
.param("name", "MOCK edited")
|
.param("name", "MOCK edited")
|
||||||
.param("quantity", "4711")
|
.param("quantity", "4711")
|
||||||
.param("wholesalePrice", "0.01")
|
.param("wholesalePrice", "0.01")
|
||||||
.param("retailPrice", "0.01")
|
.param("retailPrice", "0.03")
|
||||||
.param("promotionPrice", hasPromotionPrice ? "" : "5"))
|
.param("promotionPrice", "0.02"))
|
||||||
.andExpect(redirectedUrl("/inventory"));
|
.andExpect(redirectedUrl("/inventory"));
|
||||||
|
|
||||||
UniqueInventoryItem editedInventoryItem = inventory.findByProductIdentifier(anyConsumable.getId()).stream()
|
UniqueInventoryItem editedInventoryItem = inventory.findByProductIdentifier(anyConsumable.getId()).stream()
|
||||||
.findAny().get();
|
.findAny().get();
|
||||||
CatalogDummy editedProduct = (CatalogDummy) editedInventoryItem.getProduct();
|
Consumable editedProduct = (Consumable) editedInventoryItem.getProduct();
|
||||||
|
|
||||||
assertThat(editedInventoryItem.getQuantity()).isEqualTo(Quantity.of(4711));
|
assertThat(editedInventoryItem.getQuantity()).isEqualTo(Quantity.of(4711));
|
||||||
assertThat(editedProduct)
|
assertThat(editedProduct)
|
||||||
.extracting("type", "name", "wholesalePrice", "price", "promotionPrice")
|
.extracting("name", "wholesalePrice", "retailPrice", "promotionPrice")
|
||||||
.containsExactly(CatalogDummyType.CONSUMABLE, "MOCK edited", Money.of(0.01, EURO),
|
.containsExactly("MOCK edited", Money.of(0.01, EURO),
|
||||||
Money.of(0.01, EURO), hasPromotionPrice ? null : Money.of(0.01, EURO));
|
Money.of(0.03, EURO), Optional.of(Money.of(0.02, EURO)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@WithMockUser(username = "admin", roles = "ADMIN")
|
@WithMockUser(username = "admin", roles = "ADMIN")
|
||||||
void invalidAddReturnsNiceError() throws Exception {
|
void invalidAddReturnsNiceError() throws Exception {
|
||||||
mvc.perform(post("/inventory/add")
|
mvc.perform(post("/inventory/add")
|
||||||
.param("type", "CONSUMABLE")
|
.queryParam("type", Consumable.class.getSimpleName())
|
||||||
.param("name", "")
|
.param("name", "")
|
||||||
.param("quantity", "10")
|
.param("quantity", "10")
|
||||||
.param("wholesalePrice", "1.00")
|
.param("wholesalePrice", "1.00")
|
||||||
|
@ -176,7 +175,7 @@ class InventoryControllerIntegrationTests {
|
||||||
@WithMockUser(username = "admin", roles = "ADMIN")
|
@WithMockUser(username = "admin", roles = "ADMIN")
|
||||||
void missingRetailPriceIsNoError() throws Exception {
|
void missingRetailPriceIsNoError() throws Exception {
|
||||||
mvc.perform(post("/inventory/add")
|
mvc.perform(post("/inventory/add")
|
||||||
.param("type", "CONSUMABLE")
|
.queryParam("type", Consumable.class.getSimpleName())
|
||||||
.param("name", "MOCK")
|
.param("name", "MOCK")
|
||||||
.param("quantity", "10")
|
.param("quantity", "10")
|
||||||
.param("wholesalePrice", "1.00")
|
.param("wholesalePrice", "1.00")
|
||||||
|
@ -188,7 +187,7 @@ class InventoryControllerIntegrationTests {
|
||||||
@WithMockUser(username = "admin", roles = "ADMIN")
|
@WithMockUser(username = "admin", roles = "ADMIN")
|
||||||
void invalidEditReturnsNiceError() throws Exception {
|
void invalidEditReturnsNiceError() throws Exception {
|
||||||
mvc.perform(post("/inventory/edit/" + anyPid)
|
mvc.perform(post("/inventory/edit/" + anyPid)
|
||||||
.param("type", "CONSUMABLE")
|
.queryParam("type", Consumable.class.getSimpleName())
|
||||||
.param("name", "")
|
.param("name", "")
|
||||||
.param("quantity", "10")
|
.param("quantity", "10")
|
||||||
.param("wholesalePrice", "1.00")
|
.param("wholesalePrice", "1.00")
|
||||||
|
@ -220,22 +219,25 @@ class InventoryControllerIntegrationTests {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void disallowUnauthorizedEditPage() throws Exception {
|
void disallowUnauthorizedEditPage() throws Exception {
|
||||||
assertRedirectsToLogin(mvc.perform(get("/inventory/edit/" + anyPid)));
|
assertRedirectsToLogin(
|
||||||
|
mvc.perform(get("/inventory/edit/" + anyPid).queryParam("type", Consumable.class.getSimpleName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void disallowUnauthorizedEdit() throws Exception {
|
void disallowUnauthorizedEdit() throws Exception {
|
||||||
assertRedirectsToLogin(mvc.perform(post("/inventory/edit/" + anyPid)));
|
assertRedirectsToLogin(
|
||||||
|
mvc.perform(post("/inventory/edit/" + anyPid).queryParam("type", Consumable.class.getSimpleName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void disallowUnauthorizedAddPage() throws Exception {
|
void disallowUnauthorizedAddPage() throws Exception {
|
||||||
assertRedirectsToLogin(mvc.perform(get("/inventory/add")));
|
assertRedirectsToLogin(mvc.perform(get("/inventory/add").queryParam("type", Consumable.class.getSimpleName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void disallowUnauthorizedAdd() throws Exception {
|
void disallowUnauthorizedAdd() throws Exception {
|
||||||
assertRedirectsToLogin(mvc.perform(post("/inventory/add")));
|
assertRedirectsToLogin(
|
||||||
|
mvc.perform(post("/inventory/add").queryParam("type", Consumable.class.getSimpleName())));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
Loading…
Reference in a new issue