diff --git a/src/main/java/catering/inventory/InventoryController.java b/src/main/java/catering/inventory/InventoryController.java
new file mode 100644
index 0000000..d87c5aa
--- /dev/null
+++ b/src/main/java/catering/inventory/InventoryController.java
@@ -0,0 +1,107 @@
+/*
+ * 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 .
+ */
+package catering.inventory;
+
+import org.salespointframework.catalog.Product;
+import org.salespointframework.inventory.UniqueInventory;
+import org.salespointframework.inventory.UniqueInventoryItem;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.util.Assert;
+import org.springframework.validation.Errors;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.PostMapping;
+
+import catering.catalog.CatalogDummy;
+import catering.catalog.CateringCatalog;
+import jakarta.validation.Valid;
+
+@Controller
+class InventoryController {
+ private final UniqueInventory inventory;
+ private final CateringCatalog cateringCatalog;
+
+ InventoryController(UniqueInventory inventory, CateringCatalog cateringCatalog) {
+ Assert.notNull(inventory, "Inventory must not be null!");
+ Assert.notNull(inventory, "CateringCatalog must not be null!");
+ this.inventory = inventory;
+ this.cateringCatalog = cateringCatalog;
+ }
+
+ @GetMapping("/inventory")
+ String list(Model model) {
+ model.addAttribute("inventory", inventory.findAll());
+
+ return "inventory";
+ }
+
+ @GetMapping("/inventory/edit/{pid}")
+ String edit(Model model, @PathVariable Product pid) {
+ model.addAttribute("product", pid);
+ model.addAttribute("item", inventory.findByProduct(pid).get());
+
+ return "inventory-mutate";
+ }
+
+ @PostMapping("/inventory/edit/{pid}")
+ String edit(@Valid InventoryMutateForm form, Errors result, @PathVariable Product pid) {
+ if (result.hasErrors()) {
+ return "redirect:/inventory/edit/" + pid.getId();
+ }
+ CatalogDummy product = (CatalogDummy) pid;
+ UniqueInventoryItem item = inventory.findByProduct(pid).get();
+
+ product.setName(form.getName());
+ product.setType(form.getType());
+ product.setPrice(form.getRetailPrice());
+ product.setWholesalePrice(form.getWholesalePrice());
+ product.setPromotionPrice(form.getPromotionPrice().orElse(null));
+
+ product = cateringCatalog.save(product);
+
+ // no setQuantity in enterprise java
+ // (though returing a modified object is actually nice)
+ inventory.save(item.increaseQuantity(form.getQuantity().subtract(item.getQuantity())));
+ return "redirect:/inventory";
+ }
+
+ @GetMapping("/inventory/add")
+ String add() {
+ return "inventory-mutate";
+ }
+
+ @PostMapping("/inventory/add")
+ String add(@Valid InventoryMutateForm form, Errors result) {
+ if (result.hasErrors()) {
+ return "inventory-mutate";
+ }
+ inventory.save(new UniqueInventoryItem(
+ cateringCatalog.save(new CatalogDummy(form.getName(), form.getType(), form.getRetailPrice(),
+ form.getWholesalePrice(), form.getPromotionPrice().orElse(null))),
+ form.getQuantity()));
+ return "redirect:/inventory";
+ }
+
+ @GetMapping("/inventory/delete/{pid}")
+ String delete(@PathVariable Product pid) {
+ UniqueInventoryItem item = inventory.findByProduct(pid).get();
+ inventory.delete(item);
+ cateringCatalog.delete((CatalogDummy) pid);
+ return "redirect:/inventory";
+ }
+}
diff --git a/src/main/java/catering/inventory/InventoryInizializer.java b/src/main/java/catering/inventory/InventoryInizializer.java
new file mode 100644
index 0000000..217c957
--- /dev/null
+++ b/src/main/java/catering/inventory/InventoryInizializer.java
@@ -0,0 +1,49 @@
+/*
+ * 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 .
+ */
+package catering.inventory;
+
+import org.salespointframework.core.DataInitializer;
+import org.salespointframework.inventory.UniqueInventory;
+import org.salespointframework.inventory.UniqueInventoryItem;
+import org.salespointframework.quantity.Quantity;
+import org.springframework.stereotype.Component;
+import org.springframework.util.Assert;
+
+import catering.catalog.CateringCatalog;
+
+@Component
+class InventoryInitializer implements DataInitializer {
+ private final UniqueInventory inventory;
+ private final CateringCatalog cateringCatalog;
+
+ InventoryInitializer(UniqueInventory inventory, CateringCatalog cateringCatalog) {
+ Assert.notNull(inventory, "Inventory must not be null!");
+ Assert.notNull(cateringCatalog, "CateringCatalog must not be null!");
+
+ this.inventory = inventory;
+ this.cateringCatalog = cateringCatalog;
+ }
+
+ @Override
+ public void initialize() {
+ cateringCatalog.findAll().forEach(product -> {
+ if (inventory.findByProduct(product).isEmpty()) {
+ inventory.save(new UniqueInventoryItem(product, Quantity.of(10)));
+ }
+ });
+ }
+}
diff --git a/src/main/java/catering/inventory/InventoryMutateForm.java b/src/main/java/catering/inventory/InventoryMutateForm.java
new file mode 100644
index 0000000..dcdf99f
--- /dev/null
+++ b/src/main/java/catering/inventory/InventoryMutateForm.java
@@ -0,0 +1,74 @@
+/*
+ * 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 .
+ */
+package catering.inventory;
+
+import static org.salespointframework.core.Currencies.EURO;
+
+import java.util.Optional;
+
+import javax.money.MonetaryAmount;
+
+import org.javamoney.moneta.Money;
+import org.salespointframework.quantity.Quantity;
+
+import catering.catalog.CatalogDummyType;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.PositiveOrZero; // NonNegative in enterprise java
+
+class InventoryMutateForm {
+ private final @NotNull CatalogDummyType type;
+ private final @NotEmpty String name;
+ private final @NotNull Quantity quantity;
+ private final @NotNull MonetaryAmount wholesalePrice, retailPrice;
+ private final @NotNull Optional promotionPrice;
+
+ public InventoryMutateForm(@NotNull CatalogDummyType type, @NotEmpty String name,
+ @NotNull Quantity quantity, @PositiveOrZero double wholesalePrice, @PositiveOrZero double retailPrice,
+ @PositiveOrZero Optional promotionPrice) {
+ this.type = type;
+ this.name = name;
+ this.quantity = quantity;
+ this.wholesalePrice = Money.of(wholesalePrice, EURO);
+ this.retailPrice = Money.of(retailPrice, EURO);
+ this.promotionPrice = promotionPrice.map(price -> (MonetaryAmount) Money.of(price, EURO));
+ }
+
+ public CatalogDummyType getType() {
+ return type;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Quantity getQuantity() {
+ return quantity;
+ }
+
+ public MonetaryAmount getWholesalePrice() {
+ return wholesalePrice;
+ }
+
+ public MonetaryAmount getRetailPrice() {
+ return retailPrice;
+ }
+
+ public Optional getPromotionPrice() {
+ return promotionPrice;
+ }
+}
diff --git a/src/main/resources/templates/inventory-mutate.html b/src/main/resources/templates/inventory-mutate.html
new file mode 100644
index 0000000..354f801
--- /dev/null
+++ b/src/main/resources/templates/inventory-mutate.html
@@ -0,0 +1,42 @@
+
+
+
+
+ Lagerverwaltung
+
+
+
+
+