Implemented stock sub-feature-flags (closes #314)

This commit is contained in:
Bernd Bestel 2019-09-19 17:46:52 +02:00
parent 5e9a7fb7ca
commit cbf1d1ca40
No known key found for this signature in database
GPG Key ID: 71BD34C0D4891300
16 changed files with 164 additions and 50 deletions

View File

@ -7,7 +7,7 @@
- Available on any barcode-enabled field (so currently only for picking products) - a new camera button at the right of side the text field
- Implemented using [QuaggaJS](https://github.com/serratus/quaggaJS) - camera stream processing happens totally offline / client-side
- Please note due to browser security restrictions, this only works when serving grocy via a secure connection (`https://`)
- There is also a `config.php` setting `DISABLE_BROWSER_BARCODE_CAMERA_SCANNING` to disable this, if you don't need it at all
- There is also a `config.php` setting `DISABLE_BROWSER_BARCODE_CAMERA_SCANNING` to disable this, if you don't need it at all (defaults to `false`)
### Stock improvements
- Products can now have variations (nested products)
@ -21,6 +21,11 @@
- It's now possible to print a "Location Content Sheet" with the current stock per location - new button at the top of the stock overview page (thought to hang it at the location, note used amounts on paper and track it in grocy later)
- The product description now can have formattings (HTML/WYSIWYG editor like for recipes)
- "Factor purchase to stock quantity unit" (product option) can now also be a decimal number when "Allow partial units in stock" is enabled
- New "Sub feature flags" in `config.php` to disable some sub-features (hide the corresponding UI elements) if you don't need them (all new feature flags default to `true`, so no changed behaviour when not configured)
- `FEATURE_FLAG_STOCK_PRICE_TRACKING` to disable product price tracking
- `FEATURE_FLAG_STOCK_LOCATION_TRACKING` to disable product location tracking
- `FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING` to disable product best before date tracking
- `FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING` to disable product opened tracking
### Recipe improvements
- Based on the new linked quantity units, recipe ingredients can now use any product related unit, the amount is calculated according to the cnoversion factor of the unit relation

View File

@ -17,8 +17,8 @@
# Either "production", "dev" or "prerelease"
Setting('MODE', 'production');
# Either "en" or "de" or the filename (without extension) of
# one of the other available localization files in the "/localization" directory
# Either "en" or "de" or the directory name of
# one of the other available localization folders in the "/localization" directory
Setting('CULTURE', 'en');
# This is used to define the first day of a week for calendar views in the frontend,
@ -45,12 +45,11 @@ Setting('BASE_URL', '/');
# see /data/plugins/DemoBarcodeLookupPlugin.php for an example implementation
Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
# If, however, your webserver does not support URL rewriting,
# set this to true
# If, however, your webserver does not support URL rewriting, set this to true
Setting('DISABLE_URL_REWRITING', false);
# Specify an custom homepage if desired. By default the homepage will be set to the stock overview.
# You can chosen one of the following values:
# Specify an custom homepage if desired - by default the homepage will be set to the stock overview,
# this needs to be one of the following values:
# stock, shoppinglist, recipes, chores, tasks, batteries, equipment, calendar
Setting('ENTRY_PAGE', 'stock');
@ -61,6 +60,7 @@ Setting('DISABLE_AUTH', false);
# Set this to true if you want to disable the ability to scan a barcode via the device camera (Browser API)
Setting('DISABLE_BROWSER_BARCODE_CAMERA_SCANNING', false);
# Default user settings
# These settings can be changed per user, here the defaults
# are defined which are used when the user has not changed the setting so far
@ -103,8 +103,6 @@ DefaultUserSetting('show_clock_in_header', false);
DefaultUserSetting('shopping_list_to_stock_workflow_auto_submit_when_prefilled', false);
# Feature flags
# grocy was initially about "stock management for your household", many other things
# came and still come by, because they are useful - here you can disable the parts
@ -118,3 +116,9 @@ Setting('FEATURE_FLAG_TASKS', true);
Setting('FEATURE_FLAG_BATTERIES', true);
Setting('FEATURE_FLAG_EQUIPMENT', true);
Setting('FEATURE_FLAG_CALENDAR', true);
# Sub feature flags
Setting('FEATURE_FLAG_STOCK_PRICE_TRACKING', true);
Setting('FEATURE_FLAG_STOCK_LOCATION_TRACKING', true);
Setting('FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING', true);
Setting('FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING', true);

View File

@ -100,35 +100,38 @@ Grocy.Components.ProductCard.Refresh = function(productId)
}
);
Grocy.Api.Get('stock/products/' + productId + '/price-history',
function(priceHistoryDataPoints)
{
if (priceHistoryDataPoints.length > 0)
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{
Grocy.Api.Get('stock/products/' + productId + '/price-history',
function(priceHistoryDataPoints)
{
$("#productcard-product-price-history-chart").removeClass("d-none");
$("#productcard-no-price-data-hint").addClass("d-none");
Grocy.Components.ProductCard.ReInitPriceHistoryChart();
priceHistoryDataPoints.forEach((dataPoint) =>
if (priceHistoryDataPoints.length > 0)
{
Grocy.Components.ProductCard.PriceHistoryChart.data.labels.push(moment(dataPoint.date).toDate());
$("#productcard-product-price-history-chart").removeClass("d-none");
$("#productcard-no-price-data-hint").addClass("d-none");
var dataset = Grocy.Components.ProductCard.PriceHistoryChart.data.datasets[0];
dataset.data.push(dataPoint.price);
});
Grocy.Components.ProductCard.PriceHistoryChart.update();
}
else
Grocy.Components.ProductCard.ReInitPriceHistoryChart();
priceHistoryDataPoints.forEach((dataPoint) =>
{
Grocy.Components.ProductCard.PriceHistoryChart.data.labels.push(moment(dataPoint.date).toDate());
var dataset = Grocy.Components.ProductCard.PriceHistoryChart.data.datasets[0];
dataset.data.push(dataPoint.price);
});
Grocy.Components.ProductCard.PriceHistoryChart.update();
}
else
{
$("#productcard-product-price-history-chart").addClass("d-none");
$("#productcard-no-price-data-hint").removeClass("d-none");
}
},
function(xhr)
{
$("#productcard-product-price-history-chart").addClass("d-none");
$("#productcard-no-price-data-hint").removeClass("d-none");
console.error(xhr);
}
},
function(xhr)
{
console.error(xhr);
}
);
);
}
};
Grocy.Components.ProductCard.ReInitPriceHistoryChart = function()

