mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 17:45:39 +00:00
Move FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT
to per product option (closes #1753)
This commit is contained in:
parent
12e5377c40
commit
d1d52aea44
@ -1,6 +1,9 @@
|
||||
- Stock entry labels get now also printed on inventory (only when adding products, same option "Stock entry label" like on the purchase page)
|
||||
- Added a separate status filter and table row highlighting (blue) on the chores, tasks and batteries overview pages for items due today
|
||||
- Additionally, the "due soon" days of chores/tasks/batteries (top right corner settings menu) can be set to `0` to disable that filter/highlighting
|
||||
- The `config.php` option `FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT` was removed and is now a new product option `Treat opened as out of stock`, means, if opened items will be counted as missing for calculating if a product is below its minimum stock amount, can now be configured per product
|
||||
- The existing option will be migrated to all existing products, so no changed behavior after the update
|
||||
- There is also a new stock setting (section "Presets for new products") which can be used to configure the default when adding products (also that will be set based on the old setting on migration)
|
||||
- Optimized relative time display (also fixed a phrasing problem for some languages, e.g. Hungarian) (thanks @Tallyrald)
|
||||
- When using LDAP authentication, the configured `LDAP_UID_ATTR` is now used to compare if the user already exists instead of the username entered on the login page (that prevents creating multiple users if you entere the username in different notations) (thanks @FloSet)
|
||||
- When using reverse proxy authentication (`ReverseProxyAuthMiddleware`), it's now also possible to pass the username in an environment variable instead of an HTTP header (new `config.php` option `REVERSE_PROXY_AUTH_USE_ENV`) (thanks @Forceu)
|
||||
|
@ -149,6 +149,7 @@ DefaultUserSetting('product_presets_location_id', -1); // Default location id fo
|
||||
DefaultUserSetting('product_presets_product_group_id', -1); // Default product group id for new products (-1 means no product group is preset)
|
||||
DefaultUserSetting('product_presets_qu_id', -1); // Default quantity unit id for new products (-1 means no quantity unit is preset)
|
||||
DefaultUserSetting('product_presets_default_due_days', 0); // Default due days for new products (-1 means that the product will be never overdue)
|
||||
DefaultUserSetting('product_presets_treat_opened_as_out_of_stock', true); // Default "Treat opened as out of stock" option for new products
|
||||
DefaultUserSetting('stock_decimal_places_amounts', 4); // Default decimal places allowed for amounts
|
||||
DefaultUserSetting('stock_decimal_places_prices', 2); // Default decimal places allowed for prices
|
||||
DefaultUserSetting('stock_auto_decimal_separator_prices', false);
|
||||
@ -221,5 +222,4 @@ Setting('FEATURE_FLAG_CHORES_ASSIGNMENTS', true);
|
||||
Setting('FEATURE_FLAG_THERMAL_PRINTER', false);
|
||||
|
||||
// Feature settings
|
||||
Setting('FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT', true); // When set to true, opened items will be counted as missing for calculating if a product is below its minimum stock amount
|
||||
Setting('FEATURE_FLAG_AUTO_TORCH_ON_WITH_CAMERA', true); // Enables the torch automatically (if the device has one)
|
||||
|
@ -2286,3 +2286,9 @@ msgstr ""
|
||||
|
||||
msgid "Save & add another task"
|
||||
msgstr ""
|
||||
|
||||
msgid "Treat opened as out of stock"
|
||||
msgstr ""
|
||||
|
||||
msgid "When enabled, opened items will be counted as missing for calculating if this product is below its minimum stock amount"
|
||||
msgstr ""
|
||||
|
132
migrations/0161.sql
Normal file
132
migrations/0161.sql
Normal file
@ -0,0 +1,132 @@
|
||||
ALTER TABLE products
|
||||
ADD treat_opened_as_out_of_stock TINYINT NOT NULL DEFAULT 1 CHECK(treat_opened_as_out_of_stock IN (0, 1));
|
||||
|
||||
DROP VIEW stock_missing_products_including_opened;
|
||||
|
||||
DROP VIEW stock_missing_products;
|
||||
CREATE VIEW stock_missing_products
|
||||
AS
|
||||
|
||||
-- Products WITHOUT sub products where the amount of the sub products SHOULD NOT be cumulated
|
||||
SELECT
|
||||
p.id,
|
||||
MAX(p.name) AS name,
|
||||
p.min_stock_amount - IFNULL(SUM(s.amount), 0) - CASE WHEN p.treat_opened_as_out_of_stock = 1 THEN IFNULL(SUM(s.amount_opened), 0) ELSE 0 END AS amount_missing,
|
||||
CASE WHEN IFNULL(SUM(s.amount), 0) > 0 THEN 1 ELSE 0 END AS is_partly_in_stock
|
||||
FROM products_view p
|
||||
LEFT JOIN stock_current s
|
||||
ON p.id = s.product_id
|
||||
WHERE p.min_stock_amount != 0
|
||||
AND p.cumulate_min_stock_amount_of_sub_products = 0
|
||||
AND p.has_sub_products = 0
|
||||
AND p.parent_product_id IS NULL
|
||||
AND IFNULL(p.active, 0) = 1
|
||||
GROUP BY p.id
|
||||
HAVING IFNULL(SUM(s.amount), 0) - CASE WHEN p.treat_opened_as_out_of_stock = 1 THEN IFNULL(SUM(s.amount_opened), 0) ELSE 0 END < p.min_stock_amount
|
||||
|
||||
UNION
|
||||
|
||||
-- Parent products WITH sub products where the amount of the sub products SHOULD be cumulated
|
||||
SELECT
|
||||
p.id,
|
||||
MAX(p.name) AS name,
|
||||
SUM(sub_p.min_stock_amount) - IFNULL(SUM(s.amount_aggregated), 0) - CASE WHEN p.treat_opened_as_out_of_stock = 1 THEN IFNULL(SUM(s.amount_opened_aggregated), 0) ELSE 0 END AS amount_missing,
|
||||
CASE WHEN IFNULL(SUM(s.amount), 0) > 0 THEN 1 ELSE 0 END AS is_partly_in_stock
|
||||
FROM products_view p
|
||||
JOIN products_resolved pr
|
||||
ON p.id = pr.parent_product_id
|
||||
JOIN products sub_p
|
||||
ON pr.sub_product_id = sub_p.id
|
||||
LEFT JOIN stock_current s
|
||||
ON pr.sub_product_id = s.product_id
|
||||
WHERE sub_p.min_stock_amount != 0
|
||||
AND p.cumulate_min_stock_amount_of_sub_products = 1
|
||||
AND IFNULL(p.active, 0) = 1
|
||||
GROUP BY p.id
|
||||
HAVING IFNULL(SUM(s.amount_aggregated), 0) - CASE WHEN p.treat_opened_as_out_of_stock = 1 THEN IFNULL(SUM(s.amount_opened_aggregated), 0) ELSE 0 END < SUM(sub_p.min_stock_amount)
|
||||
|
||||
UNION
|
||||
|
||||
-- Sub products where the amount SHOULD NOT be cumulated into the parent product
|
||||
SELECT
|
||||
sub_p.id,
|
||||
MAX(sub_p.name) AS name,
|
||||
SUM(sub_p.min_stock_amount) - IFNULL(SUM(s.amount_aggregated), 0) - CASE WHEN p.treat_opened_as_out_of_stock = 1 THEN IFNULL(SUM(s.amount_opened_aggregated), 0) ELSE 0 END AS amount_missing,
|
||||
CASE WHEN IFNULL(SUM(s.amount), 0) > 0 THEN 1 ELSE 0 END AS is_partly_in_stock
|
||||
FROM products p
|
||||
JOIN products_resolved pr
|
||||
ON p.id = pr.parent_product_id
|
||||
JOIN products sub_p
|
||||
ON pr.sub_product_id = sub_p.id
|
||||
LEFT JOIN stock_current s
|
||||
ON pr.sub_product_id = s.product_id
|
||||
WHERE sub_p.min_stock_amount != 0
|
||||
AND p.cumulate_min_stock_amount_of_sub_products = 0
|
||||
AND IFNULL(p.active, 0) = 1
|
||||
GROUP BY sub_p.id
|
||||
HAVING IFNULL(SUM(s.amount), 0) - CASE WHEN p.treat_opened_as_out_of_stock = 1 THEN IFNULL(SUM(s.amount_opened), 0) ELSE 0 END < sub_p.min_stock_amount;
|
||||
|
||||
DROP VIEW uihelper_stock_current_overview_including_opened;
|
||||
|
||||
DROP VIEW uihelper_stock_current_overview;
|
||||
CREATE VIEW uihelper_stock_current_overview
|
||||
AS
|
||||
SELECT
|
||||
p.id,
|
||||
sc.amount_opened AS amount_opened,
|
||||
p.tare_weight AS tare_weight,
|
||||
p.enable_tare_weight_handling AS enable_tare_weight_handling,
|
||||
sc.amount AS amount,
|
||||
sc.value as value,
|
||||
sc.product_id AS product_id,
|
||||
sc.best_before_date AS best_before_date,
|
||||
EXISTS(SELECT id FROM stock_missing_products WHERE id = sc.product_id) AS product_missing,
|
||||
(SELECT name FROM quantity_units WHERE quantity_units.id = p.qu_id_stock) AS qu_unit_name,
|
||||
(SELECT name_plural FROM quantity_units WHERE quantity_units.id = p.qu_id_stock) AS qu_unit_name_plural,
|
||||
p.name AS product_name,
|
||||
(SELECT name FROM product_groups WHERE product_groups.id = p.product_group_id) AS product_group_name,
|
||||
EXISTS(SELECT * FROM shopping_list WHERE shopping_list.product_id = sc.product_id) AS on_shopping_list,
|
||||
(SELECT name FROM quantity_units WHERE quantity_units.id = p.qu_id_purchase) AS qu_purchase_unit_name,
|
||||
(SELECT name_plural FROM quantity_units WHERE quantity_units.id = p.qu_id_purchase) AS qu_purchase_unit_name_plural,
|
||||
sc.is_aggregated_amount,
|
||||
sc.amount_opened_aggregated,
|
||||
sc.amount_aggregated,
|
||||
p.calories AS product_calories,
|
||||
sc.amount * p.calories AS calories,
|
||||
sc.amount_aggregated * p.calories AS calories_aggregated,
|
||||
p.quick_consume_amount,
|
||||
p.due_type,
|
||||
plp.purchased_date AS last_purchased,
|
||||
plp.price AS last_price,
|
||||
pap.price as average_price,
|
||||
p.min_stock_amount,
|
||||
pbcs.barcodes AS product_barcodes,
|
||||
p.description AS product_description,
|
||||
l.name AS product_default_location_name,
|
||||
p_parent.id AS parent_product_id,
|
||||
p_parent.name AS parent_product_name,
|
||||
p.picture_file_name AS product_picture_file_name
|
||||
FROM (
|
||||
SELECT *
|
||||
FROM stock_current
|
||||
WHERE best_before_date IS NOT NULL
|
||||
UNION
|
||||
SELECT m.id, 0, 0, 0, null, 0, 0, 0, p.due_type
|
||||
FROM stock_missing_products m
|
||||
JOIN products p
|
||||
ON m.id = p.id
|
||||
WHERE m.id NOT IN (SELECT product_id FROM stock_current)
|
||||
) sc
|
||||
LEFT JOIN products_last_purchased plp
|
||||
ON sc.product_id = plp.product_id
|
||||
LEFT JOIN products_average_price pap
|
||||
ON sc.product_id = pap.product_id
|
||||
LEFT JOIN products p
|
||||
ON sc.product_id = p.id
|
||||
LEFT JOIN product_barcodes_comma_separated pbcs
|
||||
ON sc.product_id = pbcs.product_id
|
||||
LEFT JOIN products p_parent
|
||||
ON p.parent_product_id = p_parent.id
|
||||
LEFT JOIN locations l
|
||||
ON p.location_id = l.id
|
||||
WHERE p.hide_on_stock_overview = 0;
|
18
migrations/0162.php
Normal file
18
migrations/0162.php
Normal file
@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
// This is executed inside DatabaseMigrationService class/context
|
||||
|
||||
// Migrate the old config.php setting FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT
|
||||
// to the new product option treat_opened_as_out_of_stock
|
||||
// New and old default was/is enabled, so only disable it for all existing products when it was disabled
|
||||
|
||||
if (!defined('GROCY_FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT'))
|
||||
{
|
||||
define('GROCY_FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT', true);
|
||||
}
|
||||
|
||||
if (!GROCY_FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT)
|
||||
{
|
||||
$this->getDatabaseService()->ExecuteDbStatement('UPDATE products SET treat_opened_as_out_of_stock = 0');
|
||||
$this->getDatabaseService()->ExecuteDbStatement("INSERT INTO user_settings (user_id, key, value) SELECT id, 'product_presets_treat_opened_as_out_of_stock', '0' FROM users");
|
||||
}
|
@ -493,6 +493,11 @@ else if (Grocy.EditMode === 'create')
|
||||
{
|
||||
$("#default_best_before_days").val(Grocy.UserSettings.product_presets_default_due_days);
|
||||
}
|
||||
|
||||
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
|
||||
{
|
||||
$("#treat_opened_as_out_of_stock").prop("checked", BoolVal(Grocy.UserSettings.product_presets_treat_opened_as_out_of_stock));
|
||||
}
|
||||
}
|
||||
|
||||
Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
||||
|
@ -2,6 +2,10 @@
|
||||
$("#product_presets_product_group_id").val(Grocy.UserSettings.product_presets_product_group_id);
|
||||
$("#product_presets_qu_id").val(Grocy.UserSettings.product_presets_qu_id);
|
||||
$("#product_presets_default_due_days").val(Grocy.UserSettings.product_presets_default_due_days);
|
||||
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING && BoolVal(Grocy.UserSettings.product_presets_treat_opened_as_out_of_stock))
|
||||
{
|
||||
$("#product_presets_treat_opened_as_out_of_stock").prop("checked", true);
|
||||
}
|
||||
$("#stock_due_soon_days").val(Grocy.UserSettings.stock_due_soon_days);
|
||||
$("#stock_default_purchase_amount").val(Grocy.UserSettings.stock_default_purchase_amount);
|
||||
$("#stock_default_consume_amount").val(Grocy.UserSettings.stock_default_consume_amount);
|
||||
|
@ -604,14 +604,7 @@ class StockService extends BaseService
|
||||
$sql = 'SELECT * FROM stock_current';
|
||||
if ($includeNotInStockButMissingProducts)
|
||||
{
|
||||
$missingProductsView = 'stock_missing_products_including_opened';
|
||||
|
||||
if (!GROCY_FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT)
|
||||
{
|
||||
$missingProductsView = 'stock_missing_products';
|
||||
}
|
||||
|
||||
$sql = 'SELECT * FROM stock_current WHERE best_before_date IS NOT NULL UNION SELECT id, 0, 0, 0, 0, null, 0, 0, 0 FROM ' . $missingProductsView . ' WHERE id NOT IN (SELECT product_id FROM stock_current)';
|
||||
$sql = 'SELECT * FROM stock_current WHERE best_before_date IS NOT NULL UNION SELECT id, 0, 0, 0, 0, null, 0, 0, 0 FROM stock_missing_products WHERE id NOT IN (SELECT product_id FROM stock_current)';
|
||||
}
|
||||
|
||||
$currentStockMapped = $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_GROUP | \PDO::FETCH_OBJ);
|
||||
@ -640,14 +633,7 @@ class StockService extends BaseService
|
||||
|
||||
public function GetCurrentStockOverview()
|
||||
{
|
||||
if (!GROCY_FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT)
|
||||
{
|
||||
return $this->getDatabase()->uihelper_stock_current_overview();
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->getDatabase()->uihelper_stock_current_overview_including_opened();
|
||||
}
|
||||
return $this->getDatabase()->uihelper_stock_current_overview();
|
||||
}
|
||||
|
||||
public function GetDueProducts(int $days = 5, bool $excludeOverdue = false)
|
||||
@ -674,13 +660,7 @@ class StockService extends BaseService
|
||||
|
||||
public function GetMissingProducts()
|
||||
{
|
||||
$sql = 'SELECT * FROM stock_missing_products_including_opened';
|
||||
if (!GROCY_FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT)
|
||||
{
|
||||
$sql = 'SELECT * FROM stock_missing_products';
|
||||
}
|
||||
|
||||
return $this->getDatabaseService()->ExecuteDbQuery($sql)->fetchAll(\PDO::FETCH_OBJ);
|
||||
return $this->getDatabaseService()->ExecuteDbQuery('SELECT * FROM stock_missing_products')->fetchAll(\PDO::FETCH_OBJ);
|
||||
}
|
||||
|
||||
public function GetProductDetails(int $productId)
|
||||
|
@ -160,7 +160,7 @@
|
||||
'additionalCssClasses' => 'locale-number-input locale-number-quantity-amount'
|
||||
))
|
||||
|
||||
<div class="form-group">
|
||||
<div class="form-group @if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING) mb-1 @endif">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input @if($mode=='edit'
|
||||
&&
|
||||
@ -175,6 +175,22 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input @if($mode=='edit'
|
||||
&&
|
||||
$product->treat_opened_as_out_of_stock == 1) checked @endif class="form-check-input custom-control-input" type="checkbox" id="treat_opened_as_out_of_stock" name="treat_opened_as_out_of_stock" value="1">
|
||||
<label class="form-check-label custom-control-label"
|
||||
for="treat_opened_as_out_of_stock">{{ $__t('Treat opened as out of stock') }} <i class="fas fa-question-circle text-muted"
|
||||
data-toggle="tooltip"
|
||||
data-trigger="hover click"
|
||||
title="{{ $__t('When enabled, opened items will be counted as missing for calculating if this product is below its minimum stock amount') }}"></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
|
||||
<div class="form-group">
|
||||
<label class="d-block my-0"
|
||||
|
@ -63,6 +63,21 @@
|
||||
'min' => -1,
|
||||
'additionalCssClasses' => 'user-setting-control'
|
||||
))
|
||||
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input type="checkbox"
|
||||
class="form-check-input custom-control-input user-setting-control"
|
||||
id="product_presets_treat_opened_as_out_of_stock"
|
||||
data-setting-key="product_presets_treat_opened_as_out_of_stock">
|
||||
<label class="form-check-label custom-control-label"
|
||||
for="product_presets_treat_opened_as_out_of_stock">
|
||||
{{ $__t('Treat opened as out of stock') }}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<h4 class="mt-2">{{ $__t('Stock overview') }}</h4>
|
||||
|
Loading…
x
Reference in New Issue
Block a user