mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 09:39:57 +00:00
Show the substituted product for parent product ingredients currently not in-stock (closes #1797)
This commit is contained in:
parent
05485b3a4c
commit
a5ff947936
@ -40,6 +40,7 @@
|
||||
|
||||
### Recipes
|
||||
|
||||
- When a parent product is used as an ingredient, which is currently not in stock itself, the substituted product (so the one which was already taken into account when consuming the recipe) is now displayed below the ingredient and the costs (and calories) are taken from that one, to reflect the current real costs even better
|
||||
- Added a new recipes setting (top right corner settings menu) "Show a little checkbox next to each ingredient to mark it as done" (defaults to disabled)
|
||||
- When enabled, next to each ingredient a little checkbox will be shown, when clicked, the ingredient is crossed out (the status is not saved, means reset when the page is reloaded)
|
||||
- Fixed that consuming recipes was possible when not all ingredients were in-stock (and this potentially consumed some of the in-stock ingredients; not matching the message "nothing removed")
|
||||
|
@ -2335,3 +2335,6 @@ msgstr ""
|
||||
|
||||
msgid "The ingredient is crossed out when clicked, the status is not saved, means reset when the page is reloaded"
|
||||
msgstr ""
|
||||
|
||||
msgid "The parent product %1$s is currently not in stock, %2$s is the current next sub product based on the default consume rule (Opened first, then first due first, then first in first out)"
|
||||
msgstr ""
|
||||
|
172
migrations/0181.sql
Normal file
172
migrations/0181.sql
Normal file
@ -0,0 +1,172 @@
|
||||
CREATE VIEW products_current_substitutions
|
||||
AS
|
||||
|
||||
/*
|
||||
When a parent product is not in-stock itself,
|
||||
any sub product (the next based on the default consume rule) should be used
|
||||
|
||||
This view lists all parent products and in the column "product_id_effective" either itself,
|
||||
when the corresponding parent product is currently in-stock itself, or otherwise the next sub product to use
|
||||
*/
|
||||
|
||||
SELECT
|
||||
-1, -- Dummy
|
||||
p_sub.id AS parent_product_id,
|
||||
CASE WHEN p_sub.has_sub_products = 1 THEN
|
||||
CASE WHEN IFNULL(sc.amount, 0) = 0 THEN -- Parent product itself is currently not in stock => use the next sub product
|
||||
(
|
||||
SELECT x_snu.product_id
|
||||
FROM products_resolved x_pr
|
||||
JOIN stock_next_use x_snu
|
||||
ON x_pr.sub_product_id = x_snu.product_id
|
||||
WHERE x_pr.parent_product_id = p_sub.id
|
||||
AND x_pr.parent_product_id != x_pr.sub_product_id
|
||||
ORDER BY x_snu.priority DESC
|
||||
LIMIT 1
|
||||
)
|
||||
ELSE -- Parent product itself is currently in stock => use it
|
||||
p_sub.id
|
||||
END
|
||||
END AS product_id_effective
|
||||
FROM products_view p
|
||||
JOIN products_resolved pr
|
||||
ON p.id = pr.parent_product_id
|
||||
JOIN products_view p_sub
|
||||
ON pr.sub_product_id = p_sub.id
|
||||
JOIN stock_current sc
|
||||
ON p_sub.id = sc.product_id
|
||||
WHERE p_sub.has_sub_products = 1;
|
||||
|
||||
DROP VIEW recipes_pos_resolved;
|
||||
CREATE VIEW recipes_pos_resolved
|
||||
AS
|
||||
|
||||
-- Multiplication by 1.0 to force conversion to float (REAL)
|
||||
|
||||
SELECT
|
||||
r.id AS recipe_id,
|
||||
rp.id AS recipe_pos_id,
|
||||
rp.product_id AS product_id,
|
||||
CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN rp.amount * ((r.desired_servings*1.0) / (r.base_servings*1.0)) ELSE rp.amount * ((r.desired_servings*1.0) / (r.base_servings*1.0)) * ((rnr.includes_servings*1.0) / (rnrr.base_servings*1.0)) END AS recipe_amount,
|
||||
IFNULL(sc.amount_aggregated, 0) AS stock_amount,
|
||||
CASE WHEN IFNULL(sc.amount_aggregated, 0) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 0.00000001 ELSE CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN rp.amount * ((r.desired_servings*1.0) / (r.base_servings*1.0)) ELSE rp.amount * ((r.desired_servings*1.0) / (r.base_servings*1.0)) * ((rnr.includes_servings*1.0) / (rnrr.base_servings*1.0)) END END THEN 1 ELSE 0 END AS need_fulfilled,
|
||||
CASE WHEN IFNULL(sc.amount_aggregated, 0) - CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 0.00000001 ELSE CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN rp.amount * ((r.desired_servings*1.0) / (r.base_servings*1.0)) ELSE rp.amount * ((r.desired_servings*1.0) / (r.base_servings*1.0)) * ((rnr.includes_servings*1.0) / (rnrr.base_servings*1.0)) END END < 0 THEN ABS(IFNULL(sc.amount_aggregated, 0) - (CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN rp.amount * ((r.desired_servings*1.0) / (r.base_servings*1.0)) ELSE rp.amount * ((r.desired_servings*1.0) / (r.base_servings*1.0)) * ((rnr.includes_servings*1.0) / (rnrr.base_servings*1.0)) END)) ELSE 0 END AS missing_amount,
|
||||
IFNULL(sl.amount, 0) * p.qu_factor_purchase_to_stock AS amount_on_shopping_list,
|
||||
CASE WHEN ROUND(IFNULL(sc.amount_aggregated, 0) + (CASE WHEN r.not_check_shoppinglist = 1 THEN 0 ELSE IFNULL(sl.amount, 0) END * p.qu_factor_purchase_to_stock), 2) >= ROUND(CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 0.00000001 ELSE CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN rp.amount * ((r.desired_servings*1.0) / (r.base_servings*1.0)) ELSE rp.amount * ((r.desired_servings*1.0) / (r.base_servings*1.0)) * ((rnr.includes_servings*1.0) / (rnrr.base_servings*1.0)) END END, 2) THEN 1 ELSE 0 END AS need_fulfilled_with_shopping_list,
|
||||
rp.qu_id,
|
||||
(r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) * rp.amount * IFNULL(pcp.price, 0) * rp.price_factor * CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN IFNULL(qucr.factor, 1) ELSE 1 END AS costs,
|
||||
CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos,
|
||||
rp.ingredient_group,
|
||||
pg.name as product_group,
|
||||
rp.id, -- Just a dummy id column
|
||||
r.type as recipe_type,
|
||||
rnr.includes_recipe_id as child_recipe_id,
|
||||
rp.note,
|
||||
rp.variable_amount AS recipe_variable_amount,
|
||||
rp.only_check_single_unit_in_stock,
|
||||
rp.amount / r.base_servings*1.0 * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) * IFNULL(p_effective.calories, 0) * CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN IFNULL(qucr.factor, 1) ELSE 1 END AS calories,
|
||||
p.active AS product_active,
|
||||
CASE pvs.current_due_status
|
||||
WHEN 'ok' THEN 0
|
||||
WHEN 'due_soon' THEN 1
|
||||
WHEN 'overdue' THEN 10
|
||||
WHEN 'expired' THEN 20
|
||||
END AS due_score,
|
||||
IFNULL(pcs.product_id_effective, rp.product_id) AS product_id_effective
|
||||
FROM recipes r
|
||||
JOIN recipes_nestings_resolved rnr
|
||||
ON r.id = rnr.recipe_id
|
||||
JOIN recipes rnrr
|
||||
ON rnr.includes_recipe_id = rnrr.id
|
||||
JOIN recipes_pos rp
|
||||
ON rnr.includes_recipe_id = rp.recipe_id
|
||||
JOIN products p
|
||||
ON rp.product_id = p.id
|
||||
JOIN products_volatile_status pvs
|
||||
ON rp.product_id = pvs.product_id
|
||||
LEFT JOIN product_groups pg
|
||||
ON p.product_group_id = pg.id
|
||||
LEFT JOIN (
|
||||
SELECT product_id, SUM(amount) AS amount
|
||||
FROM shopping_list
|
||||
GROUP BY product_id) sl
|
||||
ON rp.product_id = sl.product_id
|
||||
LEFT JOIN stock_current sc
|
||||
ON rp.product_id = sc.product_id
|
||||
LEFT JOIN quantity_unit_conversions_resolved qucr
|
||||
ON rp.product_id = qucr.product_id
|
||||
AND rp.qu_id = qucr.from_qu_id
|
||||
AND p.qu_id_stock = qucr.to_qu_id
|
||||
LEFT JOIN products_current_substitutions pcs
|
||||
ON rp.product_id = pcs.parent_product_id
|
||||
LEFT JOIN products_current_price pcp
|
||||
ON IFNULL(pcs.product_id_effective, rp.product_id) = pcp.product_id
|
||||
LEFT JOIN products p_effective
|
||||
ON IFNULL(pcs.product_id_effective, rp.product_id) = p_effective.id
|
||||
WHERE rp.not_check_stock_fulfillment = 0
|
||||
|
||||
UNION
|
||||
|
||||
-- Just add all recipe positions which should not be checked against stock with fulfilled need
|
||||
|
||||
SELECT
|
||||
r.id AS recipe_id,
|
||||
rp.id AS recipe_pos_id,
|
||||
rp.product_id AS product_id,
|
||||
CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN rp.amount * ((r.desired_servings*1.0) / (r.base_servings*1.0)) ELSE rp.amount * ((r.desired_servings*1.0) / (r.base_servings*1.0)) * ((rnr.includes_servings*1.0) / (rnrr.base_servings*1.0)) END AS recipe_amount,
|
||||
IFNULL(sc.amount_aggregated, 0) AS stock_amount,
|
||||
1 AS need_fulfilled,
|
||||
0 AS missing_amount,
|
||||
IFNULL(sl.amount, 0) * p.qu_factor_purchase_to_stock AS amount_on_shopping_list,
|
||||
1 AS need_fulfilled_with_shopping_list,
|
||||
rp.qu_id,
|
||||
(r.desired_servings*1.0 / r.base_servings*1.0) * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) * rp.amount * IFNULL(pcp.price, 0) * rp.price_factor * CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN IFNULL(qucr.factor, 1) ELSE 1 END AS costs,
|
||||
CASE WHEN rnr.recipe_id = rnr.includes_recipe_id THEN 0 ELSE 1 END AS is_nested_recipe_pos,
|
||||
rp.ingredient_group,
|
||||
pg.name as product_group,
|
||||
rp.id, -- Just a dummy id column
|
||||
r.type as recipe_type,
|
||||
rnr.includes_recipe_id as child_recipe_id,
|
||||
rp.note,
|
||||
rp.variable_amount AS recipe_variable_amount,
|
||||
rp.only_check_single_unit_in_stock,
|
||||
rp.amount / r.base_servings*1.0 * (rnr.includes_servings*1.0 / CASE WHEN rnr.recipe_id != rnr.includes_recipe_id THEN rnrr.base_servings*1.0 ELSE 1 END) * IFNULL(p_effective.calories, 0) * CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN IFNULL(qucr.factor, 1) ELSE 1 END AS calories,
|
||||
p.active AS product_active,
|
||||
CASE pvs.current_due_status
|
||||
WHEN 'ok' THEN 0
|
||||
WHEN 'due_soon' THEN 1
|
||||
WHEN 'overdue' THEN 10
|
||||
WHEN 'expired' THEN 20
|
||||
END AS due_score,
|
||||
IFNULL(pcs.product_id_effective, rp.product_id) AS product_id_effective
|
||||
FROM recipes r
|
||||
JOIN recipes_nestings_resolved rnr
|
||||
ON r.id = rnr.recipe_id
|
||||
JOIN recipes rnrr
|
||||
ON rnr.includes_recipe_id = rnrr.id
|
||||
JOIN recipes_pos rp
|
||||
ON rnr.includes_recipe_id = rp.recipe_id
|
||||
JOIN products p
|
||||
ON rp.product_id = p.id
|
||||
JOIN products_volatile_status pvs
|
||||
ON rp.product_id = pvs.product_id
|
||||
LEFT JOIN product_groups pg
|
||||
ON p.product_group_id = pg.id
|
||||
LEFT JOIN (
|
||||
SELECT product_id, SUM(amount) AS amount
|
||||
FROM shopping_list
|
||||
GROUP BY product_id) sl
|
||||
ON rp.product_id = sl.product_id
|
||||
LEFT JOIN stock_current sc
|
||||
ON rp.product_id = sc.product_id
|
||||
LEFT JOIN quantity_unit_conversions_resolved qucr
|
||||
ON rp.product_id = qucr.product_id
|
||||
AND rp.qu_id = qucr.from_qu_id
|
||||
AND p.qu_id_stock = qucr.to_qu_id
|
||||
LEFT JOIN products_current_substitutions pcs
|
||||
ON rp.product_id = pcs.parent_product_id
|
||||
LEFT JOIN products_current_price pcp
|
||||
ON IFNULL(pcs.product_id_effective, rp.product_id) = pcp.product_id
|
||||
LEFT JOIN products p_effective
|
||||
ON IFNULL(pcs.product_id_effective, rp.product_id) = p_effective.id
|
||||
WHERE rp.not_check_stock_fulfillment = 1;
|
@ -501,6 +501,15 @@
|
||||
<span class="timeago-contextual">@if(FindObjectInArrayByPropertyValue($recipePositionsResolved, 'recipe_pos_id', $selectedRecipePosition->id)->need_fulfilled == 1) {{ $__t('Enough in stock') }} @else {{ $__t('Not enough in stock, %1$s missing, %2$s already on shopping list', round($selectedRecipePosition->missing_amount, 2), round($selectedRecipePosition->amount_on_shopping_list, 2)) }} @endif</span>
|
||||
</span>
|
||||
@endif
|
||||
@if($selectedRecipePosition->product_id != $selectedRecipePosition->product_id_effective)
|
||||
<br class="d-print-none">
|
||||
<span class="text-muted d-print-none"
|
||||
data-toggle="tooltip"
|
||||
data-trigger="hover click"
|
||||
title="{{ $__t('The parent product %1$s is currently not in stock, %2$s is the current next sub product based on the default consume rule (Opened first, then first due first, then first in first out)', FindObjectInArrayByPropertyValue($products, 'id', $selectedRecipePosition->product_id)->name, FindObjectInArrayByPropertyValue($products, 'id', $selectedRecipePosition->product_id_effective)->name) }}">
|
||||
<i class="fas fa-exchange-alt"></i> {{ FindObjectInArrayByPropertyValue($products, 'id', $selectedRecipePosition->product_id_effective)->name }}
|
||||
</span>
|
||||
@endif
|
||||
@if($selectedRecipePosition->need_fulfilled == 1 && GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) <span class="float-right font-italic ml-2 locale-number locale-number-currency">{{ $selectedRecipePosition->costs }}</span> @endif
|
||||
<span class="float-right font-italic"><span class="locale-number locale-number-generic">{{ $selectedRecipePosition->calories }}</span> {{ $__t('Calories') }}</span>
|
||||
@if(!empty($selectedRecipePosition->recipe_variable_amount))
|
||||
|
Loading…
x
Reference in New Issue
Block a user