View File

@ -17,7 +17,14 @@
var jsonData = { };
jsonData.new_amount = jsonForm.new_amount;
jsonData.best_before_date = Grocy.Components.DateTimePicker.GetValue();
jsonData.location_id = Grocy.Components.LocationPicker.GetValue();
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
jsonData.location_id = Grocy.Components.LocationPicker.GetValue();
}
else
{
jsonData.location_id = 1;
}
jsonData.price = price;
Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/inventory', jsonData,
@ -120,7 +127,10 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
}
$('#price').val(productDetails.last_price);
Grocy.Components.LocationPicker.SetId(productDetails.location.id);
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
Grocy.Components.LocationPicker.SetId(productDetails.location.id);
}
$('#new_amount').focus();
},
function(xhr)
@ -221,13 +231,24 @@ $('#new_amount').on('keyup', function(e)
{
$('#inventory-change-info').text(__t('This means %s will be added to stock', estimatedBookingAmount.toLocaleString() + ' ' + __n(estimatedBookingAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural)));
Grocy.Components.DateTimePicker.GetInputElement().attr('required', '');
Grocy.Components.LocationPicker.GetInputElement().attr('required', '');
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
Grocy.Components.LocationPicker.GetInputElement().attr('required', '');
}
}
else if (newAmount < productStockAmount + containerWeight)
{
$('#inventory-change-info').text(__t('This means %s will be removed from stock', estimatedBookingAmount.toLocaleString() + ' ' + __n(estimatedBookingAmount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural)));
Grocy.Components.DateTimePicker.GetInputElement().removeAttr('required');
Grocy.Components.LocationPicker.GetInputElement().removeAttr('required');
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
Grocy.Components.LocationPicker.GetInputElement().removeAttr('required');
}
}
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
Grocy.Components.DateTimePicker.GetInputElement().removeAttr('required');
}
Grocy.FrontendHelpers.ValidateForm('inventory-form');

