// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-FileCopyrightText: 2023 swt23w23 package catering.inventory; import static org.salespointframework.core.Currencies.EURO; import java.util.Set; import java.util.function.Predicate; import java.util.stream.Collectors; import java.util.stream.Stream; import org.javamoney.moneta.Money; import org.salespointframework.catalog.Product; import org.salespointframework.inventory.UniqueInventoryItem; import org.salespointframework.quantity.Quantity; import org.springframework.data.util.Streamable; import catering.catalog.Consumable; import catering.catalog.Rentable; import catering.order.OrderType; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.PositiveOrZero; // NonNegative in enterprise java /** * Abstract class for handling inventory mutations. * * It has children for every product type. * * 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; private @NotNull Set orderTypes; public InventoryMutateForm() { } public String getName() { return name; } public Quantity getQuantity() { return quantity; } public Double getRetailPrice() { return retailPrice; } public void setName(String name) { this.name = name; } public Set getOrderTypes() { return orderTypes; } public void setQuantity(Quantity quantity) { this.quantity = quantity; } public void setRetailPrice(Double retailPrice) { this.retailPrice = retailPrice; } public void setOrderTypes(Set orderTypes) { this.orderTypes = orderTypes; } /** * Creates an empty {@link InventoryMutateForm} for {@link Product}s of the given {@link Class}. * * @param 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 InventoryMutateForm forProductType(Class 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)); // first, remove all categories that are *not* selected Stream.of(OrderType.class.getEnumConstants()) .filter(Predicate.not(getOrderTypes()::contains)) .map(OrderType::toString) .forEach(product::removeCategory); // then, add all categories that *are* selected getOrderTypes().stream() .map(OrderType::toString) .forEach(product::addCategory); modifyProductPrimitive(product); } protected abstract void modifyProductPrimitive(Product product); /** * Returns a {@link Set} of {@link OrderType}s from the categories of a * {@link Product} * * @param categories a {@link Streamable} of category {@link String}s * @return a {@link Set} of all {@link OrderType}s attached to the product */ public static Set orderTypesFromCategories(Streamable categories) { return categories.stream() .filter(Stream.of(OrderType.class.getEnumConstants()) .map(OrderType::toString) .collect(Collectors.toSet())::contains) .map(OrderType::valueOf).collect(Collectors.toSet()); } }