2023-12-11 17:59:14 +01:00
|
|
|
|
// SPDX-License-Identifier: AGPL-3.0-or-later
|
|
|
|
|
// SPDX-FileCopyrightText: 2023 swt23w23
|
2023-11-05 16:11:36 +01:00
|
|
|
|
package catering.inventory;
|
|
|
|
|
|
2023-11-29 17:13:07 +01:00
|
|
|
|
import static org.salespointframework.core.Currencies.EURO;
|
2023-11-05 16:11:36 +01:00
|
|
|
|
|
2023-12-07 18:35:52 +01:00
|
|
|
|
import java.util.Set;
|
|
|
|
|
import java.util.function.Predicate;
|
|
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
import java.util.stream.Stream;
|
|
|
|
|
|
2023-11-29 17:13:07 +01:00
|
|
|
|
import org.javamoney.moneta.Money;
|
|
|
|
|
import org.salespointframework.catalog.Product;
|
|
|
|
|
import org.salespointframework.inventory.UniqueInventoryItem;
|
2023-11-05 16:11:36 +01:00
|
|
|
|
import org.salespointframework.quantity.Quantity;
|
2023-12-07 18:35:52 +01:00
|
|
|
|
import org.springframework.data.util.Streamable;
|
2023-11-05 16:11:36 +01:00
|
|
|
|
|
2023-11-29 17:13:07 +01:00
|
|
|
|
import catering.catalog.Consumable;
|
|
|
|
|
import catering.catalog.Rentable;
|
2023-12-07 18:35:52 +01:00
|
|
|
|
import catering.order.OrderType;
|
2023-11-05 16:11:36 +01:00
|
|
|
|
import jakarta.validation.constraints.NotEmpty;
|
|
|
|
|
import jakarta.validation.constraints.NotNull;
|
|
|
|
|
import jakarta.validation.constraints.PositiveOrZero; // NonNegative in enterprise java
|
|
|
|
|
|
2023-11-29 17:13:07 +01:00
|
|
|
|
/**
|
|
|
|
|
* 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;
|
2023-12-07 18:35:52 +01:00
|
|
|
|
private @NotNull Set<OrderType> orderTypes;
|
2023-11-05 16:11:36 +01:00
|
|
|
|
|
2023-11-29 17:13:07 +01:00
|
|
|
|
public InventoryMutateForm() {
|
2023-11-05 16:11:36 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String getName() {
|
|
|
|
|
return name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Quantity getQuantity() {
|
|
|
|
|
return quantity;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-29 17:13:07 +01:00
|
|
|
|
public Double getRetailPrice() {
|
|
|
|
|
return retailPrice;
|
2023-11-05 16:11:36 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-11-29 17:13:07 +01:00
|
|
|
|
public void setName(String name) {
|
|
|
|
|
this.name = name;
|
2023-11-05 16:11:36 +01:00
|
|
|
|
}
|
|
|
|
|
|
2023-12-07 18:35:52 +01:00
|
|
|
|
public Set<OrderType> getOrderTypes() {
|
|
|
|
|
return orderTypes;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-29 17:13:07 +01:00
|
|
|
|
public void setQuantity(Quantity quantity) {
|
|
|
|
|
this.quantity = quantity;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void setRetailPrice(Double retailPrice) {
|
|
|
|
|
this.retailPrice = retailPrice;
|
|
|
|
|
}
|
|
|
|
|
|
2023-12-07 18:35:52 +01:00
|
|
|
|
public void setOrderTypes(Set<OrderType> orderTypes) {
|
|
|
|
|
this.orderTypes = orderTypes;
|
|
|
|
|
}
|
|
|
|
|
|
2023-11-29 17:13:07 +01:00
|
|
|
|
/**
|
|
|
|
|
* 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");
|
|
|
|
|
}
|
2023-11-05 16:11:36 +01:00
|
|
|
|
}
|
2023-11-29 17:13:07 +01:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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));
|
2023-12-07 18:35:52 +01:00
|
|
|
|
|
|
|
|
|
// 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);
|
|
|
|
|
|
2023-11-29 17:13:07 +01:00
|
|
|
|
modifyProductPrimitive(product);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected abstract void modifyProductPrimitive(Product product);
|
2023-12-07 18:35:52 +01:00
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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<OrderType> orderTypesFromCategories(Streamable<String> categories) {
|
|
|
|
|
return categories.stream()
|
|
|
|
|
.filter(Stream.of(OrderType.class.getEnumConstants())
|
|
|
|
|
.map(OrderType::toString)
|
|
|
|
|
.collect(Collectors.toSet())::contains)
|
|
|
|
|
.map(OrderType::valueOf).collect(Collectors.toSet());
|
|
|
|
|
}
|
2023-11-05 16:11:36 +01:00
|
|
|
|
}
|