View File

@ -20,7 +20,14 @@
jsonData.amount = amount;
jsonData.best_before_date = Grocy.Components.DateTimePicker.GetValue();
jsonData.price = price;
jsonData.location_id = Grocy.Components.LocationPicker.GetValue();
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
jsonData.location_id = Grocy.Components.LocationPicker.GetValue();
}
else
{
jsonData.location_id = 1;
}
Grocy.Api.Post('stock/products/' + jsonForm.product_id + '/add', jsonData,
function(result)
@ -73,7 +80,10 @@
$('#price').val('');
$('#amount_qu_unit').text("");
$("#tare-weight-handling-info").addClass("d-none");
Grocy.Components.LocationPicker.Clear();
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
Grocy.Components.LocationPicker.Clear();
}
Grocy.Components.DateTimePicker.Clear();
Grocy.Components.ProductPicker.SetValue('');
Grocy.Components.ProductPicker.GetInputElement().focus();
@ -107,7 +117,10 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
function (productDetails)
{
$('#price').val(productDetails.last_price);
Grocy.Components.LocationPicker.SetId(productDetails.location.id);
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
{
Grocy.Components.LocationPicker.SetId(productDetails.location.id);
}
if (productDetails.product.qu_id_purchase === productDetails.product.qu_id_stock)
{
@ -166,7 +179,15 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
}
else
{
Grocy.Components.DateTimePicker.GetInputElement().focus();
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
Grocy.Components.DateTimePicker.GetInputElement().focus();
}
else
{
Grocy.Components.DateTimePicker.SetValue(moment().format('YYYY-MM-DD'));
$('#amount').focus();
}
}
},
function(xhr)

View File

@ -13,8 +13,9 @@
@php if(!isset($noNameAttribute)) { $noNameAttribute = false; } @endphp
@php if(!isset($nextInputSelector)) { $nextInputSelector = false; } @endphp
@php if(empty($additionalAttributes)) { $additionalAttributes = ''; } @endphp
@php if(empty($additionalGroupCssClasses)) { $additionalGroupCssClasses = ''; } @endphp
<div class="form-group">
<div class="form-group {{ $additionalGroupCssClasses }}">
<label for="{{ $id }}">{{ $__t($label) }}
<span class="small text-muted">
@if(!empty($hint)){{ $__t($hint) }}@endif

View File

@ -20,19 +20,21 @@
<strong>{{ $__t('Stock amount') . ' / ' . $__t('Quantity unit') }}:</strong> <span id="productcard-product-stock-amount"></span> <span id="productcard-product-stock-qu-name"></span> <span id="productcard-product-stock-opened-amount" class="small font-italic"></span>
<span id="productcard-aggregated-amounts" class="pl-2 text-secondary d-none"><i class="fas fa-custom-sigma-sign"></i> <span id="productcard-product-stock-amount-aggregated"></span> <span id="productcard-product-stock-qu-name-aggregated"></span> <span id="productcard-product-stock-opened-amount-aggregated" class="small font-italic"></span></span><br>
<strong>{{ $__t('Location') }}:</strong> <span id="productcard-product-location"></span><br>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)<strong>{{ $__t('Location') }}:</strong> <span id="productcard-product-location"></span><br>@endif
<strong>{{ $__t('Last purchased') }}:</strong> <span id="productcard-product-last-purchased"></span> <time id="productcard-product-last-purchased-timeago" class="timeago timeago-contextual"></time><br>
<strong>{{ $__t('Last used') }}:</strong> <span id="productcard-product-last-used"></span> <time id="productcard-product-last-used-timeago" class="timeago timeago-contextual"></time><br>
<strong>{{ $__t('Last price') }}:</strong> <span id="productcard-product-last-price"></span><br>
<strong>{{ $__t('Average shelf life') }}:</strong> <span id="productcard-product-average-shelf-life"></span><br>
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)<strong>{{ $__t('Last price') }}:</strong> <span id="productcard-product-last-price"></span><br>@endif
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)<strong>{{ $__t('Average shelf life') }}:</strong> <span id="productcard-product-average-shelf-life"></span><br>@endif
<strong>{{ $__t('Spoil rate') }}:</strong> <span id="productcard-product-spoil-rate"></span>
<h5 class="mt-3">{{ $__t('Product picture') }}</h5>
<p class="w-75 mx-auto"><img id="productcard-product-picture" data-src="" class="img-fluid img-thumbnail d-none lazy"></p>
<span id="productcard-no-product-picture" class="font-italic d-none">{{ $__t('No picture available') }}</span>
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
<h5 class="mt-3">{{ $__t('Price history') }}</h5>
<canvas id="productcard-product-price-history-chart" class="w-100 d-none"></canvas>
<span id="productcard-no-price-data-hint" class="font-italic d-none">{{ $__t('No price history available') }}</span>
@endif
</div>
</div>

