mirror of
https://github.com/grocy/grocy.git
synced 2025-04-28 17:23:56 +00:00
Add support for "Move on Open" (#1863)
* Add functionality to move a product when it is opened * Update the API to support this (and some other new fields) * Remove console, update move on open when either the default or the consume location change * Fix conflict from fridge * Ignore .DS_STORE from macOS * Fix the migration conflict * Fix the default location not appending properly * Revert changes no longer needed * Fix the checkbox disable logic, and call the function on page load * Simplify the transfer to use the existing function (which also adds logs) * Only move it if it's moving * Code formatting / naming * Clarify help text (it's not always about one unit, but about the corresponding amount opened) * Handle splitted stock entries + optimized/unified product property checks * Added UI feedback on auto moving Co-authored-by: Bernd Bestel <bernd@berrnd.de>
This commit is contained in:
parent
0152f1c69d
commit
5e30e89737
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,4 +1,5 @@
|
||||
/public/node_modules
|
||||
/vendor
|
||||
/.release
|
||||
embedded.txt
|
||||
embedded.txt
|
||||
.DS_Store
|
||||
|
@ -4481,6 +4481,15 @@
|
||||
"userfields": {
|
||||
"type": "object",
|
||||
"description": "Key/value pairs of userfields"
|
||||
},
|
||||
"should_not_be_frozen": {
|
||||
"type": "integer"
|
||||
},
|
||||
"default_consume_location_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"move_on_open": {
|
||||
"type": "integer"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
@ -4502,7 +4511,10 @@
|
||||
"tare_weight": "0.0",
|
||||
"not_check_stock_fulfillment_for_recipes": "0",
|
||||
"shopping_location_id": null,
|
||||
"userfields": null
|
||||
"userfields": null,
|
||||
"should_not_be_frozen": "1",
|
||||
"default_consume_location_id": "5",
|
||||
"move_on_open": "1"
|
||||
}
|
||||
},
|
||||
"QuantityUnit": {
|
||||
@ -4785,6 +4797,9 @@
|
||||
"has_childs": {
|
||||
"type": "boolean",
|
||||
"description": "True when the product is a parent product of others"
|
||||
},
|
||||
"default_location": {
|
||||
"$ref": "#/components/schemas/Location"
|
||||
}
|
||||
},
|
||||
"example": {
|
||||
@ -4850,7 +4865,8 @@
|
||||
"row_created_timestamp": "2019-05-02 20:12:25"
|
||||
},
|
||||
"average_shelf_life_days": -1,
|
||||
"spoil_rate_percent": 0
|
||||
"spoil_rate_percent": 0,
|
||||
"default_consume_location": null
|
||||
}
|
||||
},
|
||||
"ProductPriceHistory": {
|
||||
|
@ -2326,3 +2326,12 @@ msgstr ""
|
||||
|
||||
msgid "Stock entries at this location will be consumed first"
|
||||
msgstr ""
|
||||
|
||||
msgid "Move on open"
|
||||
msgstr ""
|
||||
|
||||
msgid "When enabled, on marking this product as opened, the corresponding amount will be moved to the default consume location"
|
||||
msgstr ""
|
||||
|
||||
msgid "Moved to %1$s"
|
||||
msgstr ""
|
||||
|
2
migrations/0190.sql
Normal file
2
migrations/0190.sql
Normal file
@ -0,0 +1,2 @@
|
||||
ALTER TABLE products
|
||||
ADD move_on_open TINYINT NOT NULL DEFAULT 0 CHECK(move_on_open IN (0, 1));
|
@ -189,6 +189,11 @@ $('#save-mark-as-open-button').on('click', function(e)
|
||||
Grocy.FrontendHelpers.EndUiBusy("consume-form");
|
||||
toastr.success(__t('Marked %1$s of %2$s as opened', parseFloat(jsonForm.amount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + __n(jsonForm.amount, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural, true), productDetails.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockTransaction(\'' + result[0].transaction_id + '\')"><i class="fa-solid fa-undo"></i> ' + __t("Undo") + '</a>');
|
||||
|
||||
if (productDetails.product.move_on_open == 1)
|
||||
{
|
||||
toastr.info('<span>' + __t("Moved to %1$s", productDetails.default_consume_location.name) + "</span> <i class='fa-solid fa-exchange-alt'></i>");
|
||||
}
|
||||
|
||||
if (BoolVal(Grocy.UserSettings.stock_default_consume_amount_use_quick_consume_amount))
|
||||
{
|
||||
$('#display_amount').val(productDetails.product.quick_consume_amount);
|
||||
|
@ -239,8 +239,30 @@ $('#product-form input').keyup(function(event)
|
||||
$('#location_id').change(function(event)
|
||||
{
|
||||
Grocy.FrontendHelpers.ValidateForm('product-form');
|
||||
UpdateMoveOnOpen();
|
||||
});
|
||||
|
||||
$('#default_consume_location_id').change(function(event)
|
||||
{
|
||||
UpdateMoveOnOpen();
|
||||
});
|
||||
|
||||
function UpdateMoveOnOpen()
|
||||
{
|
||||
var defaultLocation = $("#location_id :selected").val();
|
||||
var consumeLocationLocation = $("#default_consume_location_id :selected").val();
|
||||
|
||||
if (!consumeLocationLocation || defaultLocation === consumeLocationLocation)
|
||||
{
|
||||
document.getElementById("move_on_open").checked = false;
|
||||
$("#move_on_open").attr("disabled", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#move_on_open").attr("disabled", false);
|
||||
}
|
||||
}
|
||||
|
||||
$('#product-form input').keydown(function(event)
|
||||
{
|
||||
if (event.keyCode === 13) // Enter
|
||||
@ -556,6 +578,7 @@ Grocy.Components.ProductPicker.GetPicker().on('change', function(e)
|
||||
}
|
||||
});
|
||||
|
||||
UpdateMoveOnOpen();
|
||||
Grocy.FrontendHelpers.ValidateForm("product-form");
|
||||
Grocy.Components.ProductPicker.GetPicker().trigger("change");
|
||||
|
||||
|
@ -126,10 +126,26 @@ $(document).on('click', '.product-open-button', function(e)
|
||||
Grocy.Api.Post('stock/products/' + productId + '/open', { 'amount': 1, 'stock_entry_id': specificStockEntryId },
|
||||
function(bookingResponse)
|
||||
{
|
||||
button.addClass("disabled");
|
||||
Grocy.FrontendHelpers.EndUiBusy();
|
||||
toastr.success(__t('Marked %1$s of %2$s as opened', 1 + " " + productQuName, productName) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBookingEntry(' + bookingResponse[0].id + ',' + stockRowId + ')"><i class="fa-solid fa-undo"></i> ' + __t("Undo") + '</a>');
|
||||
RefreshStockEntryRow(stockRowId);
|
||||
Grocy.Api.Get('stock/products/' + productId,
|
||||
function(result)
|
||||
{
|
||||
button.addClass("disabled");
|
||||
Grocy.FrontendHelpers.EndUiBusy();
|
||||
toastr.success(__t('Marked %1$s of %2$s as opened', 1 + " " + productQuName, productName) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockBookingEntry(' + bookingResponse[0].id + ',' + stockRowId + ')"><i class="fa-solid fa-undo"></i> ' + __t("Undo") + '</a>');
|
||||
|
||||
if (result.product.move_on_open == 1)
|
||||
{
|
||||
toastr.info('<span>' + __t("Moved to %1$s", result.default_consume_location.name) + "</span> <i class='fa-solid fa-exchange-alt'></i>");
|
||||
}
|
||||
|
||||
RefreshStockEntryRow(stockRowId);
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.EndUiBusy();
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
|
@ -208,6 +208,12 @@ $(document).on('click', '.product-open-button', function(e)
|
||||
|
||||
Grocy.FrontendHelpers.EndUiBusy();
|
||||
toastr.success(__t('Marked %1$s of %2$s as opened', parseFloat(amount).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: Grocy.UserSettings.stock_decimal_places_amounts }) + " " + productQuName, productName) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockTransaction(\'' + bookingResponse[0].transaction_id + '\')"><i class="fa-solid fa-undo"></i> ' + __t("Undo") + '</a>');
|
||||
|
||||
if (result.product.move_on_open == 1)
|
||||
{
|
||||
toastr.info('<span>' + __t("Moved to %1$s", result.default_consume_location.name) + "</span> <i class='fa-solid fa-exchange-alt'></i>");
|
||||
}
|
||||
|
||||
RefreshStatistics();
|
||||
RefreshProductRow(productId);
|
||||
},
|
||||
|
@ -734,6 +734,12 @@ class StockService extends BaseService
|
||||
}
|
||||
$spoilRate = ($consumeCountSpoiled * 100.0) / $consumeCount;
|
||||
|
||||
$defaultConsumeLocation = null;
|
||||
if (!empty($product->default_consume_location_id))
|
||||
{
|
||||
$defaultConsumeLocation = $this->getDatabase()->locations($product->default_consume_location_id);
|
||||
}
|
||||
|
||||
return [
|
||||
'product' => $product,
|
||||
'product_barcodes' => $productBarcodes,
|
||||
@ -758,6 +764,7 @@ class StockService extends BaseService
|
||||
'spoil_rate_percent' => $spoilRate,
|
||||
'is_aggregated_amount' => $stockCurrentRow->is_aggregated_amount,
|
||||
'has_childs' => $this->getDatabase()->products()->where('parent_product_id = :1', $product->id)->count() !== 0,
|
||||
'default_consume_location' => $defaultConsumeLocation
|
||||
];
|
||||
}
|
||||
|
||||
@ -1057,6 +1064,15 @@ class StockService extends BaseService
|
||||
|
||||
$amount = 0;
|
||||
}
|
||||
|
||||
if ($product->move_on_open == 1)
|
||||
{
|
||||
$locationIdTo = $product->default_consume_location_id;
|
||||
if (!empty($locationIdTo) && $locationIdTo != $stockEntry->location_id)
|
||||
{
|
||||
$this->TransferProduct($stockEntry->product_id, $stockEntry->amount, $stockEntry->location_id, $locationIdTo, $stockEntry->stock_id, $transactionId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $transactionId;
|
||||
|
@ -146,6 +146,22 @@
|
||||
$location->id == $product->default_consume_location_id) selected="selected" @endif value="{{ $location->id }}">{{ $location->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_PRODUCT_OPENED_TRACKING)
|
||||
<div class="custom-control custom-checkbox">
|
||||
<input @if($mode=='edit'
|
||||
&&
|
||||
$product->move_on_open == 1) checked @endif class="form-check-input custom-control-input" type="checkbox" id="move_on_open" name="move_on_open" value="1">
|
||||
<label class="form-check-label custom-control-label"
|
||||
for="move_on_open">{{ $__t('Move on open') }} <i class="fa-solid fa-question-circle text-muted"
|
||||
data-toggle="tooltip"
|
||||
data-trigger="hover click"
|
||||
title="{{ $__t("When enabled, on marking this product as opened, the corresponding amount will be moved to the default consume location")
|
||||
}}"></i>
|
||||
</label>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
</div>
|
||||
@else
|
||||
<input type="hidden"
|
||||
|
Loading…
x
Reference in New Issue
Block a user