You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
450 lines
9.9 KiB
450 lines
9.9 KiB
package com.mousetech.gourmetj;
|
|
|
|
import java.io.Serializable;
|
|
import java.util.ArrayList;
|
|
import java.util.Comparator;
|
|
import java.util.List;
|
|
import java.util.stream.Collectors;
|
|
|
|
import jakarta.annotation.PostConstruct;
|
|
import jakarta.faces.view.ViewScoped;
|
|
import jakarta.inject.Inject;
|
|
import jakarta.inject.Named;
|
|
|
|
import org.apache.commons.lang3.StringUtils;
|
|
import org.primefaces.model.StreamedContent;
|
|
import org.slf4j.Logger;
|
|
import org.slf4j.LoggerFactory;
|
|
|
|
import com.mousetech.gourmetj.persistence.dao.PantryRepository;
|
|
import com.mousetech.gourmetj.persistence.dao.ShopcatRepository;
|
|
import com.mousetech.gourmetj.persistence.model.Ingredient;
|
|
import com.mousetech.gourmetj.persistence.model.Pantry;
|
|
import com.mousetech.gourmetj.persistence.model.Recipe;
|
|
import com.mousetech.gourmetj.persistence.model.Shopcat;
|
|
import com.mousetech.gourmetj.persistence.service.RecipeService;
|
|
import com.mousetech.gourmetj.utils.YamlShoppingList;
|
|
|
|
/**
|
|
* Backing bean for the Shopping List tab of the "More..." page.
|
|
* _TestedBy ShoppingListBeanTest
|
|
*
|
|
* @author timh
|
|
* @since Jan 16, 2022
|
|
*/
|
|
@Named
|
|
@ViewScoped
|
|
public class ShoppingListBean implements Serializable {
|
|
|
|
public class RecipeReference {
|
|
|
|
private int count;
|
|
private Recipe recipe;
|
|
|
|
/**
|
|
* Constructor Constructor.
|
|
*
|
|
* @param r Recipe to reference (from Shopping List)
|
|
*/
|
|
public RecipeReference(Recipe r) {
|
|
count = 1;
|
|
recipe = r;
|
|
}
|
|
|
|
/**
|
|
* @return the count
|
|
*/
|
|
public int getCount() {
|
|
return count;
|
|
}
|
|
|
|
/**
|
|
* @param count the count to set
|
|
*/
|
|
public void setCount(int count) {
|
|
this.count = count;
|
|
}
|
|
|
|
/**
|
|
* @return the recipe
|
|
*/
|
|
public Recipe getRecipe() {
|
|
return recipe;
|
|
}
|
|
|
|
/**
|
|
* @param recipe the recipe to set
|
|
*/
|
|
public void setRecipe(Recipe recipe) {
|
|
this.recipe = recipe;
|
|
}
|
|
|
|
}
|
|
|
|
/**
|
|
* Serial version for session save/restore
|
|
*/
|
|
private static final long serialVersionUID =
|
|
7449440266704831598L;
|
|
|
|
/* Logger */
|
|
|
|
@SuppressWarnings("unused")
|
|
private static final Logger log =
|
|
LoggerFactory.getLogger(ShoppingListBean.class);
|
|
|
|
@Inject
|
|
private UserSession userSession;
|
|
|
|
private List<ShopIngredient> siList;
|
|
|
|
private List<RecipeReference> recipeList;
|
|
|
|
@PostConstruct
|
|
public void init() {
|
|
// Load up details on recipes
|
|
this.siList = new ArrayList<ShopIngredient>(30);
|
|
buildMaps();
|
|
}
|
|
|
|
public void clearRecipeList() {
|
|
this.userSession.getShoppingList().clear();
|
|
getRecipeList().clear();
|
|
}
|
|
|
|
public List<RecipeReference> getRecipeList() {
|
|
if (this.recipeList == null) {
|
|
this.recipeList = loadRecipeList();
|
|
}
|
|
return this.recipeList;
|
|
}
|
|
|
|
@Inject
|
|
RecipeService recipeService;
|
|
|
|
private List<RecipeReference> loadRecipeList() {
|
|
List<RecipeReference> list =
|
|
userSession.getShoppingList().stream()
|
|
.map(r -> new RecipeReference(fetchRecipe(r)))
|
|
.collect(Collectors.toList());
|
|
return list;
|
|
}
|
|
|
|
/**
|
|
* Fetch the current version of a recipe. The reference
|
|
* version may be out of date if the recipe was edited or
|
|
* the Pantry status of one or more Ingredients changed.
|
|
* @param r reference recipe
|
|
* @return current version of reference recipe
|
|
*/
|
|
private Recipe fetchRecipe(Recipe r) {
|
|
Recipe rnew = this.recipeService.findDetails(r.getId());
|
|
return rnew;
|
|
}
|
|
|
|
public List<ShopIngredient> getIngredientList() {
|
|
return this.siList;
|
|
}
|
|
|
|
private void buildMaps() {
|
|
this.siList = new ArrayList<ShopIngredient>(30);
|
|
for (RecipeReference r : this.getRecipeList()) {
|
|
buildMapsFor(r);
|
|
}
|
|
// Now consolidate amounts and sort by
|
|
// shopcat/item/ingkey
|
|
optimizeIngredients(this.siList);
|
|
|
|
this.siList.sort(new ShopclassComparator());
|
|
}
|
|
|
|
/**
|
|
* Run the ingredient list for the selected recipe and add
|
|
* them to siList
|
|
*
|
|
* @param r
|
|
*
|
|
* @see #buildMaps()
|
|
*/
|
|
private void buildMapsFor(RecipeReference r) {
|
|
final int multiplier = r.getCount();
|
|
if (multiplier == 0) {
|
|
return;
|
|
}
|
|
for (Ingredient ing : r.getRecipe()
|
|
.getIngredientHash()) {
|
|
String ingkey = ing.getIngkey();
|
|
if (StringUtils.isBlank(ingkey)) {
|
|
continue;
|
|
}
|
|
String shopCatName = ing.getShopCat() != null
|
|
? ing.getShopCat().getShopcategory()
|
|
: null;
|
|
ShopIngredient sing;
|
|
try {
|
|
Double amt = ing.getAmount();
|
|
if (multiplier > 1 && (amt != null)) {
|
|
amt *= multiplier;
|
|
}
|
|
boolean inPantry = false;
|
|
Pantry pantry = ing.getPantry();
|
|
if ((pantry != null) && pantry.getPantry()) {
|
|
inPantry = true;
|
|
}
|
|
sing = new ShopIngredient(amt, ing.getUnit(),
|
|
ing.getItem(), ing.getIngkey(),
|
|
shopCatName, inPantry);
|
|
siList.add(sing);
|
|
} catch (Exception e) {
|
|
log.error("Unable to create ShopIngredient for "
|
|
+ r.getRecipe() + " Ingredient " + ing);
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Sort ShopIngredient list, then optimize it by
|
|
* consolidating amounts where possible.
|
|
*
|
|
* @param victim List to optimize
|
|
*
|
|
* #TestedBy @see ShoppingListBeanTest
|
|
*/
|
|
static void optimizeIngredients(
|
|
List<ShopIngredient> victim) {
|
|
victim.sort(null);
|
|
for (int i = 0; i < (victim.size() - 1); i++) {
|
|
ShopIngredient si = victim.get(i);
|
|
ShopIngredient si2 = victim.get(i + 1);
|
|
if (si.compareTo(si2) == 0) {
|
|
si.setAmount(si.getAmount() + si2.getAmount());
|
|
victim.remove(si2); // reduces size()
|
|
}
|
|
}
|
|
}
|
|
|
|
class ShopclassComparator
|
|
implements Comparator<ShopIngredient> {
|
|
|
|
@Override
|
|
public int compare(ShopIngredient ing1,
|
|
ShopIngredient ing2) {
|
|
int i = 0;
|
|
i = relate(ing1.getShopCat(), ing2.getShopCat());
|
|
if (i != 0) {
|
|
return i;
|
|
}
|
|
i = relate(ing1.getItem(), ing2.getItem());
|
|
if (i != 0) {
|
|
return i;
|
|
}
|
|
i = relate(ing1.getIngkey(), ing2.getIngkey());
|
|
if (i != 0) {
|
|
return i;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private int relate(String item2, String item3) {
|
|
if ((item2 == null) && (item3 == null)) {
|
|
return 0;
|
|
}
|
|
if (item2 == null) {
|
|
return -1;
|
|
}
|
|
if (item3 == null) {
|
|
return 1;
|
|
}
|
|
return item2.compareTo(item3);
|
|
}
|
|
}
|
|
|
|
public StreamedContent getDlIngredientList() {
|
|
return YamlShoppingList
|
|
.createDownload(getIngredientList());
|
|
}
|
|
|
|
|
|
@Inject
|
|
PantryRepository pantryRepository;
|
|
|
|
/**
|
|
* Listener for when Pantry checkbox is checked/unchecked
|
|
* @param ing Ingredient to update
|
|
*/
|
|
public void updatePantry(ShopIngredient ing) {
|
|
String ingKey = ing.getIngkey();
|
|
boolean checked = ing.isInPantry();
|
|
Pantry pantry = pantryRepository.findPantryByIngkey(ingKey);
|
|
if ( pantry == null ) {
|
|
pantry = new Pantry(ingKey, checked);
|
|
} else {
|
|
pantry.setPantry(checked);
|
|
}
|
|
pantryRepository.save(pantry);
|
|
// Reset recipes to cover ingredient change
|
|
this.recipeList = null;
|
|
}
|
|
|
|
// =============================================
|
|
private List<String> shopcatList;
|
|
|
|
public List<String> getShopcatList() {
|
|
if (shopcatList == null) {
|
|
shopcatList = loadShopcatList();
|
|
}
|
|
return shopcatList;
|
|
}
|
|
|
|
@Inject
|
|
ShopcatRepository shopcatRepository;
|
|
|
|
private List<String> loadShopcatList() {
|
|
return shopcatRepository.findDistinctCategoryNative();
|
|
// .findAllByOrderByShopcategoryAsc();
|
|
}
|
|
|
|
private Shopcat xeditShopcat = new Shopcat();
|
|
|
|
private String oldShopcategoryName;
|
|
|
|
/**
|
|
* @return the editShopcat
|
|
*/
|
|
public Shopcat getEditShopcat() {
|
|
return xeditShopcat;
|
|
}
|
|
|
|
public void doEditShopcat(int scId) {
|
|
// xeditShopcat = null;
|
|
// final List<Shopcat> scl = getShopcatList();
|
|
// for (Shopcat sc : scl) {
|
|
// if (sc.getId() == scId) {
|
|
// xeditShopcat = sc;
|
|
// this.oldShopcategoryName = sc.getShopcategory();
|
|
// if (sc.getPosition() == null) {
|
|
// sc.setPosition(0);
|
|
// }
|
|
// return;
|
|
// }
|
|
// }
|
|
// log.error("SHOPCAT " + scId + " NOT FOUND");
|
|
}
|
|
|
|
public void ajaxOnClickShopcatIngkey() {
|
|
// Saves 1 shopcat/ingkey
|
|
this.shopcatRepository.save(xeditShopcat);
|
|
this.shopcatList = null;
|
|
}
|
|
|
|
/**
|
|
* Updates all ingredient keys for shopcat name-change. Note
|
|
* that once done, this cannot be undone!
|
|
*/
|
|
public void ajaxOnClickShopcat() {
|
|
this.shopcatRepository.UpdateShopcats(
|
|
this.oldShopcategoryName,
|
|
xeditShopcat.getShopcategory());
|
|
this.shopcatList = null;
|
|
}
|
|
|
|
/**
|
|
* Primefaces AJAX listener for changes to amount values of
|
|
* recipes the recipe list. Forces re-computation of
|
|
* ingredient requirements.
|
|
*/
|
|
public void pfAmountChange() {
|
|
buildMaps();
|
|
}
|
|
|
|
// ===
|
|
private String selectedShopcat;
|
|
|
|
/**
|
|
* @return the selectedShopcat
|
|
*/
|
|
public String getSelectedShopcat() {
|
|
return selectedShopcat;
|
|
}
|
|
|
|
/**
|
|
* @param selectedShopcat the selectedShopcat to set
|
|
*/
|
|
public void setSelectedShopcat(String selectedShopcat) {
|
|
this.selectedShopcat = selectedShopcat;
|
|
this.ingkeyList = null;
|
|
}
|
|
|
|
private List<String> selectedIngkey;
|
|
|
|
/**
|
|
* @return the selectedIngkey
|
|
*/
|
|
public List<String> getSelectedIngkey() {
|
|
return selectedIngkey;
|
|
}
|
|
|
|
/**
|
|
* @param selectedIngkey the selectedIngkey to set
|
|
*/
|
|
public void setSelectedIngkey(List<String> selectedIngkey) {
|
|
this.selectedIngkey = selectedIngkey;
|
|
}
|
|
|
|
private List<String> ingkeyList;
|
|
|
|
/**
|
|
* @return the ingkeyList
|
|
*/
|
|
public List<String> getIngkeyList() {
|
|
if (ingkeyList == null) {
|
|
ingkeyList = loadIngkeyListFor(selectedShopcat);
|
|
}
|
|
return ingkeyList;
|
|
}
|
|
|
|
private List<String> loadIngkeyListFor(
|
|
String selectedShopcat2) {
|
|
List<String> list = this.shopcatRepository
|
|
.findByIngkeySorted(selectedShopcat2);
|
|
return list;
|
|
}
|
|
|
|
private String newShopcat;
|
|
|
|
/**
|
|
* @return the newShopcat
|
|
*/
|
|
public String getNewShopcat() {
|
|
return newShopcat;
|
|
}
|
|
|
|
/**
|
|
* @param newShopcat the newShopcat to set
|
|
*/
|
|
public void setNewShopcat(String newShopcat) {
|
|
this.newShopcat = newShopcat;
|
|
}
|
|
|
|
public List<String> suggestShopcat(String query) {
|
|
return this.shopcatList;
|
|
}
|
|
|
|
public void doChangeShopcat() {
|
|
String oldCat = this.getSelectedShopcat();
|
|
String newCat = this.getNewShopcat();
|
|
if (oldCat.equals(newCat)) {
|
|
return; // effective NO-OP
|
|
}
|
|
newCat = newCat.trim();
|
|
if (StringUtils.isBlank(newCat)) {
|
|
this.shopcatRepository
|
|
.deleteShopcatFor(this.getSelectedIngkey());
|
|
} else {
|
|
this.shopcatRepository.updateShopcatFor(newCat,
|
|
this.getSelectedIngkey());
|
|
}
|
|
}
|
|
|
|
}
|
|
|