View File

@ -52,7 +52,10 @@
@endif
<button id="save-consume-button" class="btn btn-success">{{ $__t('OK') }}</button>
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
<button id="save-mark-as-open-button" class="btn btn-secondary">{{ $__t('Mark as opened') }}</button>
@endif
</form>
</div>

View File

@ -28,6 +28,13 @@
'additionalHtmlContextHelp' => '<div id="tare-weight-handling-info" class="text-small text-info font-italic d-none">' . $__t('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '</div>'
))
@php
$additionalGroupCssClasses = '';
if (!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
$additionalGroupCssClasses = 'd-none';
}
@endphp
@include('components.datetimepicker', array(
'id' => 'best_before_date',
'label' => 'Best before',
@ -42,9 +49,12 @@
'shortcutValue' => '2999-12-31',
'shortcutLabel' => 'Never expires',
'earlierThanInfoLimit' => date('Y-m-d'),
'earlierThanInfoText' => $__t('The given date is earlier than today, are you sure?')
'earlierThanInfoText' => $__t('The given date is earlier than today, are you sure?'),
'additionalGroupCssClasses' => $additionalGroupCssClasses
))
@php $additionalGroupCssClasses = ''; @endphp
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
@include('components.numberpicker', array(
'id' => 'price',
'label' => 'Price',
@ -56,11 +66,18 @@
'invalidFeedback' => $__t('The price cannot be lower than %s', '0'),
'isRequired' => false
))
@else
<input type="hidden" name="price" id="price" value="0">
@endif
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
@include('components.locationpicker', array(
'locations' => $locations,
'hint' => $__t('This will apply to added products')
))
@else
<input type="hidden" name="location_id" id="location_id" value="1">
@endif
<button id="save-inventory-button" class="btn btn-success">{{ $__t('OK') }}</button>

View File

@ -212,12 +212,14 @@
<span class="nav-link-text">{{ $__t('Products') }}</span>
</a>
</li>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<li data-nav-for-page="locations" data-sub-menu-of="#top-nav-manager-master-data">
<a class="nav-link discrete-link" href="{{ $U('/locations') }}">
<i class="fas fa-map-marker-alt"></i>
<span class="nav-link-text">{{ $__t('Locations') }}</span>
</a>
</li>
@endif
<li data-nav-for-page="quantityunits" data-sub-menu-of="#top-nav-manager-master-data">
<a class="nav-link discrete-link" href="{{ $U('/quantityunits') }}">
<i class="fas fa-balance-scale"></i>

View File

