mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 09:39:57 +00:00
Added a new product option "Disable own stock" (closes #564)
This commit is contained in:
parent
b53d1a076f
commit
ccc59dfc8b
@ -28,6 +28,8 @@
|
||||
|
||||
- It's now possible to change a products stock QU, even after it was once added to stock
|
||||
- When the product was once added to stock, there needs to exist a corresponding unit conversion for the new QU
|
||||
- New product option "Disable own stock" (defaults to disabled)
|
||||
- When enabled, the corresponding product can't have own stock, means it will not be selectable on purchase (useful for parent products which are just used as a summary/total view of the child products)
|
||||
- Added the product grocycode as a (hidden by default) column to the products list (master data)
|
||||
- Fixed that consuming via the consume page was not possible when `FEATURE_FLAG_STOCK_LOCATION_TRACKING` was disabled
|
||||
|
||||
@ -91,5 +93,5 @@
|
||||
- Added a new endpoint `GET /stock/locations/{locationId}/entries` to get all stock entries of a given location (similar to the already existing endpoint `GET /stock/products/{productId}/entries`)
|
||||
- Endpoint `/recipes/{recipeId}/consume`: Fixed that consuming partially fulfilled recipes was possible, although an error was already returned in that case (and potentially some of the in-stock ingredients were consumed in fact)
|
||||
- Endpoint `/stock/products/{productId}`:
|
||||
- New field/property `current_price` which returns the current price of the corresponding products based on the stock entry to use next defined by the default consume rule (Opened first, then first due first, then first in first out)
|
||||
- New field/property `current_price` which returns the current price of the corresponding product, based on the stock entry to use next (defined by the default consume rule "Opened first, then first due first, then first in first out") or on the last price if the product is currently not in stock
|
||||
- The field/property `oldest_price` is deprecated and will be removed in a future version (this had no real sense, currently returns the same as `current_price`)
|
||||
|
@ -24,7 +24,7 @@ class StockController extends BaseController
|
||||
public function Inventory(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
return $this->renderPage($response, 'inventory', [
|
||||
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
|
||||
'products' => $this->getDatabase()->products()->where('active = 1 AND no_own_stock = 0')->orderBy('name', 'COLLATE NOCASE'),
|
||||
'barcodes' => $this->getDatabase()->product_barcodes_comma_separated(),
|
||||
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
|
||||
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
|
||||
@ -259,7 +259,7 @@ class StockController extends BaseController
|
||||
public function Purchase(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
return $this->renderPage($response, 'purchase', [
|
||||
'products' => $this->getDatabase()->products()->where('active = 1')->orderBy('name', 'COLLATE NOCASE'),
|
||||
'products' => $this->getDatabase()->products()->where('active = 1 AND no_own_stock = 0')->orderBy('name', 'COLLATE NOCASE'),
|
||||
'barcodes' => $this->getDatabase()->product_barcodes_comma_separated(),
|
||||
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name', 'COLLATE NOCASE'),
|
||||
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
|
||||
@ -512,7 +512,7 @@ class StockController extends BaseController
|
||||
public function Transfer(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
return $this->renderPage($response, 'transfer', [
|
||||
'products' => $this->getDatabase()->products()->where('active = 1')->where('id IN (SELECT product_id from stock_current WHERE amount_aggregated > 0)')->orderBy('name', 'COLLATE NOCASE'),
|
||||
'products' => $this->getDatabase()->products()->where('active = 1')->where('no_own_stock = 0 AND id IN (SELECT product_id from stock_current WHERE amount_aggregated > 0)')->orderBy('name', 'COLLATE NOCASE'),
|
||||
'barcodes' => $this->getDatabase()->product_barcodes_comma_separated(),
|
||||
'locations' => $this->getDatabase()->locations()->orderBy('name', 'COLLATE NOCASE'),
|
||||
'quantityUnits' => $this->getDatabase()->quantity_units()->orderBy('name', 'COLLATE NOCASE'),
|
||||
|
@ -4475,6 +4475,9 @@
|
||||
"treat_opened_as_out_of_stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"no_own_stock": {
|
||||
"type": "integer"
|
||||
},
|
||||
"userfields": {
|
||||
"type": "object",
|
||||
"description": "Key/value pairs of userfields"
|
||||
@ -4756,11 +4759,11 @@
|
||||
},
|
||||
"avg_price": {
|
||||
"type": "number",
|
||||
"description": "The average price af all items currently in stock of the corresponding product"
|
||||
"description": "The average price af all stock entries currently in stock of the corresponding product"
|
||||
},
|
||||
"current_price": {
|
||||
"type": "number",
|
||||
"description": "The current price of the corresponding products, based on the stock entry to use next defined by the default consume rule (Opened first, then first due first, then first in first out)"
|
||||
"description": "The current price of the corresponding product, based on the stock entry to use next (defined by the default consume rule \"Opened first, then first due first, then first in first out\") or on the last price if the product is currently not in stock"
|
||||
},
|
||||
"oldest_price": {
|
||||
"type": "number",
|
||||
@ -4781,7 +4784,7 @@
|
||||
},
|
||||
"has_childs": {
|
||||
"type": "boolean",
|
||||
"description": "True when the product is a parent products of others"
|
||||
"description": "True when the product is a parent product of others"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
|
@ -2320,3 +2320,9 @@ msgstr ""
|
||||
|
||||
msgid "The higher this number is, the more ingredients currently in stock are due soon, overdue or already expired"
|
||||
msgstr ""
|
||||
|
||||
msgid "Disable own stock"
|
||||
msgstr ""
|
||||
|
||||
msgid "When enabled, this product can't have own stock, means it will not be selectable on purchase (useful for parent products which are just used as a summary/total view of the child products)"
|
||||
msgstr ""
|
||||
|
66
migrations/0180.sql
Normal file
66
migrations/0180.sql
Normal file
@ -0,0 +1,66 @@
|
||||
ALTER TABLE products
|
||||
ADD no_own_stock TINYINT NOT NULL DEFAULT 0 CHECK(no_own_stock IN (0, 1));
|
||||
|
||||
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,
|
||||
p.no_own_stock AS product_no_own_stock
|
||||
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;
|
@ -110,7 +110,16 @@ Grocy.Components.ProductCard.Refresh = function(productId)
|
||||
$("#productcard-product-picture").addClass("d-none");
|
||||
}
|
||||
|
||||
$("#productcard-product-stock-amount-wrapper").removeClass("d-none");
|
||||
$("#productcard-aggregated-amounts").addClass("pl-2");
|
||||
if (productDetails.product.no_own_stock == 1)
|
||||
{
|
||||
$("#productcard-product-stock-amount-wrapper").addClass("d-none");
|
||||
$("#productcard-aggregated-amounts").removeClass("pl-2");
|
||||
}
|
||||
|
||||
RefreshContextualTimeago(".productcard");
|
||||
RefreshLocaleNumberDisplay(".productcard");
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
|
@ -122,11 +122,19 @@ if (!prefillProductId2.isEmpty())
|
||||
if (typeof prefillProductId !== "undefined")
|
||||
{
|
||||
$('#product_id').val(prefillProductId);
|
||||
|
||||
if ($('#product_id').val() != null)
|
||||
{
|
||||
$('#product_id').data('combobox').refresh();
|
||||
$('#product_id').trigger('change');
|
||||
|
||||
var nextInputElement = $(Grocy.Components.ProductPicker.GetPicker().parent().data('next-input-selector').toString());
|
||||
nextInputElement.focus();
|
||||
}
|
||||
else
|
||||
{
|
||||
Grocy.Components.ProductPicker.GetInputElement().focus();
|
||||
}
|
||||
}
|
||||
|
||||
if (GetUriParam("flow") === "InplaceAddBarcodeToExistingProduct")
|
||||
|
@ -38,8 +38,11 @@
|
||||
href="#productcard-product-description">{{ $__t('Show more') }}</a>
|
||||
</div>
|
||||
|
||||
<strong>{{ $__t('Stock amount') }}:</strong> <span id="productcard-product-stock-amount"
|
||||
<strong>{{ $__t('Stock amount') }}:</strong>
|
||||
<span id="productcard-product-stock-amount-wrapper">
|
||||
<span id="productcard-product-stock-amount"
|
||||
class="locale-number locale-number-quantity-amount"></span> <span id="productcard-product-stock-qu-name"></span>
|
||||
</span>
|
||||
<span id="productcard-product-stock-opened-amount"
|
||||
class="small font-italic locale-number locale-number-quantity-amount"></span>
|
||||
<span id="productcard-aggregated-amounts"
|
||||
|
@ -495,6 +495,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input @if($mode=='edit'
|
||||
&&
|
||||
$product->no_own_stock == 1) checked @endif class="form-check-input custom-control-input" type="checkbox" id="no_own_stock" name="no_own_stock" value="1">
|
||||
<label class="form-check-label custom-control-label"
|
||||
for="no_own_stock">{{ $__t('Disable own stock') }} <i class="fas fa-question-circle text-muted"
|
||||
data-toggle="tooltip"
|
||||
data-trigger="hover click"
|
||||
title="{{ $__t('When enabled, this product can\'t have own stock, means it will not be selectable on purchase (useful for parent products which are just used as a summary/total view of the child products)') }}"></i>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sticky-form-footer pt-1">
|
||||
<small id="save-hint"
|
||||
class="my-1 form-text text-muted @if($mode == 'edit') d-none @endif">{{ $__t('Save & continue to add quantity unit conversions & barcodes') }}</small>
|
||||
|
@ -321,13 +321,15 @@
|
||||
<td>
|
||||
@if($currentStockEntry->product_group_name !== null){{ $currentStockEntry->product_group_name }}@endif
|
||||
</td>
|
||||
<td data-order="{{ $currentStockEntry->amount }}">
|
||||
<td data-order="@if($currentStockEntry->product_no_own_stock == 1){{ $currentStockEntry->amount_aggregated }}@else{{ $currentStockEntry->amount }}@endif">
|
||||
<span class="@if($currentStockEntry->product_no_own_stock == 1) d-none @endif">
|
||||
<span id="product-{{ $currentStockEntry->product_id }}-amount"
|
||||
class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount }}</span> <span id="product-{{ $currentStockEntry->product_id }}-qu-name">{{ $__n($currentStockEntry->amount, $currentStockEntry->qu_unit_name, $currentStockEntry->qu_unit_name_plural) }}</span>
|
||||
<span id="product-{{ $currentStockEntry->product_id }}-opened-amount"
|
||||
class="small font-italic">@if($currentStockEntry->amount_opened > 0){{ $__t('%s opened', $currentStockEntry->amount_opened) }}@endif</span>
|
||||
</span>
|
||||
@if($currentStockEntry->is_aggregated_amount == 1)
|
||||
<span class="pl-1 text-secondary">
|
||||
<span class="@if($currentStockEntry->product_no_own_stock == 0) pl-1 @endif text-secondary">
|
||||
<i class="fas fa-custom-sigma-sign"></i> <span id="product-{{ $currentStockEntry->product_id }}-amount-aggregated"
|
||||
class="locale-number locale-number-quantity-amount">{{ $currentStockEntry->amount_aggregated }}</span> {{ $__n($currentStockEntry->amount_aggregated, $currentStockEntry->qu_unit_name, $currentStockEntry->qu_unit_name_plural, true) }}
|
||||
@if($currentStockEntry->amount_opened_aggregated > 0)
|
||||
|
Loading…
x
Reference in New Issue
Block a user