Display total cost of recipes based on last purchase prices (closes #128)

This commit is contained in:
Bernd Bestel 2019-03-03 16:33:48 +01:00
parent 8c11d0f15d
commit bb5daa5f8b
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
6 changed files with 124 additions and 21 deletions

View File

@ -35,19 +35,21 @@ class RecipesController extends BaseController
}
}
// Scale ingredients amount based on desired servings
foreach ($selectedRecipePositions as $selectedRecipePosition)
{
$selectedRecipePosition->amount = $selectedRecipePosition->amount * ($selectedRecipe->desired_servings / $selectedRecipe->base_servings);
}
$selectedRecipeSubRecipes = $this->Database->recipes()->where('id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('name')->fetchAll();
$selectedRecipeSubRecipesPositions = $this->Database->recipes_pos()->where('recipe_id IN (SELECT includes_recipe_id FROM recipes_nestings_resolved WHERE recipe_id = :1 AND includes_recipe_id != :1)', $selectedRecipe->id)->orderBy('ingredient_group')->fetchAll();
// Scale ingredients amount based on desired servings
// Scale ingredients amount based on desired servings & calculate total costs
$recipesFulfillment = $this->RecipesService->GetRecipesFulfillment();
$totalRecipeCosts = 0;
foreach ($selectedRecipePositions as $selectedRecipePosition)
{
$selectedRecipePosition->amount = $selectedRecipePosition->amount * ($selectedRecipe->desired_servings / $selectedRecipe->base_servings);
$totalRecipeCosts += FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $selectedRecipePosition->id)->costs;
}
foreach ($selectedRecipeSubRecipesPositions as $selectedSubRecipePosition)
{
$selectedSubRecipePosition->amount = $selectedSubRecipePosition->amount * ($selectedRecipe->desired_servings / $selectedRecipe->base_servings);
$totalRecipeCosts += FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $selectedSubRecipePosition->id)->costs;
}
$includedRecipeIdsAbsolute = array();
@ -59,7 +61,7 @@ class RecipesController extends BaseController
return $this->AppContainer->view->render($response, 'recipes', [
'recipes' => $recipes,
'recipesFulfillment' => $this->RecipesService->GetRecipesFulfillment(),
'recipesFulfillment' => $recipesFulfillment,
'recipesSumFulfillment' => $this->RecipesService->GetRecipesSumFulfillment(),
'selectedRecipe' => $selectedRecipe,
'selectedRecipePositions' => $selectedRecipePositions,
@ -67,7 +69,8 @@ class RecipesController extends BaseController
'quantityunits' => $this->Database->quantity_units(),
'selectedRecipeSubRecipes' => $selectedRecipeSubRecipes,
'selectedRecipeSubRecipesPositions' => $selectedRecipeSubRecipesPositions,
'includedRecipeIdsAbsolute' => $includedRecipeIdsAbsolute
'includedRecipeIdsAbsolute' => $includedRecipeIdsAbsolute,
'totalRecipeCosts' => $totalRecipeCosts
]);
}

79
migrations/0054.sql Normal file
View File

