More or less finalize recipes feature

This commit is contained in:
Bernd Bestel 2018-07-14 22:49:42 +02:00
parent d9246b9b42
commit 734814d96b
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
16 changed files with 171 additions and 24 deletions

View 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);
}
}

View File

@ -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;

View File

@ -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",

View File

@ -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);
}
);
});

View File

@ -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)
{

View File

@ -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);
}
);
}
}
});
});

View File

@ -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');

View File

@ -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;
}

View File

@ -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);

View File

@ -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();
}
}
}
}
}

View File

@ -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>

View File

@ -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)

View File

@ -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>

View File

@ -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

View File

@ -12,7 +12,7 @@
<a class="btn btn-outline-dark" href="{{ $U('/shoppinglistitem/new') }}">
<i class="fas fa-plus"></i>&nbsp;{{ $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>&nbsp;{{ $L('Add products that are below defined min. stock amount') }}
</a>
</h1>

View File

@ -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"