@ -73,6 +73,7 @@
<div id="barcode-taginput-container"></div>
</div>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<div class="form-group">
<label for="location_id">{{ $__t('Location') }}</label>
<select required class="form-control" id="location_id" name="location_id">
@ -83,6 +84,9 @@
</select>
<div class="invalid-feedback">{{ $__t('A location is required') }}</div>
</div>
@else
<input type="hidden" name="location_id" id="location_id" value="1">
@endif
@php if($mode == 'edit') { $value = $product->min_stock_amount; } else { $value = 0; } @endphp
@include('components.numberpicker', array(

View File

@ -45,7 +45,7 @@
<tr>
<th class="border-right"></th>
<th>{{ $__t('Name') }}</th>
<th>{{ $__t('Location') }}</th>
<th class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">{{ $__t('Location') }}</th>
<th>{{ $__t('Min. stock amount') }}</th>
<th>{{ $__t('QU purchase') }}</th>
<th>{{ $__t('QU stock') }}</th>
@ -73,7 +73,7 @@
<td>
{{ $product->name }}@if(!empty($product->picture_file_name)) <i class="fas fa-image text-muted"></i>@endif
</td>
<td>
<td class="@if(!GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING) d-none @endif">
{{ FindObjectInArrayByPropertyValue($locations, 'id', $product->location_id)->name }}
</td>
<td>

View File

@ -16,6 +16,13 @@
'nextInputSelector' => '#best_before_date .datetimepicker-input'
))
@php
$additionalGroupCssClasses = '';
if (!GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
$additionalGroupCssClasses = 'd-none';
}
@endphp
@include('components.datetimepicker', array(
'id' => 'best_before_date',
'label' => 'Best before',
@ -29,8 +36,10 @@
'shortcutValue' => '2999-12-31',
'shortcutLabel' => 'Never expires',
'earlierThanInfoLimit' => date('Y-m-d'),
'earlierThanInfoText' => $__t('The given date is earlier than today, are you sure?')
'earlierThanInfoText' => $__t('The given date is earlier than today, are you sure?'),
'additionalGroupCssClasses' => $additionalGroupCssClasses
))
@php $additionalGroupCssClasses = ''; @endphp
@include('components.numberpicker', array(
'id' => 'amount',
@ -41,6 +50,7 @@
'additionalHtmlContextHelp' => '<div id="tare-weight-handling-info" class="text-info font-italic d-none">' . $__t('Tare weight handling enabled - please weigh the whole container, the amount to be posted will be automatically calculcated') . '</div>'
))
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
@include('components.numberpicker', array(
'id' => 'price',
'label' => 'Price',
@ -51,11 +61,18 @@
'invalidFeedback' => $__t('The price cannot be lower than %s', '0'),
'isRequired' => false
))
@else
<input type="hidden" name="price" id="price" value="0">
@endif
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
@include('components.locationpicker', array(
'locations' => $locations,
'isRequired' => false
))
@else
<input type="hidden" name="location_id" id="location_id" value="1">
@endif
<button id="save-purchase-button" class="btn btn-success">{{ $__t('OK') }}</button>

View File

@ -155,6 +155,7 @@
'additionalAttributes' => 'data-recipe-id="' . $selectedRecipe->id . '"'
))
</div>
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
<div class="col-7">
<label>{{ $__t('Costs') }}&nbsp;&nbsp;
<span class="small text-muted">{{ $__t('Based on the prices of the last purchase per product') }}</span>
@ -163,6 +164,7 @@
<span class="locale-number-format" data-format="currency">{{ $selectedRecipeTotalCosts }}</span>
</p>
</div>
@endif
</div>
</div>

View File

