diff --git a/src/main/asciidoc/models/design/catalog.puml b/src/main/asciidoc/models/design/catalog.puml index caea3e8..a24affe 100644 --- a/src/main/asciidoc/models/design/catalog.puml +++ b/src/main/asciidoc/models/design/catalog.puml @@ -1,5 +1,5 @@ ' SPDX-License-Identifier: AGPL-3.0-or-later -' SPDX-FileCopyrightText: 2023 swt23w23 +' SPDX-FileCopyrightText: 2023-2024 swt23w23 @startuml skinparam linetype ortho skinparam groupInheritance 2 @@ -57,7 +57,7 @@ package catering.catalog { class Consumable { - promotionPrice : MonetaryAmount - wholesalePrice : MonetaryAmount - + Consumable(name : String, retailPrice : MonetaryAmount, wholesalePrice : MonetaryAmount, promotionPrice : Optional, Set categories) : Consumable + + Consumable(name : String, retailPrice : MonetaryAmount, wholesalePrice : MonetaryAmount, promotionPrice : Optional, Set categories, metric : Metric) : Consumable + getPrice() : MonetaryAmount + getRetailPrice() : MonetaryAmount + setRetailPrice(price : MonetaryAmount) : void @@ -74,7 +74,7 @@ package catering.catalog { class Rentable { - wholesalePrice : MonetaryAmount - + Rentable(name : String, pricePerHour : MonetaryAmount, wholesalePrice : MonetaryAmount, Set categories) : Rentable + + Rentable(name : String, pricePerHour : MonetaryAmount, wholesalePrice : MonetaryAmount, Set categories, metric : Metric) : Rentable + getWholesalePrice() : MonetaryAmount + setWholesalePrice(price : MonetaryAmount) } diff --git a/src/main/java/catering/catalog/CatalogDataInitializer.java b/src/main/java/catering/catalog/CatalogDataInitializer.java index 8f36b20..b60d698 100644 --- a/src/main/java/catering/catalog/CatalogDataInitializer.java +++ b/src/main/java/catering/catalog/CatalogDataInitializer.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -// SPDX-FileCopyrightText: 2023 swt23w23 +// SPDX-FileCopyrightText: 2023-2024 swt23w23 package catering.catalog; import static org.salespointframework.core.Currencies.EURO; @@ -9,6 +9,7 @@ import java.util.Optional; import org.javamoney.moneta.Money; import org.salespointframework.core.DataInitializer; +import org.salespointframework.quantity.Metric; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import org.springframework.util.Assert; @@ -32,21 +33,32 @@ class CatalogDataInitializer implements DataInitializer { return; } + // !!! These need to be kept in sync with CatalogUnitTests cateringCatalog.save(new Consumable( "Brötchen Vollkorn", Money.of(1, EURO), Money.of(0.5, EURO), Optional.of(Money.of(0.75, EURO)), - Set.of(OrderType.EVENT_CATERING, OrderType.MOBILE_BREAKFAST))); + Set.of(OrderType.EVENT_CATERING, OrderType.MOBILE_BREAKFAST), + Metric.UNIT)); + cateringCatalog.save(new Consumable( + "Tafelwasser", + Money.of(5, EURO), + Money.of(0.01, EURO), + Optional.empty(), + Set.of(OrderType.PARTY_SERVICE, OrderType.EVENT_CATERING), + Metric.LITER)); cateringCatalog.save(new Rentable( "Kerze Rot", Money.of(2, EURO), Money.of(1.5, EURO), - Set.of(OrderType.EVENT_CATERING, OrderType.MOBILE_BREAKFAST))); + Set.of(OrderType.EVENT_CATERING, OrderType.MOBILE_BREAKFAST), + Metric.UNIT)); cateringCatalog.save(new Rentable( "Brotschneidemaschine Power X 3000", Money.of(25, EURO), Money.of(10000, EURO), - Set.of(OrderType.EVENT_CATERING, OrderType.MOBILE_BREAKFAST))); + Set.of(OrderType.EVENT_CATERING, OrderType.MOBILE_BREAKFAST), + Metric.UNIT)); } } diff --git a/src/main/java/catering/catalog/Consumable.java b/src/main/java/catering/catalog/Consumable.java index 15c3baa..3453154 100644 --- a/src/main/java/catering/catalog/Consumable.java +++ b/src/main/java/catering/catalog/Consumable.java @@ -9,6 +9,7 @@ import java.util.Set; import javax.money.MonetaryAmount; import org.salespointframework.catalog.Product; +import org.salespointframework.quantity.Metric; import org.springframework.lang.NonNull; import org.springframework.util.Assert; @@ -26,8 +27,8 @@ public class Consumable extends Product { } public Consumable(String name, MonetaryAmount price, MonetaryAmount wholesalePrice, - Optional promotionPrice, Set categories) { - super(name, price); + Optional promotionPrice, Set categories, Metric metric) { + super(name, price, metric); Assert.notNull(wholesalePrice, "wholesalePrice must not be null!"); Assert.notNull(promotionPrice, "promotionPrice must not be null!"); this.wholesalePrice = wholesalePrice; diff --git a/src/main/java/catering/catalog/Rentable.java b/src/main/java/catering/catalog/Rentable.java index 058b4aa..a80b45b 100644 --- a/src/main/java/catering/catalog/Rentable.java +++ b/src/main/java/catering/catalog/Rentable.java @@ -8,6 +8,7 @@ import java.util.Set; import javax.money.MonetaryAmount; import org.salespointframework.catalog.Product; +import org.salespointframework.quantity.Metric; import org.springframework.lang.NonNull; import org.springframework.util.Assert; @@ -25,8 +26,8 @@ public class Rentable extends Product { } public Rentable(String name, MonetaryAmount pricePerHour, MonetaryAmount wholesalePrice, - Set categories) { - super(name, pricePerHour); + Set categories, Metric metric) { + super(name, pricePerHour, metric); this.wholesalePrice = wholesalePrice; Assert.notNull(pricePerHour, "pricePerHour must not be null!"); Assert.notNull(wholesalePrice, "wholesalePrice must not be null!"); diff --git a/src/main/java/catering/inventory/ConsumableMutateForm.java b/src/main/java/catering/inventory/ConsumableMutateForm.java index df8e30c..c41893a 100644 --- a/src/main/java/catering/inventory/ConsumableMutateForm.java +++ b/src/main/java/catering/inventory/ConsumableMutateForm.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -// SPDX-FileCopyrightText: 2023 swt23w23 +// SPDX-FileCopyrightText: 2023-2024 swt23w23 package catering.inventory; import static org.salespointframework.core.Currencies.EURO; @@ -44,13 +44,15 @@ class ConsumableMutateForm extends InventoryMutateForm { Money.of(getRetailPrice(), EURO), Money.of(getWholesalePrice(), EURO), getPromotionPrice().map(price -> Money.of(price, EURO)), - getOrderTypes()); + getOrderTypes(), + getMetric()); } public static ConsumableMutateForm of(Consumable product, UniqueInventoryItem item) { ConsumableMutateForm form = new ConsumableMutateForm(); form.setName(product.getName()); - form.setQuantity(item.getQuantity()); + form.setQuantity(item.getQuantity().getAmount().longValueExact()); + form.setMetric(product.createQuantity(0).getMetric()); // hack form.setOrderTypes(orderTypesFromCategories(product.getCategories())); form.setWholesalePrice(product.getWholesalePrice().getNumber().doubleValueExact()); form.setRetailPrice(product.getRetailPrice().getNumber().doubleValueExact()); diff --git a/src/main/java/catering/inventory/InventoryController.java b/src/main/java/catering/inventory/InventoryController.java index b3df29c..06bee3b 100644 --- a/src/main/java/catering/inventory/InventoryController.java +++ b/src/main/java/catering/inventory/InventoryController.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -// SPDX-FileCopyrightText: 2023 swt23w23 +// SPDX-FileCopyrightText: 2023-2024 swt23w23 package catering.inventory; import org.salespointframework.catalog.Product; @@ -163,7 +163,7 @@ class InventoryController { UniqueInventoryItem item = inventory.findByProduct(product).get(); // no setQuantity in enterprise java // (though returing a modified object is actually nice) - inventory.save(item.increaseQuantity(form.getQuantity().subtract(item.getQuantity()))); + inventory.save(item.increaseQuantity(product.createQuantity(form.getQuantity()).subtract(item.getQuantity()))); return "redirect:/inventory"; } @@ -246,7 +246,9 @@ class InventoryController { if (result.hasErrors()) { return add(model, form); } - inventory.save(new UniqueInventoryItem(cateringCatalog.save(form.toProduct()), form.getQuantity())); + Product product = form.toProduct(); + inventory.save( + new UniqueInventoryItem(cateringCatalog.save(product), product.createQuantity(form.getQuantity()))); return "redirect:/inventory"; } diff --git a/src/main/java/catering/inventory/InventoryInizializer.java b/src/main/java/catering/inventory/InventoryInizializer.java index 00483b3..0fbaf82 100644 --- a/src/main/java/catering/inventory/InventoryInizializer.java +++ b/src/main/java/catering/inventory/InventoryInizializer.java @@ -1,11 +1,10 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -// SPDX-FileCopyrightText: 2023 swt23w23 +// SPDX-FileCopyrightText: 2023-2024 swt23w23 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; @@ -28,7 +27,7 @@ class InventoryInitializer implements DataInitializer { public void initialize() { cateringCatalog.findAll().forEach(product -> { if (inventory.findByProduct(product).isEmpty()) { - inventory.save(new UniqueInventoryItem(product, Quantity.of(10))); + inventory.save(new UniqueInventoryItem(product, product.createQuantity(10))); } }); } diff --git a/src/main/java/catering/inventory/InventoryMutateForm.java b/src/main/java/catering/inventory/InventoryMutateForm.java index 9afce43..5d6d724 100644 --- a/src/main/java/catering/inventory/InventoryMutateForm.java +++ b/src/main/java/catering/inventory/InventoryMutateForm.java @@ -1,9 +1,11 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -// SPDX-FileCopyrightText: 2023 swt23w23 +// SPDX-FileCopyrightText: 2023-2024 swt23w23 package catering.inventory; import static org.salespointframework.core.Currencies.EURO; +import java.util.Collection; +import java.util.List; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -12,6 +14,7 @@ import java.util.stream.Stream; import org.javamoney.moneta.Money; import org.salespointframework.catalog.Product; import org.salespointframework.inventory.UniqueInventoryItem; +import org.salespointframework.quantity.Metric; import org.salespointframework.quantity.Quantity; import org.springframework.data.util.Streamable; @@ -32,9 +35,10 @@ import jakarta.validation.constraints.PositiveOrZero; // NonNegative in enterpri */ abstract class InventoryMutateForm { private @NotEmpty String name; - private @NotNull Quantity quantity; + private @NotNull @PositiveOrZero Long quantity; private @NotNull @PositiveOrZero Double retailPrice; private @NotNull Set orderTypes; + private @NotNull Metric metric = Metric.UNIT; public InventoryMutateForm() { } @@ -43,7 +47,7 @@ abstract class InventoryMutateForm { return name; } - public Quantity getQuantity() { + public Long getQuantity() { return quantity; } @@ -51,6 +55,10 @@ abstract class InventoryMutateForm { return retailPrice; } + public Metric getMetric() { + return metric; + } + public void setName(String name) { this.name = name; } @@ -59,7 +67,7 @@ abstract class InventoryMutateForm { return orderTypes; } - public void setQuantity(Quantity quantity) { + public void setQuantity(Long quantity) { this.quantity = quantity; } @@ -71,6 +79,10 @@ abstract class InventoryMutateForm { this.orderTypes = orderTypes; } + public void setMetric(Metric metric) { + this.metric = metric; + } + /** * Creates an empty {@link InventoryMutateForm} for {@link Product}s of the given {@link Class}. * @@ -158,4 +170,8 @@ abstract class InventoryMutateForm { .collect(Collectors.toSet())::contains) .map(OrderType::valueOf).collect(Collectors.toSet()); } + + public Collection supportedMetrics() { + return List.of(Metric.UNIT, Metric.LITER, Metric.KILOGRAM); + } } diff --git a/src/main/java/catering/inventory/RentableMutateForm.java b/src/main/java/catering/inventory/RentableMutateForm.java index 41d5145..43884fc 100644 --- a/src/main/java/catering/inventory/RentableMutateForm.java +++ b/src/main/java/catering/inventory/RentableMutateForm.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -// SPDX-FileCopyrightText: 2023 swt23w23 +// SPDX-FileCopyrightText: 2023-2024 swt23w23 package catering.inventory; import static org.salespointframework.core.Currencies.EURO; @@ -26,7 +26,8 @@ class RentableMutateForm extends InventoryMutateForm { public static RentableMutateForm of(Rentable product, UniqueInventoryItem item) { RentableMutateForm form = new RentableMutateForm(); form.setName(product.getName()); - form.setQuantity(item.getQuantity()); + form.setQuantity(item.getQuantity().getAmount().longValueExact()); + form.setMetric(product.createQuantity(0).getMetric()); // hack form.setOrderTypes(orderTypesFromCategories(product.getCategories())); form.setWholesalePrice(product.getWholesalePrice().getNumber().doubleValueExact()); form.setRetailPrice(product.getRetailPrice().getNumber().doubleValueExact()); @@ -39,7 +40,8 @@ class RentableMutateForm extends InventoryMutateForm { getName(), Money.of(getRetailPrice(), EURO), Money.of(getWholesalePrice(), EURO), - getOrderTypes()); + getOrderTypes(), + getMetric()); } @Override diff --git a/src/main/java/catering/order/OrderController.java b/src/main/java/catering/order/OrderController.java index b854d93..ea27407 100644 --- a/src/main/java/catering/order/OrderController.java +++ b/src/main/java/catering/order/OrderController.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -// SPDX-FileCopyrightText: 2023 swt23w23 +// SPDX-FileCopyrightText: 2023-2024 swt23w23 package catering.order; import catering.catalog.CateringCatalog; @@ -178,7 +178,7 @@ public class OrderController { return "redirect:/event"; } - Quantity amount = Quantity.of(number > 0 ? number : 1); + Quantity amount = product.createQuantity(number > 0 ? number : 1); Quantity cartQuantity = cart.getQuantity(product); Quantity available; diff --git a/src/main/resources/templates/inventory-mutate.html b/src/main/resources/templates/inventory-mutate.html index 21a32d5..bcd1fbe 100644 --- a/src/main/resources/templates/inventory-mutate.html +++ b/src/main/resources/templates/inventory-mutate.html @@ -1,6 +1,6 @@ SPDX-License-Identifier: AGPL-3.0-or-later -SPDX-FileCopyrightText: 2023 swt23w23 +SPDX-FileCopyrightText: 2023-2024 swt23w23
- -
Ungültige Menge.
+
+ + +
Ungültige Menge.
+
+
+ + +
Ungültige Einheit.
+
+ +
diff --git a/src/test/java/catering/catalog/CatalogUnitTests.java b/src/test/java/catering/catalog/CatalogUnitTests.java index 8df9a14..1d942b6 100644 --- a/src/test/java/catering/catalog/CatalogUnitTests.java +++ b/src/test/java/catering/catalog/CatalogUnitTests.java @@ -9,6 +9,7 @@ import static org.assertj.core.api.Assertions.*; import static org.salespointframework.core.Currencies.EURO; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.salespointframework.quantity.Metric; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.annotation.DirtiesContext; @@ -23,20 +24,21 @@ class CatalogUnitTests { @Test void findByCategories() { + // !!! These need to be kept in sync with CatalogDataIntializer assertThat(cateringCatalog.findRentablesByCategoriesContains(OrderType.EVENT_CATERING.toString())).hasSize(2); assertThat(cateringCatalog.findRentablesByCategoriesContains(OrderType.MOBILE_BREAKFAST.toString())).hasSize(2); assertThat(cateringCatalog.findRentablesByCategoriesContains(OrderType.RENT_A_COOK.toString())).hasSize(0); assertThat(cateringCatalog.findRentablesByCategoriesContains(OrderType.PARTY_SERVICE.toString())).hasSize(0); - assertThat(cateringCatalog.findConsumablesByCategoriesContains(OrderType.EVENT_CATERING.toString())).hasSize(1); + assertThat(cateringCatalog.findConsumablesByCategoriesContains(OrderType.EVENT_CATERING.toString())).hasSize(2); assertThat(cateringCatalog.findConsumablesByCategoriesContains(OrderType.MOBILE_BREAKFAST.toString())).hasSize(1); assertThat(cateringCatalog.findConsumablesByCategoriesContains(OrderType.RENT_A_COOK.toString())).hasSize(0); - assertThat(cateringCatalog.findConsumablesByCategoriesContains(OrderType.PARTY_SERVICE.toString())).hasSize(0); + assertThat(cateringCatalog.findConsumablesByCategoriesContains(OrderType.PARTY_SERVICE.toString())).hasSize(1); } @Test void findConsumables() { - assertThat(cateringCatalog.findConsumables()).hasSize(1); + assertThat(cateringCatalog.findConsumables()).hasSize(2); } @Test @@ -56,7 +58,8 @@ class CatalogUnitTests { Money.of(1.5, EURO), Money.of(0.7, EURO), Optional.of(Money.of(0.90, EURO)), - Set.of(OrderType.MOBILE_BREAKFAST))); + Set.of(OrderType.MOBILE_BREAKFAST), + Metric.UNIT)); assertThat(cateringCatalog.findAll().stream().count()).isEqualTo(1 + countAllBefore); assertThat(cateringCatalog.findConsumables().stream().count()).isEqualTo(1 + countConsumablesBefore); assertThat(cateringCatalog.findConsumables()).contains(addedConsumable); diff --git a/src/test/java/catering/inventory/InventoryControllerIntegrationTests.java b/src/test/java/catering/inventory/InventoryControllerIntegrationTests.java index 11dc811..5badb9c 100644 --- a/src/test/java/catering/inventory/InventoryControllerIntegrationTests.java +++ b/src/test/java/catering/inventory/InventoryControllerIntegrationTests.java @@ -1,5 +1,5 @@ // SPDX-License-Identifier: AGPL-3.0-or-later -// SPDX-FileCopyrightText: 2023 swt23w23 +// SPDX-FileCopyrightText: 2023-2024 swt23w23 package catering.inventory; import static catering.inventory.InventoryControllerIntegrationTests.PermissionResult.FORBIDDEN; @@ -31,6 +31,7 @@ import org.salespointframework.catalog.Product; import org.salespointframework.catalog.Product.ProductIdentifier; import org.salespointframework.inventory.UniqueInventory; import org.salespointframework.inventory.UniqueInventoryItem; +import org.salespointframework.quantity.Metric; import org.salespointframework.quantity.Quantity; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; @@ -82,11 +83,12 @@ class InventoryControllerIntegrationTests { anyPid = anyProduct.getId(); anyConsumableItem = inventory.save(new UniqueInventoryItem( catalog.save(new Consumable("Any Consumable", Money.of(1, EURO), Money.of(0.5, EURO), - Optional.of(Money.of(0.75, EURO)), Set.of(OrderType.EVENT_CATERING, OrderType.PARTY_SERVICE))), + Optional.of(Money.of(0.75, EURO)), Set.of(OrderType.EVENT_CATERING, OrderType.PARTY_SERVICE), + Metric.UNIT)), Quantity.of(1))); anyRentableItem = inventory.save(new UniqueInventoryItem( catalog.save(new Rentable("Any Rentable", Money.of(1, EURO), Money.of(0.5, EURO), - Set.of(OrderType.EVENT_CATERING, OrderType.PARTY_SERVICE))), + Set.of(OrderType.EVENT_CATERING, OrderType.PARTY_SERVICE), Metric.UNIT)), Quantity.of(1))); } @@ -131,7 +133,8 @@ class InventoryControllerIntegrationTests { .isEqualTo( new UniqueInventoryItem(new Consumable("MOCK Schnitzel Wiener Art (vegan)", Money.of(7.5, EURO), Money.of(3, EURO), Optional.of(Money.of(6.66, EURO)), - Set.of(OrderType.MOBILE_BREAKFAST, OrderType.PARTY_SERVICE)), Quantity.of(100))); + Set.of(OrderType.MOBILE_BREAKFAST, OrderType.PARTY_SERVICE), Metric.UNIT), + Quantity.of(100))); } @Test