mirror of
https://github.com/grocy/grocy.git
synced 2025-04-28 17:23:56 +00:00
More or less finalize recipes feature
This commit is contained in:
parent
d9246b9b42
commit
734814d96b
22
controllers/RecipesApiController.php
Normal file
22
controllers/RecipesApiController.php
Normal file
@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace Grocy\Controllers;
|
||||
|
||||
use \Grocy\Services\RecipesService;
|
||||
|
||||
class RecipesApiController extends BaseApiController
|
||||
{
|
||||
public function __construct(\Slim\Container $container)
|
||||
{
|
||||
parent::__construct($container);
|
||||
$this->RecipesService = new RecipesService();
|
||||
}
|
||||
|
||||
protected $RecipesService;
|
||||
|
||||
public function AddNotFulfilledProductsToShoppingList(\Slim\Http\Request $request, \Slim\Http\Response $response, array $args)
|
||||
{
|
||||
$this->RecipesService->AddNotFulfilledProductsToShoppingList($args['recipeId']);
|
||||
return $this->VoidApiActionResponse($response);
|
||||
}
|
||||
}
|
@ -19,20 +19,32 @@ AS
|
||||
SELECT
|
||||
r.id AS recipe_id,
|
||||
rp.id AS recipe_pos_id,
|
||||
rp.product_id,
|
||||
rp.product_id AS product_id,
|
||||
rp.amount AS recipe_amount,
|
||||
sc.amount AS stock_amount,
|
||||
CASE WHEN sc.amount >= rp.amount THEN 1 ELSE 0 END AS need_fullfiled
|
||||
CASE WHEN IFNULL(sc.amount, 0) >= rp.amount THEN 1 ELSE 0 END AS need_fulfilled,
|
||||
CASE WHEN IFNULL(sc.amount, 0) - rp.amount < 0 THEN ABS(sc.amount - rp.amount) ELSE 0 END AS missing_amount,
|
||||
IFNULL(sl.amount, 0) AS amount_on_shopping_list,
|
||||
CASE WHEN IFNULL(sc.amount, 0) + IFNULL(sl.amount, 0) >= rp.amount THEN 1 ELSE 0 END AS need_fulfilled_with_shopping_list
|
||||
FROM recipes r
|
||||
LEFT JOIN recipes_pos rp
|
||||
JOIN recipes_pos rp
|
||||
ON r.id = rp.recipe_id
|
||||
LEFT JOIN (
|
||||
SELECT product_id, SUM(amount + amount_autoadded) 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;
|
||||
|
||||
CREATE VIEW recipes_fulfillment_sum
|
||||
AS
|
||||
SELECT
|
||||
rf.recipe_id,
|
||||
MIN(rf.need_fullfiled) AS need_fullfiled
|
||||
FROM recipes_fulfillment rf
|
||||
GROUP BY rf.recipe_id;
|
||||
r.id AS recipe_id,
|
||||
IFNULL(MIN(rf.need_fulfilled), 1) AS need_fulfilled,
|
||||
IFNULL(MIN(rf.need_fulfilled_with_shopping_list), 1) AS need_fulfilled_with_shopping_list,
|
||||
(SELECT COUNT(*) FROM recipes_fulfillment WHERE recipe_id = rf.recipe_id AND need_fulfilled = 0 AND recipe_pos_id IS NOT NULL) AS missing_products_count
|
||||
FROM recipes r
|
||||
LEFT JOIN recipes_fulfillment rf
|
||||
ON rf.recipe_id = r.id
|
||||
GROUP BY r.id;
|
||||
|
@ -14,6 +14,8 @@
|
||||
"datatables.net-responsive-bs4": "^2.2.3",
|
||||
"datatables.net-colreorder": "^1.5.1",
|
||||
"datatables.net-colreorder-bs4": "^1.5.1",
|
||||
"datatables.net-select": "^1.2.7",
|
||||
"datatables.net-select-bs4": "^1.2.7",
|
||||
"jquery": "^3.3.1",
|
||||
"jquery-serializejson": "^2.8.1",
|
||||
"jquery-ui-dist": "^1.12.1",
|
||||
|
@ -96,3 +96,27 @@ $(document).on('click', '.recipe-pos-delete-button', function(e)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.recipe-pos-order-missing-button', function(e)
|
||||
{
|
||||
var productName = $(e.currentTarget).attr('data-product-name');
|
||||
var productId = $(e.currentTarget).attr('data-product-id');
|
||||
var productAmount = $(e.currentTarget).attr('data-product-amount');
|
||||
var recipeName = $(e.currentTarget).attr('data-recipe-name');
|
||||
|
||||
var jsonData = {};
|
||||
jsonData.product_id = productId;
|
||||
jsonData.amount = productAmount;
|
||||
jsonData.note = L('Added for recipe #1', recipeName);
|
||||
|
||||
Grocy.Api.Post('add-object/shopping_list', jsonData,
|
||||
function(result)
|
||||
{
|
||||
window.location.href = U('/recipe/' + Grocy.EditObjectId);
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -46,6 +46,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
||||
{
|
||||
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
|
||||
$('#amount').focus();
|
||||
Grocy.FrontendHelpers.ValidateForm('recipe-pos-form');
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
|
@ -7,7 +7,8 @@
|
||||
'language': JSON.parse(L('datatables_localization')),
|
||||
'scrollY': false,
|
||||
'colReorder': true,
|
||||
'stateSave': true
|
||||
'stateSave': true,
|
||||
'select': 'single'
|
||||
});
|
||||
|
||||
$("#search").on("keyup", function()
|
||||
@ -56,3 +57,39 @@ $(document).on('click', '.recipe-delete-button', function(e)
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.recipe-order-missing-button', function(e)
|
||||
{
|
||||
var objectName = $(e.currentTarget).attr('data-recipe-name');
|
||||
var objectId = $(e.currentTarget).attr('data-recipe-id');
|
||||
|
||||
bootbox.confirm({
|
||||
message: L('Are you sure to put all missing ingredients for recipe "#1" on the shopping list?', objectName),
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: L('Yes'),
|
||||
className: 'btn-success'
|
||||
},
|
||||
cancel: {
|
||||
label: L('No'),
|
||||
className: 'btn-danger'
|
||||
}
|
||||
},
|
||||
callback: function(result)
|
||||
{
|
||||
if (result === true)
|
||||
{
|
||||
Grocy.Api.Get('recipes/add-not-fulfilled-products-to-shopping-list/' + objectId,
|
||||
function(result)
|
||||
{
|
||||
window.location.href = U('/recipes');
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -77,6 +77,8 @@ $app->group('/api', function()
|
||||
$this->get('/stock/add-missing-products-to-shoppinglist', 'Grocy\Controllers\StockApiController:AddMissingProductsToShoppingList');
|
||||
$this->get('/stock/external-barcode-lookup/{barcode}', 'Grocy\Controllers\StockApiController:ExternalBarcodeLookup');
|
||||
|
||||
$this->get('/recipes/add-not-fulfilled-products-to-shopping-list/{recipeId}', 'Grocy\Controllers\RecipesApiController:AddNotFulfilledProductsToShoppingList');
|
||||
|
||||
$this->get('/habits/track-habit-execution/{habitId}', 'Grocy\Controllers\HabitsApiController:TrackHabitExecution');
|
||||
$this->get('/habits/get-habit-details/{habitId}', 'Grocy\Controllers\HabitsApiController:HabitDetails');
|
||||
|
||||
|
@ -3,14 +3,19 @@
|
||||
namespace Grocy\Services;
|
||||
|
||||
use \Grocy\Services\DatabaseService;
|
||||
use \Grocy\Services\LocalizationService;
|
||||
|
||||
class BaseService
|
||||
{
|
||||
public function __construct() {
|
||||
$this->DatabaseService = new DatabaseService();
|
||||
$this->Database = $this->DatabaseService->GetDbConnection();
|
||||
|
||||
$localizationService = new LocalizationService(CULTURE);
|
||||
$this->LocalizationService = $localizationService;
|
||||
}
|
||||
|
||||
protected $DatabaseService;
|
||||
protected $Database;
|
||||
protected $LocalizationService;
|
||||
}
|
||||
|
@ -2,14 +2,12 @@
|
||||
|
||||
namespace Grocy\Services;
|
||||
|
||||
class LocalizationService extends BaseService
|
||||
class LocalizationService
|
||||
{
|
||||
const DEFAULT_CULTURE = 'en';
|
||||
|
||||
public function __construct(string $culture)
|
||||
{
|
||||
parent::__construct();
|
||||
|
||||
$this->Culture = $culture;
|
||||
|
||||
$this->StringsDefaultCulture = $this->LoadLocalizationFile(self::DEFAULT_CULTURE);
|
||||
|
@ -15,4 +15,27 @@ class RecipesService extends BaseService
|
||||
$sql = 'SELECT * from recipes_fulfillment_sum';
|
||||
return $this->DatabaseService->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
|
||||
}
|
||||
|
||||
public function AddNotFulfilledProductsToShoppingList($recipeId)
|
||||
{
|
||||
$recipe = $this->Database->recipes($recipeId);
|
||||
|
||||
$recipePositions = $this->GetRecipesFulfillment();
|
||||
foreach ($recipePositions as $recipePosition)
|
||||
{
|
||||
if($recipePosition->recipe_id == $recipeId)
|
||||
{
|
||||
$toOrderAmount = $recipePosition->missing_amount - $recipePosition->amount_on_shopping_list;
|
||||
if($toOrderAmount > 0)
|
||||
{
|
||||
$shoppinglistRow = $this->Database->shopping_list()->createRow(array(
|
||||
'product_id' => $recipePosition->product_id,
|
||||
'amount' => $toOrderAmount,
|
||||
'note' => $this->LocalizationService->Localize('Added for recipe #1', $recipe->name)
|
||||
));
|
||||
$shoppinglistRow->save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
<link href="{{ $U('/node_modules/datatables.net-bs4/css/dataTables.bootstrap4.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||
<link href="{{ $U('/node_modules/datatables.net-responsive-bs4/css/responsive.bootstrap4.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||
<link href="{{ $U('/node_modules/datatables.net-colreorder-bs4/css/colReorder.bootstrap4.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||
<link href="{{ $U('/node_modules/datatables.net-select-bs4/css/select.bootstrap4.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||
<link href="{{ $U('/node_modules/toastr/build/toastr.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||
<link href="{{ $U('/node_modules/tagmanager/tagmanager.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||
<link href="{{ $U('/node_modules/tempusdominus-bootstrap-4/build/css/tempusdominus-bootstrap-4.min.css?v=', true) }}{{ $version }}" rel="stylesheet">
|
||||
@ -90,7 +91,7 @@
|
||||
</li>
|
||||
<li class="nav-item" data-toggle="tooltip" data-placement="right" title="{{ $L('Shopping list') }}" data-nav-for-page="shoppinglist">
|
||||
<a class="nav-link discrete-link" href="{{ $U('/shoppinglist') }}">
|
||||
<i class="fas fa-shopping-bag"></i>
|
||||
<i class="fas fa-shopping-cart"></i>
|
||||
<span class="nav-link-text">{{ $L('Shopping list') }}</span>
|
||||
</a>
|
||||
</li>
|
||||
@ -236,6 +237,8 @@
|
||||
<script src="{{ $U('/node_modules/datatables.net-responsive-bs4/js/responsive.bootstrap4.min.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules/datatables.net-colreorder/js/dataTables.colReorder.min.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules/datatables.net-colreorder-bs4/js/colReorder.bootstrap4.min.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules/datatables.net-select/js/dataTables.select.min.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules/datatables.net-select-bs4/js/select.bootstrap4.min.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules/timeago/jquery.timeago.js?v=', true) }}{{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules', true) }}/timeago/locales/jquery.timeago.{{ $L('timeago_locale') }}.js?v={{ $version }}"></script>
|
||||
<script src="{{ $U('/node_modules/toastr/build/toastr.min.js?v=', true) }}{{ $version }}"></script>
|
||||
|
@ -22,7 +22,7 @@
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-6 pb-3">
|
||||
<div class="col-xs-12 col-md-4 pb-3">
|
||||
<form id="recipe-form" novalidate>
|
||||
|
||||
<div class="form-group">
|
||||
@ -41,7 +41,7 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-md-6 pb-3">
|
||||
<div class="col-xs-12 col-md-8 pb-3">
|
||||
<h2>
|
||||
{{ $L('Ingredients list') }}
|
||||
<a class="btn btn-outline-dark" href="{{ $U('/recipe/' . $recipe->id . '/pos/new') }}">
|
||||
@ -60,7 +60,7 @@
|
||||
<tbody>
|
||||
@if($mode == "edit")
|
||||
@foreach($recipePositions as $recipePosition)
|
||||
<tr class="@if(FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->need_fullfiled == 1) table-success @endif">
|
||||
<tr class="@if(FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->need_fulfilled == 1) table-success @elseif(FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->need_fulfilled_with_shopping_list == 1) table-warning @else table-danger @endif">
|
||||
<td class="fit-content">
|
||||
<a class="btn btn-sm btn-info" href="{{ $U('/recipe/' . $recipe->id . '/pos/' . $recipePosition->id) }}">
|
||||
<i class="fas fa-edit"></i>
|
||||
@ -68,13 +68,16 @@
|
||||
<a class="btn btn-sm btn-danger recipe-pos-delete-button" href="#" data-recipe-pos-id="{{ $recipePosition->id }}" data-recipe-pos-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
<a class="btn btn-sm btn-primary recipe-pos-order-missing-button @if(FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->need_fulfilled_with_shopping_list == 1){{ disabled }}@endif" href="#" title="{{ $L('Order missing amount') }}" data-recipe-name="{{ $recipe->name }}" data-product-id="{{ $recipePosition->product_id }}" data-product-amount="{{ FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->missing_amount }}" data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }}">
|
||||
<i class="fas fa-shopping-cart"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->name }}
|
||||
</td>
|
||||
<td>
|
||||
{{ $recipePosition->amount }} {{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $recipePosition->product_id)->qu_id_stock)->name }}
|
||||
<span class="timeago-contextual">@if(FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->need_fullfiled == 1) {{ $L('Enough in stock') }} @else {{ $L('Not enough in stock') }} @endif</span>
|
||||
<span class="timeago-contextual">@if(FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->need_fulfilled == 1) {{ $L('Enough in stock') }} @else {{ $L('Not enough in stock, #1 missing, #2 already on shopping list', FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->missing_amount, FindObjectInArrayByPropertyValue($recipesFulfillment, 'recipe_pos_id', $recipePosition->id)->amount_on_shopping_list) }} @endif</span>
|
||||
</td>
|
||||
<td>
|
||||
@if(strlen($recipePosition->note) > 50)
|
||||
|
@ -10,7 +10,7 @@
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-md-6 col-xl-4 pb-3">
|
||||
<div class="col-xs-12 col-md-6 col-xl-5 pb-3">
|
||||
<h1>@yield('title')</h1>
|
||||
<h3 class="text-muted">{{ $L('Recipe') }} <strong>{{ $recipe->name }}</strong></h3>
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach($recipes as $recipe)
|
||||
<tr class="@if(FindObjectInArrayByPropertyValue($recipesSumFulfillment, 'recipe_id', $recipe->id)->need_fullfiled == 1) table-success @endif">
|
||||
<tr>
|
||||
<td class="fit-content">
|
||||
<a class="btn btn-sm btn-info" href="{{ $U('/recipe/') }}{{ $recipe->id }}">
|
||||
<i class="fas fa-edit"></i>
|
||||
@ -43,12 +43,16 @@
|
||||
<a class="btn btn-sm btn-danger recipe-delete-button" href="#" data-recipe-id="{{ $recipe->id }}" data-recipe-name="{{ $recipe->name }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
<a class="btn btn-sm btn-primary recipe-order-missing-button @if(FindObjectInArrayByPropertyValue($recipesSumFulfillment, 'recipe_id', $recipe->id)->need_fulfilled_with_shopping_list == 1){{ disabled }}@endif" href="#" title="{{ $L('Order missing products') }}" data-recipe-id="{{ $recipe->id }}" data-recipe-name="{{ $recipe->name }}">
|
||||
<i class="fas fa-shopping-cart"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ $recipe->name }}
|
||||
</td>
|
||||
<td>
|
||||
@if(FindObjectInArrayByPropertyValue($recipesSumFulfillment, 'recipe_id', $recipe->id)->need_fullfiled == 1){{ $L('Yes') }}@else{{ $L('No') }}@endif
|
||||
@if(FindObjectInArrayByPropertyValue($recipesSumFulfillment, 'recipe_id', $recipe->id)->need_fulfilled == 1){{ $L('Yes') }}@else{{ $L('No') }}@endif
|
||||
<span class="timeago-contextual">@if(FindObjectInArrayByPropertyValue($recipesSumFulfillment, 'recipe_id', $recipe->id)->need_fulfilled == 1) {{ $L('Enough in stock') }} @else {{ $L('Not enough in stock, #1 ingredients missing', FindObjectInArrayByPropertyValue($recipesSumFulfillment, 'recipe_id', $recipe->id)->missing_products_count) }} @endif</span>
|
||||
</td>
|
||||
</tr>
|
||||
@endforeach
|
||||
|
@ -12,7 +12,7 @@
|
||||
<a class="btn btn-outline-dark" href="{{ $U('/shoppinglistitem/new') }}">
|
||||
<i class="fas fa-plus"></i> {{ $L('Add') }}
|
||||
</a>
|
||||
<a id="add-products-below-min-stock-amount" class="btn btn-info" href="#">
|
||||
<a id="add-products-below-min-stock-amount" class="btn btn-outline-primary" href="#">
|
||||
<i class="fas fa-plus"></i> {{ $L('Add products that are below defined min. stock amount') }}
|
||||
</a>
|
||||
</h1>
|
||||
|
19
yarn.lock
19
yarn.lock
@ -104,6 +104,21 @@ datatables.net-responsive@2.2.3, datatables.net-responsive@^2.2.3:
|
||||
datatables.net "^1.10.15"
|
||||
jquery ">=1.7"
|
||||
|
||||
datatables.net-select-bs4@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net-select-bs4/-/datatables.net-select-bs4-1.2.7.tgz#5e4ddd8feb412e974b54a15e81b2bb29840ba55b"
|
||||
dependencies:
|
||||
datatables.net-bs4 "^1.10.15"
|
||||
datatables.net-select "1.2.7"
|
||||
jquery ">=1.7"
|
||||
|
||||
datatables.net-select@1.2.7, datatables.net-select@^1.2.7:
|
||||
version "1.2.7"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net-select/-/datatables.net-select-1.2.7.tgz#7d5badfca49c438f8b51df04483d8d77857e917c"
|
||||
dependencies:
|
||||
datatables.net "^1.10.15"
|
||||
jquery ">=1.7"
|
||||
|
||||
datatables.net@1.10.16:
|
||||
version "1.10.16"
|
||||
resolved "https://registry.yarnpkg.com/datatables.net/-/datatables.net-1.10.16.tgz#4b052d1082824261b68eed9d22741b711d3d2469"
|
||||
@ -169,10 +184,6 @@ startbootstrap-sb-admin@^4.0.0:
|
||||
jquery "3.3.1"
|
||||
jquery.easing "^1.4.1"
|
||||
|
||||
summernote@^0.8.10:
|
||||
version "0.8.10"
|
||||
resolved "https://registry.yarnpkg.com/summernote/-/summernote-0.8.10.tgz#21a5d7f18a3b07500b58b60d5907417a54897520"
|
||||
|
||||
swagger-ui-dist@^3.17.3:
|
||||
version "3.17.3"
|
||||
resolved "https://registry.yarnpkg.com/swagger-ui-dist/-/swagger-ui-dist-3.17.3.tgz#dfb96408ccc46775155f7369190c5d4b2016fe5c"
|
||||
|
Loading…
x
Reference in New Issue
Block a user