@ -24,12 +24,16 @@
<a class="btn btn-outline-dark responsive-button" href="{{ $U('/stockjournal') }}">
<i class="fas fa-file-alt"></i> {{ $__t('Journal') }}
</a>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<a class="btn btn-outline-dark responsive-button" href="{{ $U('/locationcontentsheet') }}">
<i class="fas fa-print"></i> {{ $__t('Location Content Sheet') }}
</a>
@endif
</h1>
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
<p id="info-expiring-products" data-next-x-days="{{ $nextXDays }}" data-status-filter="expiring" class="btn btn-lg btn-warning status-filter-button responsive-button mr-2"></p>
<p id="info-expired-products" data-status-filter="expired" class="btn btn-lg btn-danger status-filter-button responsive-button mr-2"></p>
@endif
<p id="info-missing-products" data-status-filter="belowminstockamount" class="btn btn-lg btn-info status-filter-button responsive-button"></p>
</div>
</div>
@ -39,6 +43,7 @@
<label for="search">{{ $__t('Search') }}</label> <i class="fas fa-search"></i>
<input type="text" class="form-control" id="search">
</div>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<div class="col-xs-12 col-md-6 col-xl-3">
<label for="location-filter">{{ $__t('Filter by location') }}</label> <i class="fas fa-filter"></i>
<select class="form-control" id="location-filter">
@ -48,6 +53,7 @@
@endforeach
</select>
</div>
@endif
<div class="col-xs-12 col-md-6 col-xl-3">
<label for="location-filter">{{ $__t('Filter by product group') }}</label> <i class="fas fa-filter"></i>
<select class="form-control" id="product-group-filter">
@ -61,8 +67,10 @@
<label for="status-filter">{{ $__t('Filter by status') }}</label> <i class="fas fa-filter"></i>
<select class="form-control" id="status-filter">
<option class="bg-white" value="all">{{ $__t('All') }}</option>
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
<option class="bg-warning" value="expiring">{{ $__t('Expiring soon') }}</option>
<option class="bg-danger" value="expired">{{ $__t('Already expired') }}</option>
@endif
<option class="bg-info" value="belowminstockamount">{{ $__t('Below min. stock amount') }}</option>
</select>
</div>
@ -89,7 +97,7 @@
</thead>
<tbody class="d-none">
@foreach($currentStock as $currentStockEntry)
<tr id="product-{{ $currentStockEntry->product_id }}-row" class="@if($currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $currentStockEntry->amount > 0) table-danger @elseif($currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime("+$nextXDays days")) && $currentStockEntry->amount > 0) table-warning @elseif (FindObjectInArrayByPropertyValue($missingProducts, 'id', $currentStockEntry->product_id) !== null) table-info @endif">
<tr id="product-{{ $currentStockEntry->product_id }}-row" class="@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime('-1 days')) && $currentStockEntry->amount > 0) table-danger @elseif(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING && $currentStockEntry->best_before_date < date('Y-m-d 23:59:59', strtotime("+$nextXDays days")) && $currentStockEntry->amount > 0) table-warning @elseif (FindObjectInArrayByPropertyValue($missingProducts, 'id', $currentStockEntry->product_id) !== null) table-info @endif">
<td class="fit-content border-right">
<a class="btn btn-success btn-sm product-consume-button @if($currentStockEntry->amount < 1) disabled @endif" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Consume %1$s of %2$s', '1 ' . FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
@ -105,12 +113,14 @@
data-consume-amount="{{ $currentStockEntry->amount }}">
<i class="fas fa-utensils"></i> {{ $__t('All') }}
</a>
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
<a class="btn btn-success btn-sm product-open-button @if($currentStockEntry->amount < 1 || $currentStockEntry->amount == $currentStockEntry->amount_opened) disabled @endif" href="#" data-toggle="tooltip" data-placement="left" title="{{ $__t('Mark %1$s of %2$s as open', '1 ' . FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name, FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name) }}"
data-product-id="{{ $currentStockEntry->product_id }}"
data-product-name="{{ FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->name }}"
data-product-qu-name="{{ FindObjectInArrayByPropertyValue($quantityunits, 'id', FindObjectInArrayByPropertyValue($products, 'id', $currentStockEntry->product_id)->qu_id_stock)->name }}">
<i class="fas fa-box-open"></i> 1
</a>
@endif
<div class="dropdown d-inline-block">
<button class="btn btn-sm btn-light text-secondary" type="button" data-toggle="dropdown">
<i class="fas fa-ellipsis-v"></i>

View File

@ -12,6 +12,7 @@
<div id="productpresets">
<h4>{{ $__t('Presets for new products') }}</h4>
@if(GROCY_FEATURE_FLAG_STOCK_LOCATION_TRACKING)
<div class="form-group">
<label for="product_presets_location_id">{{ $__t('Location') }}</label>
<select class="form-control user-setting-control" id="product_presets_location_id" data-setting-key="product_presets_location_id">
@ -21,6 +22,7 @@
@endforeach
</select>
</div>
@endif
<div class="form-group">
<label for="product_presets_product_group_id">{{ $__t('Product group') }}</label>