commit
c80b8598d4
16 changed files with 1232 additions and 24 deletions
@ -0,0 +1,121 @@ |
|||||||
|
CREATE TABLE keylookup ( |
||||||
|
id INTEGER NOT NULL, |
||||||
|
word TEXT, |
||||||
|
item TEXT, |
||||||
|
ingkey TEXT, |
||||||
|
count INTEGER, |
||||||
|
PRIMARY KEY (id) |
||||||
|
); |
||||||
|
CREATE TABLE info ( |
||||||
|
version_super INTEGER, |
||||||
|
version_major INTEGER, |
||||||
|
version_minor INTEGER, |
||||||
|
last_access INTEGER, |
||||||
|
rowid INTEGER NOT NULL, |
||||||
|
PRIMARY KEY (rowid) |
||||||
|
); |
||||||
|
CREATE TABLE recipe ( |
||||||
|
id INTEGER NOT NULL, |
||||||
|
title TEXT, |
||||||
|
instructions TEXT, |
||||||
|
modifications TEXT, |
||||||
|
cuisine TEXT, |
||||||
|
rating INTEGER, |
||||||
|
description TEXT, |
||||||
|
source TEXT, |
||||||
|
preptime INTEGER, |
||||||
|
cooktime INTEGER, |
||||||
|
servings FLOAT, |
||||||
|
yields FLOAT, |
||||||
|
yield_unit VARCHAR(32), |
||||||
|
image BLOB, |
||||||
|
thumb BLOB, |
||||||
|
deleted BOOLEAN, |
||||||
|
recipe_hash VARCHAR(32), |
||||||
|
ingredient_hash VARCHAR(32), |
||||||
|
link TEXT, |
||||||
|
last_modified INTEGER, |
||||||
|
PRIMARY KEY (id), |
||||||
|
CHECK (deleted IN (0, 1)) |
||||||
|
); |
||||||
|
CREATE TABLE plugin_info ( |
||||||
|
plugin TEXT, |
||||||
|
id INTEGER NOT NULL, |
||||||
|
version_super INTEGER, |
||||||
|
version_major INTEGER, |
||||||
|
version_minor INTEGER, |
||||||
|
plugin_version VARCHAR(32), |
||||||
|
PRIMARY KEY (id) |
||||||
|
); |
||||||
|
CREATE TABLE categories ( |
||||||
|
id INTEGER NOT NULL, |
||||||
|
recipe_id INTEGER, |
||||||
|
category TEXT, |
||||||
|
PRIMARY KEY (id), |
||||||
|
FOREIGN KEY(recipe_id) REFERENCES recipe (id) |
||||||
|
); |
||||||
|
CREATE TABLE ingredients ( |
||||||
|
id INTEGER NOT NULL, |
||||||
|
recipe_id INTEGER, |
||||||
|
refid INTEGER, |
||||||
|
unit TEXT, |
||||||
|
amount FLOAT, |
||||||
|
rangeamount FLOAT, |
||||||
|
item TEXT, |
||||||
|
ingkey TEXT, |
||||||
|
optional BOOLEAN, |
||||||
|
shopoptional INTEGER, |
||||||
|
inggroup TEXT, |
||||||
|
position INTEGER, |
||||||
|
deleted BOOLEAN, |
||||||
|
PRIMARY KEY (id), |
||||||
|
CHECK (deleted IN (0, 1)), |
||||||
|
FOREIGN KEY(recipe_id) REFERENCES recipe (id), |
||||||
|
FOREIGN KEY(refid) REFERENCES recipe (id), |
||||||
|
CHECK (optional IN (0, 1)) |
||||||
|
); |
||||||
|
CREATE TABLE IF NOT EXISTS "pantry" ( |
||||||
|
id INTEGER NOT NULL, |
||||||
|
ingkey TEXT(32), |
||||||
|
pantry BOOLEAN, |
||||||
|
PRIMARY KEY (id), |
||||||
|
CHECK (pantry IN (0, 1)) |
||||||
|
); |
||||||
|
CREATE TABLE unitdict ( |
||||||
|
id INTEGER NOT NULL, |
||||||
|
ukey VARCHAR(150), |
||||||
|
value VARCHAR(150), |
||||||
|
PRIMARY KEY (id) |
||||||
|
); |
||||||
|
CREATE TABLE crossunitdict ( |
||||||
|
id INTEGER NOT NULL, |
||||||
|
cukey VARCHAR(150), |
||||||
|
value VARCHAR(150), |
||||||
|
PRIMARY KEY (id) |
||||||
|
); |
||||||
|
CREATE TABLE density ( |
||||||
|
id INTEGER NOT NULL, |
||||||
|
dkey VARCHAR(150), |
||||||
|
value VARCHAR(150), |
||||||
|
PRIMARY KEY (id) |
||||||
|
); |
||||||
|
CREATE TABLE shopcatsorder ( |
||||||
|
id INTEGER NOT NULL, |
||||||
|
shopcategory TEXT(32), |
||||||
|
position INTEGER, |
||||||
|
PRIMARY KEY (id) |
||||||
|
); |
||||||
|
CREATE TABLE convtable ( |
||||||
|
id INTEGER NOT NULL, |
||||||
|
ckey VARCHAR(150), |
||||||
|
value VARCHAR(150), |
||||||
|
PRIMARY KEY (id) |
||||||
|
); |
||||||
|
CREATE TABLE IF NOT EXISTS "shopcats" ( |
||||||
|
id INTEGER NOT NULL, |
||||||
|
ingkey TEXT(32), |
||||||
|
shopcategory TEXT, |
||||||
|
position INTEGER, |
||||||
|
PRIMARY KEY (id) |
||||||
|
); |
||||||
|
CREATE TABLE IF NOT EXISTS "recipe_ingredients" ("Recipe_id" integer not null, "ingredientHash_id" integer not null, unique ("ingredientHash_id")); |
@ -0,0 +1,165 @@ |
|||||||
|
package com.mousetech.gourmetj; |
||||||
|
|
||||||
|
import com.mousetech.gourmetj.utils.IngredientDigester; |
||||||
|
import com.mousetech.gourmetj.utils.IngredientDigester.IngredientAmountFormat; |
||||||
|
|
||||||
|
public class ShopIngredient implements Comparable<Object> { |
||||||
|
/** |
||||||
|
* Constructor. |
||||||
|
* |
||||||
|
* @param shopCat |
||||||
|
* @param ingkey |
||||||
|
*/ |
||||||
|
public ShopIngredient( String shopCat, String item, |
||||||
|
String ingkey) { |
||||||
|
this.shopCat = shopCat; |
||||||
|
this.ingkey = ingkey; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Constructor. |
||||||
|
* |
||||||
|
* @param amount |
||||||
|
* @param unit |
||||||
|
* @param item |
||||||
|
* @param ingkey |
||||||
|
* @param shopCat |
||||||
|
*/ |
||||||
|
public ShopIngredient( Double amount, String unit, |
||||||
|
String item, String ingkey, |
||||||
|
String shopCat) { |
||||||
|
this.amount = amount; |
||||||
|
this.unit = unit; |
||||||
|
this.item = item; |
||||||
|
this.ingkey = ingkey; |
||||||
|
this.shopCat = shopCat; |
||||||
|
} |
||||||
|
|
||||||
|
private String shopCat; |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the shopCat |
||||||
|
*/ |
||||||
|
public String getShopCat() { |
||||||
|
return shopCat; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param shopCat the shopCat to set |
||||||
|
*/ |
||||||
|
public void setShopCat(String shopCat) { |
||||||
|
this.shopCat = shopCat; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the ingkey |
||||||
|
*/ |
||||||
|
public String getIngkey() { |
||||||
|
return ingkey; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param ingkey the ingkey to set |
||||||
|
*/ |
||||||
|
public void setIngkey(String ingkey) { |
||||||
|
this.ingkey = ingkey; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the amount |
||||||
|
*/ |
||||||
|
public Double getAmount() { |
||||||
|
return amount; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param amount the amount to set |
||||||
|
*/ |
||||||
|
public void setAmount(Double amount) { |
||||||
|
this.amount = amount; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the displayAmount |
||||||
|
*/ |
||||||
|
public String getDisplayAmount() { |
||||||
|
Double amt = this.getAmount(); |
||||||
|
if ( amt == null) { |
||||||
|
return ""; |
||||||
|
} |
||||||
|
return IngredientDigester.displayAmount( |
||||||
|
IngredientAmountFormat.IA_TEXT, amt, |
||||||
|
null); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the unit |
||||||
|
*/ |
||||||
|
public String getUnit() { |
||||||
|
return unit; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* @param unit the unit to set |
||||||
|
*/ |
||||||
|
public void setUnit(String unit) { |
||||||
|
this.unit = unit; |
||||||
|
} |
||||||
|
|
||||||
|
private String item; |
||||||
|
|
||||||
|
/** |
||||||
|
* @return the item |
||||||
|
*/ |
||||||
|
public String getItem() { |
||||||
|
return item; |
||||||
|
} |
||||||
|
|
||||||
|
private String ingkey; |
||||||
|
private Double amount; |
||||||
|
private String displayAmount; |
||||||
|
private String unit; |
||||||
|
|
||||||
|
@Override |
||||||
|
public int compareTo(Object o) { |
||||||
|
if ((o == null) || !(o instanceof ShopIngredient)) { |
||||||
|
throw new RuntimeException( |
||||||
|
"Invalid shipIngredient comparison"); |
||||||
|
} |
||||||
|
ShopIngredient o1 = (ShopIngredient) o; |
||||||
|
int i = relate(this.getItem(), o1.getItem()); |
||||||
|
if (i != 0) { |
||||||
|
return i; |
||||||
|
} |
||||||
|
i = relate(this.getShopCat(), o1.getShopCat()); |
||||||
|
if (i != 0) { |
||||||
|
return i; |
||||||
|
} |
||||||
|
// TODO: normalize case, singular/plural/abbreviations
|
||||||
|
i = relate(this.getUnit(), o1.getUnit()); |
||||||
|
if (i != 0) { |
||||||
|
return i; |
||||||
|
} |
||||||
|
return i; // ZERO
|
||||||
|
} |
||||||
|
|
||||||
|
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); |
||||||
|
} |
||||||
|
|
||||||
|
@Override |
||||||
|
public String toString() { |
||||||
|
return this.getAmount() + " " + this.getUnit() + " " |
||||||
|
+ this.getItem() + " " + this.getIngkey() + " " |
||||||
|
+ this.getShopCat(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,397 @@ |
|||||||
|
package com.mousetech.gourmetj; |
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream; |
||||||
|
import java.io.PrintWriter; |
||||||
|
import java.io.Serializable; |
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.Comparator; |
||||||
|
import java.util.List; |
||||||
|
import java.util.stream.Collectors; |
||||||
|
|
||||||
|
import javax.annotation.PostConstruct; |
||||||
|
import javax.faces.view.ViewScoped; |
||||||
|
import javax.inject.Inject; |
||||||
|
import javax.inject.Named; |
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils; |
||||||
|
import org.primefaces.model.ByteArrayContent; |
||||||
|
import org.primefaces.model.DefaultStreamedContent; |
||||||
|
import org.primefaces.model.StreamedContent; |
||||||
|
import org.slf4j.Logger; |
||||||
|
import org.slf4j.LoggerFactory; |
||||||
|
|
||||||
|
import com.mousetech.gourmetj.persistence.dao.ShopcatRepository; |
||||||
|
import com.mousetech.gourmetj.persistence.model.Ingredient; |
||||||
|
import com.mousetech.gourmetj.persistence.model.Recipe; |
||||||
|
import com.mousetech.gourmetj.persistence.model.Shopcat; |
||||||
|
import com.mousetech.gourmetj.utils.YamlShoppingList; |
||||||
|
|
||||||
|
@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 List<RecipeReference> getRecipeList() { |
||||||
|
if (this.recipeList == null) { |
||||||
|
this.recipeList = loadRecipeList(); |
||||||
|
} |
||||||
|
return this.recipeList; |
||||||
|
} |
||||||
|
|
||||||
|
private List<RecipeReference> loadRecipeList() { |
||||||
|
List<RecipeReference> list = |
||||||
|
userSession.getShoppingList().stream() |
||||||
|
.map(r -> new RecipeReference(r)) |
||||||
|
.collect(Collectors.toList()); |
||||||
|
return list; |
||||||
|
} |
||||||
|
|
||||||
|
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; |
||||||
|
} |
||||||
|
sing = new ShopIngredient(amt, ing.getUnit(), |
||||||
|
ing.getItem(), ing.getIngkey(), |
||||||
|
shopCatName); |
||||||
|
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()); |
||||||
|
} |
||||||
|
|
||||||
|
// =============================================
|
||||||
|
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()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,71 @@ |
|||||||
|
package com.mousetech.gourmetj.utils; |
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream; |
||||||
|
import java.io.PrintWriter; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils; |
||||||
|
import org.primefaces.model.ByteArrayContent; |
||||||
|
import org.primefaces.model.StreamedContent; |
||||||
|
|
||||||
|
import com.mousetech.gourmetj.ShopIngredient; |
||||||
|
|
||||||
|
/** |
||||||
|
* Construct a Primefaces file output content for an ingredient |
||||||
|
* list in YAML format. |
||||||
|
* |
||||||
|
* @author timh |
||||||
|
* @since Jan 15, 2022 |
||||||
|
*/ |
||||||
|
|
||||||
|
public class YamlShoppingList { |
||||||
|
|
||||||
|
public static StreamedContent createDownload( |
||||||
|
List<ShopIngredient> ingredientList) { |
||||||
|
ByteArrayOutputStream ary = new ByteArrayOutputStream(); |
||||||
|
PrintWriter wtr = new PrintWriter(ary); |
||||||
|
wtr.println("---"); |
||||||
|
formatContent(wtr, ingredientList); |
||||||
|
wtr.close(); |
||||||
|
byte[] bas = ary.toByteArray(); |
||||||
|
|
||||||
|
StreamedContent dlList = new ByteArrayContent(bas, |
||||||
|
"text/text", "shopping_list.yml"); |
||||||
|
return dlList; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* Output line items in the ingredient list with topics for |
||||||
|
* each Shopping Category. |
||||||
|
* |
||||||
|
* @param wtr Output Writer |
||||||
|
* @param ingredientList Ingredient list to output. |
||||||
|
*/ |
||||||
|
private static void formatContent(PrintWriter wtr, |
||||||
|
List<ShopIngredient> ingredientList) { |
||||||
|
String oldShopcat = null; |
||||||
|
for (ShopIngredient ing : ingredientList) { |
||||||
|
String newShopcat = ing.getShopCat(); |
||||||
|
if (StringUtils.isBlank(newShopcat)) { |
||||||
|
newShopcat = "Unassigned"; |
||||||
|
} |
||||||
|
if (!StringUtils.equals(newShopcat, oldShopcat)) { |
||||||
|
wtr.println(newShopcat + ":"); |
||||||
|
oldShopcat = newShopcat; |
||||||
|
} |
||||||
|
wtr.print(" - "); |
||||||
|
String displa = ing.getDisplayAmount(); |
||||||
|
wtr.print(displa); |
||||||
|
if (!displa.isBlank()) { |
||||||
|
wtr.print(' '); |
||||||
|
} |
||||||
|
String unit = ing.getUnit(); |
||||||
|
if (StringUtils.isNotBlank(unit)) { |
||||||
|
wtr.print(unit); |
||||||
|
wtr.print(' '); |
||||||
|
} |
||||||
|
wtr.println(ing.getItem()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
} |
@ -0,0 +1,96 @@ |
|||||||
|
<?xml version="1.0" encoding="UTF-8"?> |
||||||
|
<!DOCTYPE html> |
||||||
|
<html xmlns:h="http://xmlns.jcp.org/jsf/html" |
||||||
|
xmlns:f="http://xmlns.jcp.org/jsf/core" |
||||||
|
xmlns:ui="http://java.sun.com/jsf/facelets" |
||||||
|
xmlns:p="http://primefaces.org/ui" |
||||||
|
xmlns:c="http://xmlns.jcp.org/jstl" |
||||||
|
> |
||||||
|
<!-- === Edit ingkey/shopcat === --> |
||||||
|
<h:form id="frmIsk"> |
||||||
|
<p:panelGrid> |
||||||
|
<p:row> |
||||||
|
<p:column> |
||||||
|
<p:outputLabel for="ctlScSel" |
||||||
|
value="Shopping Category" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
<p:column> |
||||||
|
<p:outputLabel for="ctlIngkeySel" |
||||||
|
value="Ingredient Key" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
</p:row> |
||||||
|
<p:row style="vertical-align: top"> |
||||||
|
<p:column> |
||||||
|
<p:selectOneListbox id="ctlScSel" |
||||||
|
style="width: 240px" |
||||||
|
value="#{shoppingListBean.selectedShopcat}" |
||||||
|
> |
||||||
|
<f:selectItems |
||||||
|
value="#{shoppingListBean.shopcatList}" |
||||||
|
/> |
||||||
|
<p:ajax update="ctlIngkeySel" event="change" /> |
||||||
|
</p:selectOneListbox> |
||||||
|
</p:column> |
||||||
|
<p:column> |
||||||
|
<h:selectManyListbox id="ctlIngkeySel" |
||||||
|
style="width: 240px" |
||||||
|
value="#{shoppingListBean.selectedIngkey}" |
||||||
|
label="Ingcat" |
||||||
|
> |
||||||
|
<f:selectItems |
||||||
|
value="#{shoppingListBean.ingkeyList}" |
||||||
|
/> |
||||||
|
<p:ajax event="change" update="ctlChangeCat"/> |
||||||
|
</h:selectManyListbox> |
||||||
|
</p:column> |
||||||
|
</p:row> |
||||||
|
<p:row> |
||||||
|
<p:column> |
||||||
|
<p:outputLabel |
||||||
|
value="Change shopping category to:" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
<p:column> |
||||||
|
<p:autoComplete |
||||||
|
value="#{shoppingListBean.newShopcat}" |
||||||
|
autoSelection="false" forceSelection="false" |
||||||
|
maxResults="12" |
||||||
|
completeMethod="#{shoppingListBean.suggestShopcat}" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
</p:row> |
||||||
|
<p:row> |
||||||
|
<p:column> |
||||||
|
<p:commandButton id="ctlChangeCat" value="Change..." |
||||||
|
disabled="#{empty shoppingListBean.selectedIngkey}" |
||||||
|
onclick="PF('dlgOkRecat').show()" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
<p:column> |
||||||
|
<h:outputText value="" /> |
||||||
|
</p:column> |
||||||
|
</p:row> |
||||||
|
</p:panelGrid> |
||||||
|
</h:form> |
||||||
|
<!-- --> |
||||||
|
<h:form id="frmDelete"> |
||||||
|
<p:confirmDialog closable="false" id="dlgOkRecat" |
||||||
|
header="Confirm Change - CANNOT UNDO" |
||||||
|
message="OK to CHANGE Shopping Category for these Ingredient Keys?" |
||||||
|
severity="alert" widgetVar="dlgOkRecat" |
||||||
|
style="z-index: 25000" |
||||||
|
> |
||||||
|
<p:commandButton id="dlgOK" value="OK" |
||||||
|
oncomplete="PF('dlgOkRecat').hide()" |
||||||
|
action="#{shoppingListBean.doChangeShopcat}" |
||||||
|
update="@form:@parent:frmIsk:ctlScSel @form:@parent:frmIsk:ctlIngkeySel" |
||||||
|
immediate="true" |
||||||
|
/> |
||||||
|
<p:commandButton id="dlgCancel" value="Cancel" |
||||||
|
onclick="PF('dlgOkRecat').hide()" |
||||||
|
/> |
||||||
|
</p:confirmDialog> |
||||||
|
</h:form> |
||||||
|
</html> |
@ -0,0 +1,68 @@ |
|||||||
|
<!DOCTYPE html> |
||||||
|
<html xmlns="http://www.w3.org/1999/xhtml" |
||||||
|
xmlns:h="http://xmlns.jcp.org/jsf/html" |
||||||
|
xmlns:f="http://xmlns.jcp.org/jsf/core" |
||||||
|
xmlns:ui="http://java.sun.com/jsf/facelets" |
||||||
|
xmlns:p="http://primefaces.org/ui" |
||||||
|
> |
||||||
|
<h:head> |
||||||
|
<title>Shopping Category</title> |
||||||
|
<style type="text/css"> |
||||||
|
html { |
||||||
|
font-size: 14px; |
||||||
|
} |
||||||
|
</style> |
||||||
|
</h:head> |
||||||
|
<h:body> |
||||||
|
<ui:component> |
||||||
|
<h:form id="frmShopcat"> |
||||||
|
<p:panelGrid columns="1"> |
||||||
|
<p:dataTable id="tblShopcats" |
||||||
|
style="width: 600px" |
||||||
|
value="#{shoppingListBean.shopcatList}" |
||||||
|
sortBy="#{item.shopCat}" var="item" |
||||||
|
> |
||||||
|
<p:headerRow> |
||||||
|
<p:column colspan="4"> |
||||||
|
<h:outputText value="#{item.shopCat}" /> |
||||||
|
</p:column> |
||||||
|
</p:headerRow> |
||||||
|
</p:dataTable> |
||||||
|
<div>Ingredient key: |
||||||
|
#{editShopcatBean.ingkey}</div> |
||||||
|
<p:outputLabel for="@next" |
||||||
|
value="Category Name" |
||||||
|
/> |
||||||
|
<p:inputText id="ctlShopcat" |
||||||
|
value="#{editShopcatBean.shopcatName}" |
||||||
|
> |
||||||
|
</p:inputText> |
||||||
|
<h:outputText value="suggestion:" /> |
||||||
|
<p:selectOneMenu id="ctlShopcatMenu" |
||||||
|
value="#{editShopcatBean.shopcatSuggestion}" |
||||||
|
> |
||||||
|
<f:selectItems |
||||||
|
value="#{editShopcatBean.shopcatSuggestionList}" |
||||||
|
/> |
||||||
|
<p:ajax event="change" |
||||||
|
listener="#{editShopcatBean.ajaxShopcatSuggest}" |
||||||
|
update="ctlShopcat" |
||||||
|
/> |
||||||
|
</p:selectOneMenu> |
||||||
|
<p:panelGrid columns="2" style="width: 100%"> |
||||||
|
<p:commandButton id="scDlgOK" value="OK" |
||||||
|
style="width: 6em" |
||||||
|
action="#{recipeDetailBean.doUpdateShopcat}" |
||||||
|
update="form1:tabGroupClient:ingredientTable" |
||||||
|
oncomplete="PF('editShopcatDlg').hide()" |
||||||
|
/> |
||||||
|
<p:commandButton id="scDlgCan" |
||||||
|
value="Cancel" style="width: 6em" |
||||||
|
onclick="PF('editShopcatDlg').hide()" |
||||||
|
/> |
||||||
|
</p:panelGrid> |
||||||
|
</p:panelGrid> |
||||||
|
</h:form> |
||||||
|
</ui:component> |
||||||
|
</h:body> |
||||||
|
</html> |
@ -0,0 +1,141 @@ |
|||||||
|
<?xml version="1.0"?> |
||||||
|
<ui:composition template="/WEB-INF/layout/layout.xhtml" |
||||||
|
xmlns:h="http://xmlns.jcp.org/jsf/html" |
||||||
|
xmlns:f="http://xmlns.jcp.org/jsf/core" |
||||||
|
xmlns:ui="http://java.sun.com/jsf/facelets" |
||||||
|
xmlns:p="http://primefaces.org/ui" |
||||||
|
xmlns:c="http://xmlns.jcp.org/jstl" |
||||||
|
> |
||||||
|
<ui:define name="title">Gourmet Recipe Manager - Shopping</ui:define> |
||||||
|
<ui:define name="content"> |
||||||
|
<style> |
||||||
|
.recipeTitle { |
||||||
|
font-size: larger; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.subtitle { |
||||||
|
font-size: large; |
||||||
|
font-weight: bold; |
||||||
|
} |
||||||
|
|
||||||
|
.ui-panelgrid-cell { |
||||||
|
border-width: 0; |
||||||
|
border-style: none; |
||||||
|
} |
||||||
|
|
||||||
|
.noRecipe { |
||||||
|
text-decoration: line-through; |
||||||
|
color: gray; |
||||||
|
} |
||||||
|
|
||||||
|
.plusRecipe { |
||||||
|
|
||||||
|
} |
||||||
|
</style> |
||||||
|
<h:messages /> |
||||||
|
<p:tabView id="tabGroupClient" orientation="left" |
||||||
|
dynamic="true" |
||||||
|
> |
||||||
|
<p:tab id="overviewTab" title="Shopping List"> |
||||||
|
<h:form id="form1"> |
||||||
|
<p:dataTable id="tblRecipes" |
||||||
|
style="width: 600px" |
||||||
|
value="#{shoppingListBean.recipeList}" |
||||||
|
var="item" |
||||||
|
> |
||||||
|
<f:facet name="header"> |
||||||
|
<h:outputText value="Recipes" /> |
||||||
|
</f:facet> |
||||||
|
<p:column style="width: 4em"> |
||||||
|
<p:spinner required="true" min="0" |
||||||
|
value="#{item.count}" size="1" |
||||||
|
> |
||||||
|
<p:ajax |
||||||
|
listener="#{shoppingListBean.pfAmountChange}" |
||||||
|
update="@form:tblShopIngredients rname" |
||||||
|
/> |
||||||
|
</p:spinner> |
||||||
|
</p:column> |
||||||
|
<p:column> |
||||||
|
<h:outputText id="rname" |
||||||
|
styleClass="#{(item.count eq 0) ? 'noRecipe' :'plusRecipe' }" |
||||||
|
value="#{item.recipe.title}" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
</p:dataTable> |
||||||
|
<!-- ====== Ingredients To Buy ======================= --> |
||||||
|
<p:column id="dlIng"> |
||||||
|
<p:commandButton value="Download List" ajax="false"> |
||||||
|
<p:fileDownload |
||||||
|
value="#{shoppingListBean.dlIngredientList}" |
||||||
|
/> |
||||||
|
</p:commandButton> |
||||||
|
</p:column> |
||||||
|
<p:column id="ingredientsc" |
||||||
|
style="width: 25%; vertical-align: top;" |
||||||
|
> |
||||||
|
<p:dataTable id="tblShopIngredients" |
||||||
|
style="width: 600px; margin-top: 10px" |
||||||
|
value="#{shoppingListBean.ingredientList}" |
||||||
|
sortBy="#{item.shopCat}" var="item" |
||||||
|
> |
||||||
|
<f:facet name="header"> |
||||||
|
<h:outputText |
||||||
|
styleClass="subtitle" |
||||||
|
value="Ingredients" |
||||||
|
/> |
||||||
|
</f:facet> |
||||||
|
<p:headerRow> |
||||||
|
<p:column colspan="4"> |
||||||
|
<h:outputText |
||||||
|
value="#{item.shopCat}" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
</p:headerRow> |
||||||
|
<p:column label="Amt" |
||||||
|
style="width: 3em; text-align: right" |
||||||
|
> |
||||||
|
<h:outputText |
||||||
|
value="#{item.displayAmount}" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
<p:column label="Units" |
||||||
|
style="width: 5em" |
||||||
|
> |
||||||
|
<h:outputText |
||||||
|
value="#{item.unit}" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
<p:column label="Item" |
||||||
|
style="width: 20em" |
||||||
|
> |
||||||
|
<h:outputText |
||||||
|
value="#{item.item}" |
||||||
|
/> |
||||||
|
</p:column> |
||||||
|
</p:dataTable> |
||||||
|
</p:column> |
||||||
|
</h:form> |
||||||
|
</p:tab> |
||||||
|
<!-- --> |
||||||
|
<p:tab id="ingshopcatEditTab" |
||||||
|
title="Edit Shopping Categories" |
||||||
|
> |
||||||
|
<ui:include |
||||||
|
src="/WEB-INF/layout/misctabs/ingshopkey.xhtml" |
||||||
|
/> |
||||||
|
</p:tab> |
||||||
|
<!-- --> |
||||||
|
<p:tab id="tabImportExport" title="Import/Export"> |
||||||
|
<h:outputText value="For future implementation" /> |
||||||
|
</p:tab> |
||||||
|
</p:tabView> |
||||||
|
<h:form id="frmHome"> |
||||||
|
<p:commandButton id="doHome" value="Home" |
||||||
|
icon="ui-icon-home" ajax="false" immediate="true" |
||||||
|
action="main.jsf" |
||||||
|
/> |
||||||
|
</h:form> |
||||||
|
</ui:define> |
||||||
|
</ui:composition> |
@ -0,0 +1,52 @@ |
|||||||
|
/** |
||||||
|
* Copyright (C) 2022, Tim Holloway |
||||||
|
* |
||||||
|
* Date written: Jan 12, 2022 |
||||||
|
* Author: Tim Holloway <timh@mousetech.com> |
||||||
|
*/ |
||||||
|
package com.mousetech.gourmetj; |
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*; |
||||||
|
|
||||||
|
import java.util.ArrayList; |
||||||
|
import java.util.List; |
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeAll; |
||||||
|
import org.junit.jupiter.api.Test; |
||||||
|
|
||||||
|
import com.mousetech.gourmetj.ShopIngredient; |
||||||
|
import com.mousetech.gourmetj.persistence.model.Shopcat; |
||||||
|
|
||||||
|
/** |
||||||
|
* @author timh |
||||||
|
* @since Jan 12, 2022 |
||||||
|
*/ |
||||||
|
class ShoppingListBeanTest { |
||||||
|
|
||||||
|
static List<ShopIngredient> testList; |
||||||
|
|
||||||
|
/** |
||||||
|
* @throws java.lang.Exception |
||||||
|
*/ |
||||||
|
@BeforeAll |
||||||
|
static void setUpBeforeClass() throws Exception { |
||||||
|
testList = new ArrayList<ShopIngredient>(); |
||||||
|
testList.add(new ShopIngredient(2.0d, "cup", |
||||||
|
"sugar", "sugar", "baking")); |
||||||
|
testList.add(new ShopIngredient(1.5, "tsp", |
||||||
|
"salt", "salt", "condiments")); |
||||||
|
testList.add(new ShopIngredient(0.5, "tsp", |
||||||
|
"pepper", "pepper", "condiments")); |
||||||
|
testList.add(new ShopIngredient(2.0d, "cup", |
||||||
|
"milk", "milk", "dairy")); |
||||||
|
testList.add(new ShopIngredient(0.75d, "cup", |
||||||
|
"sugar", "sugar", "baking")); |
||||||
|
} |
||||||
|
|
||||||
|
@Test |
||||||
|
void test() { |
||||||
|
ShoppingListBean.optimizeIngredients(testList); |
||||||
|
assertEquals(4, testList.size()); |
||||||
|
} |
||||||
|
|
||||||
|
} |
Loading…
Reference in new issue