@ -0,0 +1,79 @@
CREATE VIEW products_current_price
AS
SELECT
p.id AS product_id,
IFNULL(sl.price, 0) AS last_price
FROM products p
LEFT JOIN (
SELECT product_id, MAX(id) AS newest_Id
FROM stock_log
WHERE undone = 0
AND transaction_type = 'purchase'
GROUP BY product_id
) slg
ON p.id = slg.product_id
LEFT JOIN stock_log sl
ON slg.newest_id = sl.id;
DROP VIEW recipes_fulfillment;
CREATE VIEW recipes_fulfillment
AS
SELECT
r.id AS recipe_id,
rp.id AS recipe_pos_id,
rp.product_id AS product_id,
rp.amount * (r.desired_servings / r.base_servings) AS recipe_amount,
IFNULL(sc.amount, 0) AS stock_amount,
CASE WHEN IFNULL(sc.amount, 0) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE IFNULL(rp.amount, 0) * (r.desired_servings / r.base_servings) END THEN 1 ELSE 0 END AS need_fulfilled,
CASE WHEN IFNULL(sc.amount, 0) - CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE IFNULL(rp.amount, 0) * (r.desired_servings / r.base_servings) END < 0 THEN ABS(IFNULL(sc.amount, 0) - (CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE IFNULL(rp.amount, 0) * (r.desired_servings / r.base_servings) END)) ELSE 0 END AS missing_amount,
IFNULL(sl.amount, 0) * p.qu_factor_purchase_to_stock AS amount_on_shopping_list,
CASE WHEN IFNULL(sc.amount, 0) + (CASE WHEN r.not_check_shoppinglist = 1 THEN 0 ELSE IFNULL(sl.amount, 0) END * p.qu_factor_purchase_to_stock) >= CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE IFNULL(rp.amount, 0) * (r.desired_servings / r.base_servings) END THEN 1 ELSE 0 END AS need_fulfilled_with_shopping_list,
rp.qu_id,
(CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings / r.base_servings) END / p.qu_factor_purchase_to_stock) * pcp.last_price AS costs
FROM recipes r
JOIN recipes_pos rp
ON r.id = rp.recipe_id
JOIN products p
ON rp.product_id = p.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 products_current_price pcp
ON rp.product_id = pcp.product_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,
rp.amount * (r.desired_servings / r.base_servings) AS recipe_amount,
IFNULL(sc.amount, 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,
(CASE WHEN rp.only_check_single_unit_in_stock = 1 THEN 1 ELSE rp.amount * (r.desired_servings / r.base_servings) END / p.qu_factor_purchase_to_stock) * pcp.last_price AS costs
FROM recipes r
JOIN recipes_pos rp
ON r.id = rp.recipe_id
JOIN products p
ON rp.product_id = p.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 products_current_price pcp
ON rp.product_id = pcp.product_id
WHERE rp.not_check_stock_fulfillment = 1;

View File

@ -501,3 +501,8 @@ $("#about-dialog-link").on("click", function()
closeButton: false
});
});
$(".local-number-format[data-format='currency']").each(function ()
{
$(this).text(parseFloat($(this).text()).toLocaleString(undefined, { minimumFractionDigits: 2 }));
});

View File

@ -26,6 +26,12 @@ class StockService extends BaseService
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
}
public function GetCurrentProductPrices()
{
$sql = 'SELECT * FROM products_current_price';
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
}
public function GetMissingProducts()
{
$sql = 'SELECT * FROM stock_missing_products';

View File

@ -66,7 +66,7 @@
<div class="form-check">
<input type="hidden" name="not_check_shoppinglist" value="0">
<input @if($mode == 'edit' && $recipe->not_check_shoppinglist == 1) checked @endif class="form-check-input" type="checkbox" id="not_check_shoppinglist" name="not_check_shoppinglist" value="1">
<label class="form-check-label" for="not_check_shoppinglist">{{ $L('Do not check against the shopping list when adding missing items to it') }}
<label class="form-check-label" for="not_check_shoppinglist">{{ $L('Do not check against the shopping list when adding missing items to it') }}&nbsp;&nbsp;
<span class="small text-muted">{{ $L('By default the amount to be added to the shopping list is "needed amount - stock amount - shopping list amount". When this is enabled, it is only checked against the stock amount, not against what is already on the shopping list.') }}</span>
</label>
</div>

View File

@ -71,18 +71,28 @@
</a>
</div>
<div class="card-body">
<div class="card-body mb-0 pb-0">
<div class="row">
<div class="col-3">
@include('components.numberpicker', array(
'id' => 'servings-scale',
'label' => 'Servings',
'min' => 1,
'value' => $selectedRecipe->desired_servings,
'invalidFeedback' => $L('This cannot be lower than #1', '1'),
'additionalGroupCssClasses' => 'mb-0',
'additionalCssClasses' => 'col-2',
'additionalAttributes' => 'data-recipe-id="' . $selectedRecipe->id . '"'
))
</div>
<div class="col-9">
<label>{{ $L('Costs') }}&nbsp;&nbsp;
<span class="small text-muted">{{ $L('Based on the prices of the last purchase per product') }}</span>
</label>
<p class="font-weight-bold font-italic">
<span class="local-number-format" data-format="currency">{{ $totalRecipeCosts }}</span> {{ GROCY_CURRENCY }}
</p>
</div>
</div>
</div>
<!-- Subrecipes first -->
@foreach($selectedRecipeSubRecipes as $selectedRecipeSubRecipe)