mirror of
https://github.com/grocy/grocy.git
synced 2026-04-07 05:16:15 +02:00
product barcodes with QU and Stores
This commit is contained in:
parent
23d80c3b77
commit
6676e3c508
|
|
@ -23,6 +23,18 @@ class StockApiController extends BaseApiController
|
|||
}
|
||||
}
|
||||
|
||||
public function ProductBarcodeDetails(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
{
|
||||
return $this->ApiResponse($response, $this->getDatabase()->product_barcodes()->where('barcode = :1', $args['barcode'])->fetch());
|
||||
}
|
||||
catch (\Exception $ex)
|
||||
{
|
||||
return $this->GenericErrorResponse($response, $ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public function ProductDetailsByBarcode(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
try
|
||||
|
|
|
|||
|
|
@ -179,6 +179,7 @@ class StockController extends BaseController
|
|||
{
|
||||
return $this->renderPage($response, 'productform', [
|
||||
'locations' => $this->getDatabase()->locations()->orderBy('name'),
|
||||
'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'),
|
||||
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'),
|
||||
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'),
|
||||
'productgroups' => $this->getDatabase()->product_groups()->orderBy('name'),
|
||||
|
|
@ -195,6 +196,7 @@ class StockController extends BaseController
|
|||
return $this->renderPage($response, 'productform', [
|
||||
'product' => $product,
|
||||
'locations' => $this->getDatabase()->locations()->orderBy('name'),
|
||||
'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'),
|
||||
'quantityunits' => $this->getDatabase()->quantity_units()->orderBy('name'),
|
||||
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name'),
|
||||
'productgroups' => $this->getDatabase()->product_groups()->orderBy('name'),
|
||||
|
|
@ -354,6 +356,34 @@ class StockController extends BaseController
|
|||
]);
|
||||
}
|
||||
|
||||
public function ProductBarcodesEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$product = null;
|
||||
if (isset($request->getQueryParams()['product']))
|
||||
{
|
||||
$product = $this->getDatabase()->products($request->getQueryParams()['product']);
|
||||
}
|
||||
|
||||
if ($args['productBarcodeId'] == 'new')
|
||||
{
|
||||
return $this->renderPage($response, 'productbarcodesform', [
|
||||
'mode' => 'create',
|
||||
'barcodes' => $this->getDatabase()->product_barcodes()->orderBy('barcode'),
|
||||
'product' => $product,
|
||||
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name')
|
||||
]);
|
||||
}
|
||||
else
|
||||
{
|
||||
return $this->renderPage($response, 'productbarcodesform', [
|
||||
'mode' => 'edit',
|
||||
'barcode' => $this->getDatabase()->product_barcodes($args['productBarcodeId']),
|
||||
'product' => $product,
|
||||
'shoppinglocations' => $this->getDatabase()->shopping_locations()->orderBy('name')
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
public function QuantityUnitConversionEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||
{
|
||||
$product = null;
|
||||
|
|
|
|||
|
|
@ -2703,6 +2703,57 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"/productbarcodedetails/{barcode}": {
|
||||
"get": {
|
||||
"summary": "Executes a product barcode details lookoup via the configured plugin with the given barcode",
|
||||
"tags": [
|
||||
"Product"
|
||||
],
|
||||
"parameters": [
|
||||
{
|
||||
"in": "path",
|
||||
"name": "barcode",
|
||||
"required": true,
|
||||
"description": "The barcode to lookup up",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "add",
|
||||
"required": false,
|
||||
"description": "When true, the product is added to the database on a successful lookup and the new product id is in included in the response",
|
||||
"schema": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "An ProductBarcodeDetails object or null, when nothing was found for the given barcode",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/ProductBarcodeDetailsResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "The operation was not successful (possible errors are: Plugin error)",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GenericErrorResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/recipes/{recipeId}/add-not-fulfilled-products-to-shoppinglist": {
|
||||
"post": {
|
||||
"summary": "Adds all missing products for the given recipe to the shopping list",
|
||||
|
|
@ -3374,6 +3425,7 @@
|
|||
"enum": [
|
||||
"products",
|
||||
"chores",
|
||||
"product_barcodes",
|
||||
"batteries",
|
||||
"locations",
|
||||
"quantity_units",
|
||||
|
|
@ -3862,6 +3914,28 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"ProductBarcodeDetailsResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"product_id": {
|
||||
"type": "integer"
|
||||
},
|
||||
"barcode": {
|
||||
"type": "string"
|
||||
},
|
||||
"qu_factor_purchase_to_stock": {
|
||||
"type": "number",
|
||||
"format": "number"
|
||||
},
|
||||
"barcode": {
|
||||
"type": "string",
|
||||
"description": "Can contain multiple barcodes separated by comma"
|
||||
},
|
||||
"shopping_location_id": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"ExternalBarcodeLookupResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
|
|
|
|||
19
migrations/0103.sql
Normal file
19
migrations/0103.sql
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
ALTER TABLE stock_log
|
||||
ADD qu_factor_purchase_to_stock REAL NOT NULL DEFAULT 1.0;
|
||||
|
||||
ALTER TABLE stock
|
||||
ADD qu_factor_purchase_to_stock REAL NOT NULL DEFAULT 1.0;
|
||||
|
||||
UPDATE stock
|
||||
SET qu_factor_purchase_to_stock = (select qu_factor_purchase_to_stock from products where product_id = id);
|
||||
|
||||
UPDATE stock_log
|
||||
SET qu_factor_purchase_to_stock = (select qu_factor_purchase_to_stock from products where product_id = id);
|
||||
|
||||
CREATE TABLE product_barcodes (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT UNIQUE,
|
||||
product_id INT NOT NULL,
|
||||
barcode TEXT NOT NULL,
|
||||
qu_factor_purchase_to_stock REAL NOT NULL DEFAULT 1,
|
||||
shopping_location_id INTEGER
|
||||
);
|
||||
|
|
@ -80,7 +80,14 @@ Grocy.Components.ProductCard.Refresh = function(productId)
|
|||
|
||||
if (productDetails.last_price !== null)
|
||||
{
|
||||
$('#productcard-product-last-price').text(Number.parseFloat(productDetails.last_price).toLocaleString() + ' ' + Grocy.Currency + ' per ' + productDetails.quantity_unit_purchase.name);
|
||||
if (productDetails.last_qu_factor_purchase_to_stock > 1)
|
||||
{
|
||||
$('#productcard-product-last-price').text(Number.parseFloat(productDetails.last_price).toLocaleString() + ' ' + Grocy.Currency + ' per 1 ' + productDetails.quantity_unit_purchase.name + ' of ' + productDetails.last_qu_factor_purchase_to_stock + ' ' + productDetails.quantity_unit_stock.name_plural);
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#productcard-product-last-price').text(Number.parseFloat(productDetails.last_price).toLocaleString() + ' ' + Grocy.Currency + ' per ' + productDetails.quantity_unit_purchase.name);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
|
|
@ -132,6 +132,7 @@ $('#product_id_text_input').on('blur', function(e)
|
|||
{
|
||||
return;
|
||||
}
|
||||
$('#product_id').attr("barcode", "null");
|
||||
|
||||
var input = $('#product_id_text_input').val().toString();
|
||||
var possibleOptionElement = $("#product_id option[data-additional-searchdata*=\"" + input + ",\"]").first();
|
||||
|
|
@ -139,6 +140,7 @@ $('#product_id_text_input').on('blur', function(e)
|
|||
if (GetUriParam('addbarcodetoselection') === undefined && input.length > 0 && possibleOptionElement.length > 0)
|
||||
{
|
||||
$('#product_id').val(possibleOptionElement.val());
|
||||
$('#product_id').attr("barcode", input);
|
||||
$('#product_id').data('combobox').refresh();
|
||||
$('#product_id').trigger('change');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -180,6 +180,11 @@ var calendar = $("#calendar").fullCalendar({
|
|||
productDetails.last_price = 0;
|
||||
}
|
||||
|
||||
if (productDetails.last_qu_factor_purchase_to_stock === null)
|
||||
{
|
||||
productDetails.last_qu_factor_purchase_to_stock = 1;
|
||||
}
|
||||
|
||||
element.attr("data-product-details", event.productDetails);
|
||||
|
||||
var productOrderMissingButtonDisabledClasses = "disabled";
|
||||
|
|
@ -205,7 +210,7 @@ var calendar = $("#calendar").fullCalendar({
|
|||
var costsAndCaloriesPerServing = ""
|
||||
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
|
||||
{
|
||||
costsAndCaloriesPerServing = '<h5 class="small text-truncate"><span class="locale-number locale-number-currency">' + productDetails.last_price / productDetails.product.qu_factor_purchase_to_stock * mealPlanEntry.product_amount + '</span> / <span class="locale-number locale-number-generic">' + productDetails.product.calories * mealPlanEntry.product_amount + '</span> kcal ' + '<h5>';
|
||||
costsAndCaloriesPerServing = '<h5 class="small text-truncate"><span class="locale-number locale-number-currency">' + productDetails.last_price / productDetails.last_qu_factor_purchase_to_stock * mealPlanEntry.product_amount + '</span> / <span class="locale-number locale-number-generic">' + productDetails.product.calories * mealPlanEntry.product_amount + '</span> kcal ' + '<h5>';
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
|||
98
public/viewjs/productbarcodesform.js
Normal file
98
public/viewjs/productbarcodesform.js
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
$('#save-barcode-button').on('click', function(e)
|
||||
{
|
||||
e.preventDefault();
|
||||
|
||||
var jsonData = $('#barcode-form').serializeJSON();
|
||||
Grocy.FrontendHelpers.BeginUiBusy("barcode-form");
|
||||
|
||||
if (Grocy.EditMode === 'create')
|
||||
{
|
||||
Grocy.Api.Post('objects/product_barcodes', jsonData,
|
||||
function(result)
|
||||
{
|
||||
Grocy.EditObjectId = result.created_object_id;
|
||||
window.location.href = U("/product/" + GetUriParam("product"));
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.EndUiBusy("barcode-form");
|
||||
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
Grocy.Api.Put('objects/product_barcodes/' + Grocy.EditObjectId, jsonData,
|
||||
function(result)
|
||||
{
|
||||
window.location.href = U("/product/" + GetUriParam("product"));
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.EndUiBusy("barcode-form");
|
||||
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
Grocy.Api.Get('stock/products/' + jsonData.product_id,
|
||||
function(productDetails)
|
||||
{
|
||||
var existingBarcodes = productDetails.product.barcode || '';
|
||||
if (existingBarcodes.length === 0)
|
||||
{
|
||||
productDetails.product.barcode = jsonData.barcode;
|
||||
}
|
||||
else
|
||||
{
|
||||
productDetails.product.barcode += ',' + jsonData.barcode;
|
||||
}
|
||||
var jsonDataProduct = {};
|
||||
jsonDataProduct.barcode = productDetails.product.barcode;
|
||||
|
||||
Grocy.Api.Put('objects/products/' + jsonData.product_id, jsonDataProduct,
|
||||
function(result)
|
||||
{
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.EndUiBusy("barcode-form");
|
||||
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||
}
|
||||
);
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.EndUiBusy("barcode-form");
|
||||
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
$('#barcode').on('change', function(e)
|
||||
{
|
||||
Grocy.FrontendHelpers.ValidateForm('barcode-form');
|
||||
});
|
||||
|
||||
$('#qu_factor_purchase_to_stock').on('change', function(e)
|
||||
{
|
||||
Grocy.FrontendHelpers.ValidateForm('barcode-form');
|
||||
});
|
||||
|
||||
$('#barcode-form input').keydown(function(event)
|
||||
{
|
||||
if (event.keyCode === 13) //Enter
|
||||
{
|
||||
event.preventDefault();
|
||||
|
||||
if (document.getElementById('barcode-form').checkValidity() === false) //There is at least one validation error
|
||||
{
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#save-barcode-button').click();
|
||||
}
|
||||
}
|
||||
});
|
||||
Grocy.FrontendHelpers.ValidateForm('barcode-form');
|
||||
|
|
@ -249,12 +249,28 @@ $('#product-form input').keyup(function(event)
|
|||
{
|
||||
$("#qu-conversion-add-button").removeClass("disabled");
|
||||
}
|
||||
if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
|
||||
{
|
||||
$("#barcode-add-button").addClass("disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#barcode-add-button").removeClass("disabled");
|
||||
}
|
||||
});
|
||||
|
||||
$('#product-form select').change(function(event)
|
||||
{
|
||||
Grocy.FrontendHelpers.ValidateForm('product-form');
|
||||
|
||||
if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
|
||||
{
|
||||
$("#barcode-add-button").addClass("disabled");
|
||||
}
|
||||
else
|
||||
{
|
||||
$("#barcode-add-button").removeClass("disabled");
|
||||
}
|
||||
if (document.getElementById('product-form').checkValidity() === false) //There is at least one validation error
|
||||
{
|
||||
$("#qu-conversion-add-button").addClass("disabled");
|
||||
|
|
@ -365,6 +381,18 @@ var quConversionsTable = $('#qu-conversions-table').DataTable({
|
|||
$('#qu-conversions-table tbody').removeClass("d-none");
|
||||
quConversionsTable.columns.adjust().draw();
|
||||
|
||||
var barcodeTable = $('#barcode-table').DataTable({
|
||||
'order': [[1, 'asc']],
|
||||
"orderFixed": [[1, 'asc']],
|
||||
'columnDefs': [
|
||||
{ 'orderable': false, 'targets': 0 },
|
||||
{ 'searchable': false, "targets": 0 }
|
||||
]
|
||||
});
|
||||
$('#barcode-table tbody').removeClass("d-none");
|
||||
barcodeTable.columns.adjust().draw();
|
||||
|
||||
|
||||
Grocy.Components.UserfieldsForm.Load();
|
||||
$("#name").trigger("keyup");
|
||||
$('#name').focus();
|
||||
|
|
@ -407,6 +435,29 @@ $(document).on('click', '.qu-conversion-delete-button', function(e)
|
|||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
|
||||
var newBarcode = '';
|
||||
productBarcode.split(',').forEach(function(item)
|
||||
{
|
||||
if(barcode != item)
|
||||
{
|
||||
newBarcode += ',' + item;
|
||||
}
|
||||
});
|
||||
|
||||
var jsonDataProduct = {};
|
||||
jsonDataProduct.barcode = newBarcode;
|
||||
|
||||
Grocy.Api.Put('objects/products/' + productId, jsonDataProduct,
|
||||
function(result)
|
||||
{
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.EndUiBusy("product-form");
|
||||
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
@ -425,6 +476,82 @@ $("#qu-conversion-add-button").on("click", function(e)
|
|||
$('#save-product-button').click();
|
||||
});
|
||||
|
||||
$(document).on('click', '.barcode-delete-button', function(e)
|
||||
{
|
||||
var objectId = $(e.currentTarget).attr('data-barcode-id');
|
||||
var productId = $(e.currentTarget).attr('data-product-id');
|
||||
var barcode = $(e.currentTarget).attr('data-barcode');
|
||||
var productBarcode = $(e.currentTarget).attr('data-product-barcode');
|
||||
|
||||
bootbox.confirm({
|
||||
message: __t('Are you sure to remove this barcode?'),
|
||||
closeButton: false,
|
||||
buttons: {
|
||||
confirm: {
|
||||
label: __t('Yes'),
|
||||
className: 'btn-success'
|
||||
},
|
||||
cancel: {
|
||||
label: __t('No'),
|
||||
className: 'btn-danger'
|
||||
}
|
||||
},
|
||||
callback: function(result)
|
||||
{
|
||||
if (result === true)
|
||||
{
|
||||
Grocy.Api.Delete('objects/product_barcodes/' + objectId, { },
|
||||
function(result)
|
||||
{
|
||||
Grocy.ProductEditFormRedirectUri = "reload";
|
||||
$('#save-product-button').click();
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
|
||||
var newBarcode = '';
|
||||
productBarcode.split(',').forEach(function(item)
|
||||
{
|
||||
if(barcode != item)
|
||||
{
|
||||
newBarcode += ',' + item;
|
||||
}
|
||||
});
|
||||
|
||||
var jsonDataProduct = {};
|
||||
jsonDataProduct.barcode = newBarcode;
|
||||
|
||||
Grocy.Api.Put('objects/products/' + productId, jsonDataProduct,
|
||||
function(result)
|
||||
{
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
Grocy.FrontendHelpers.EndUiBusy("product-form");
|
||||
Grocy.FrontendHelpers.ShowGenericError('Error while saving, probably this item already exists', xhr.response)
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$(document).on('click', '.barcode-edit-button', function (e)
|
||||
{
|
||||
var id = $(e.currentTarget).attr('data-barcode-id');
|
||||
Grocy.ProductEditFormRedirectUri = U("/productbarcodes/" + id.toString() + "?product=editobjectid");
|
||||
$('#save-product-button').click();
|
||||
});
|
||||
|
||||
$("#barcode-add-button").on("click", function(e)
|
||||
{
|
||||
Grocy.ProductEditFormRedirectUri = U("/productbarcodes/new?product=editobjectid");
|
||||
$('#save-product-button').click();
|
||||
});
|
||||
|
||||
$('#qu_id_purchase').blur(function(e)
|
||||
{
|
||||
// Preset the stock quantity unit with the purchase quantity unit, if the stock quantity unit is unset.
|
||||
|
|
|
|||
|
|
@ -141,13 +141,66 @@ if (Grocy.Components.ProductPicker !== undefined)
|
|||
{
|
||||
Grocy.Components.ProductCard.Refresh(productId);
|
||||
|
||||
if (document.getElementById("product_id").getAttribute("barcode") != "null")
|
||||
{
|
||||
Grocy.Api.Get('productbarcodedetails/' + document.getElementById("product_id").getAttribute("barcode"),
|
||||
function(resultBarcode)
|
||||
{
|
||||
if (resultBarcode != null)
|
||||
{
|
||||
$('#product_id').attr("barcode-qu-factor-purchase-to-stock", resultBarcode.qu_factor_purchase_to_stock);
|
||||
$('#product_id').attr("barcode-shopping-location-id", resultBarcode.shopping_location_id);
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#product_id').attr("barcode-qu-factor-purchase-to-stock", "null");
|
||||
$('#product_id').attr("barcode-shopping-location-id", "null");
|
||||
}
|
||||
},
|
||||
function(xhr)
|
||||
{
|
||||
console.error(xhr);
|
||||
}
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#product_id').attr("barcode-qu-factor-purchase-to-stock", "null");
|
||||
$('#product_id').attr("barcode-shopping-location-id", "null");
|
||||
}
|
||||
|
||||
Grocy.Api.Get('stock/products/' + productId,
|
||||
function(productDetails)
|
||||
{
|
||||
$('#price').val(parseFloat(productDetails.last_price).toLocaleString({ minimumFractionDigits: 2, maximumFractionDigits: 2 }));
|
||||
|
||||
$('#price').val(parseFloat(productDetails.last_price).toLocaleString({ minimumFractionDigits: 2, maximumFractionDigits: 2 }));
|
||||
|
||||
var qu_factor_purchase_to_stock = null;
|
||||
var barcode_shopping_location_id = null;
|
||||
|
||||
if (document.getElementById("product_id").getAttribute("barcode") != "null" && document.getElementById("product_id").getAttribute("barcode-qu-factor-purchase-to-stock") != "null")
|
||||
{
|
||||
qu_factor_purchase_to_stock = document.getElementById("product_id").getAttribute("barcode-qu-factor-purchase-to-stock");
|
||||
barcode_shopping_location_id = document.getElementById("product_id").getAttribute("barcode-shopping-location-id");
|
||||
}
|
||||
else
|
||||
{
|
||||
if (productDetails.last_qu_factor_purchase_to_stock != null)
|
||||
{
|
||||
qu_factor_purchase_to_stock = productDetails.last_qu_factor_purchase_to_stock;
|
||||
}
|
||||
else
|
||||
{
|
||||
qu_factor_purchase_to_stock = productDetails.product.qu_factor_purchase_to_stock;
|
||||
}
|
||||
}
|
||||
|
||||
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING) {
|
||||
if (productDetails.last_shopping_location_id != null)
|
||||
if (barcode_shopping_location_id != null)
|
||||
{
|
||||
Grocy.Components.ShoppingLocationPicker.SetId(barcode_shopping_location_id);
|
||||
}
|
||||
else if (productDetails.last_shopping_location_id != null)
|
||||
{
|
||||
Grocy.Components.ShoppingLocationPicker.SetId(productDetails.last_shopping_location_id);
|
||||
}
|
||||
|
|
@ -162,20 +215,21 @@ if (Grocy.Components.ProductPicker !== undefined)
|
|||
Grocy.Components.LocationPicker.SetId(productDetails.location.id);
|
||||
}
|
||||
|
||||
$('#amount_qu_unit').attr("qu-factor-purchase-to-stock", productDetails.product.qu_factor_purchase_to_stock);
|
||||
$('#amount_qu_unit').attr("qu-factor-purchase-to-stock", qu_factor_purchase_to_stock);
|
||||
$('#amount_qu_unit').attr("quantity-unit-purchase-name", productDetails.quantity_unit_purchase.name);
|
||||
$('#amount_qu_unit').attr("quantity-unit-stock-name", productDetails.quantity_unit_stock.name);
|
||||
$('#amount_qu_unit').attr("quantity-unit-stock-name-plural", productDetails.quantity_unit_stock.name_plural);
|
||||
$('#qu_factor_purchase_to_stock').val(productDetails.product.qu_factor_purchase_to_stock);
|
||||
$('#qu_factor_purchase_to_stock').val(qu_factor_purchase_to_stock);
|
||||
|
||||
if (productDetails.product.qu_id_purchase === productDetails.product.qu_id_stock)
|
||||
|
||||
if (qu_factor_purchase_to_stock == 1)
|
||||
{
|
||||
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name);
|
||||
$('#group-qu_factor_purchase_to_stock').addClass('d-none');
|
||||
}
|
||||
else
|
||||
{
|
||||
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name + " (" + __t("will be multiplied by a factor of %1$s to get %2$s", parseFloat(productDetails.product.qu_factor_purchase_to_stock).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: 2 }), __n(2, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural)) + ")");
|
||||
$('#amount_qu_unit').text(productDetails.quantity_unit_purchase.name + " (" + __t("will be multiplied by a factor of %1$s to get %2$s", parseFloat(qu_factor_purchase_to_stock).toLocaleString({ minimumFractionDigits: 0, maximumFractionDigits: 2 }), __n(2, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural)) + ")");
|
||||
$('#group-qu_factor_purchase_to_stock').removeClass('d-none');
|
||||
}
|
||||
|
||||
|
|
@ -200,7 +254,7 @@ if (Grocy.Components.ProductPicker !== undefined)
|
|||
|
||||
if (productDetails.product.enable_tare_weight_handling == 1)
|
||||
{
|
||||
var minAmount = parseFloat(productDetails.product.tare_weight) / productDetails.product.qu_factor_purchase_to_stock + parseFloat(productDetails.stock_amount);
|
||||
var minAmount = parseFloat(productDetails.product.tare_weight) / qu_factor_purchase_to_stock + parseFloat(productDetails.stock_amount);
|
||||
$("#amount").attr("min", minAmount);
|
||||
$("#amount").attr("step", "0.0001");
|
||||
$("#amount").parent().find(".invalid-feedback").text(__t('The amount cannot be lower than %s', minAmount.toLocaleString()));
|
||||
|
|
|
|||
|
|
@ -45,6 +45,7 @@ $app->group('', function(RouteCollectorProxy $group)
|
|||
$group->get('/stockentry/{entryId}', '\Grocy\Controllers\StockController:StockEntryEditForm');
|
||||
$group->get('/products', '\Grocy\Controllers\StockController:ProductsList');
|
||||
$group->get('/product/{productId}', '\Grocy\Controllers\StockController:ProductEditForm');
|
||||
$group->get('/productbarcodes/{productBarcodeId}', '\Grocy\Controllers\StockController:ProductBarcodesEditForm');
|
||||
$group->get('/stocksettings', '\Grocy\Controllers\StockController:StockSettings');
|
||||
$group->get('/locations', '\Grocy\Controllers\StockController:LocationsList');
|
||||
$group->get('/location/{locationId}', '\Grocy\Controllers\StockController:LocationEditForm');
|
||||
|
|
@ -199,6 +200,7 @@ $app->group('/api', function(RouteCollectorProxy $group)
|
|||
$group->get('/stock/transactions/{transactionId}', '\Grocy\Controllers\StockApiController:StockTransactions');
|
||||
$group->post('/stock/transactions/{transactionId}/undo', '\Grocy\Controllers\StockApiController:UndoTransaction');
|
||||
$group->get('/stock/barcodes/external-lookup/{barcode}', '\Grocy\Controllers\StockApiController:ExternalBarcodeLookup');
|
||||
$group->get('/productbarcodedetails/{barcode}', '\Grocy\Controllers\StockApiController:ProductBarcodeDetails');
|
||||
}
|
||||
|
||||
// Shopping list
|
||||
|
|
|
|||
|
|
@ -127,12 +127,14 @@ class StockService extends BaseService
|
|||
$averageShelfLifeDays = intval($this->getDatabase()->stock_average_product_shelf_life()->where('id', $productId)->fetch()->average_shelf_life_days);
|
||||
|
||||
$lastPrice = null;
|
||||
$lastQuFactorPurchaseToStock = null;
|
||||
$defaultShoppingLocation = null;
|
||||
$lastShoppingLocation = null;
|
||||
$lastLogRow = $this->getDatabase()->stock_log()->where('product_id = :1 AND transaction_type IN (:2, :3) AND undone = 0', $productId, self::TRANSACTION_TYPE_PURCHASE, self::TRANSACTION_TYPE_INVENTORY_CORRECTION)->orderBy('row_created_timestamp', 'DESC')->limit(1)->fetch();
|
||||
if ($lastLogRow !== null && !empty($lastLogRow))
|
||||
{
|
||||
$lastPrice = $lastLogRow->price;
|
||||
$lastQuFactorPurchaseToStock = $lastLogRow->qu_factor_purchase_to_stock;
|
||||
$lastShoppingLocation = $lastLogRow->shopping_location_id;
|
||||
}
|
||||
|
||||
|
|
@ -155,6 +157,7 @@ class StockService extends BaseService
|
|||
'quantity_unit_purchase' => $quPurchase,
|
||||
'quantity_unit_stock' => $quStock,
|
||||
'last_price' => $lastPrice,
|
||||
'last_qu_factor_purchase_to_stock' => $lastQuFactorPurchaseToStock,
|
||||
'last_shopping_location_id' => $lastShoppingLocation,
|
||||
'default_shopping_location_id' => $product->shopping_location_id,
|
||||
'next_best_before_date' => $nextBestBeforeDate,
|
||||
|
|
|
|||
68
views/productbarcodesform.blade.php
Normal file
68
views/productbarcodesform.blade.php
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
@extends('layout.default')
|
||||
|
||||
@if($mode == 'edit')
|
||||
@section('title', $__t('Edit Barcodes'))
|
||||
@else
|
||||
@section('title', $__t('Create Barcodes'))
|
||||
@endif
|
||||
|
||||
@section('viewJsName', 'productbarcodesform')
|
||||
|
||||
@section('content')
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h2 class="title">@yield('title')</h2>
|
||||
<hr>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-6 col-xs-12">
|
||||
|
||||
<h3 class="text-muted">{{ $__t('Barcode for product') }} <strong>{{ $product->name }}</strong></h3>
|
||||
|
||||
<script>Grocy.EditMode = '{{ $mode }}';</script>
|
||||
|
||||
@if($mode == 'edit')
|
||||
<script>Grocy.EditObjectId = {{ $barcode->id }};</script>
|
||||
@endif
|
||||
|
||||
<form id="barcode-form" novalidate>
|
||||
|
||||
<input type="hidden" name="product_id" value="{{ $product->id }}">
|
||||
|
||||
<div class="form-group">
|
||||
<label for="name">{{ $__t('Barcode') }}<i class="fas fa-barcode"></i></label>
|
||||
<input type="text" class="form-control" required id="barcode" name="barcode" value="@if($mode == 'edit'){{ $barcode->barcode }}@endif">
|
||||
</div>
|
||||
|
||||
@php if($mode == 'edit') { $value = $barcode->qu_factor_purchase_to_stock; } else { $value = 1; } @endphp
|
||||
@include('components.numberpicker', array(
|
||||
'id' => 'qu_factor_purchase_to_stock',
|
||||
'label' => 'Factor purchase to stock quantity unit',
|
||||
'min' => 1,
|
||||
'value' => $value,
|
||||
'isRequired' => true,
|
||||
'invalidFeedback' => $__t('The amount cannot be lower than %s', '1'),
|
||||
'additionalCssClasses' => 'input-group-qu',
|
||||
))
|
||||
|
||||
@if(GROCY_FEATURE_FLAG_PRICE_TRACKING)
|
||||
<div class="form-group">
|
||||
<label for="shopping_location_id_id">{{ $__t('Default store') }}</label>
|
||||
<select class="form-control" id="shopping_location_id" name="shopping_location_id">
|
||||
<option></option>
|
||||
@foreach($shoppinglocations as $store)
|
||||
<option @if($mode == 'edit' && $store->id == $product->shopping_location_id) selected="selected" @endif value="{{ $store->id }}">{{ $store->name }}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
@else
|
||||
<input type="hidden" name="shopping_location_id" id="shopping_location_id" value="1">
|
||||
@endif
|
||||
|
||||
<button id="save-barcode-button" class="btn btn-success">{{ $__t('Save') }}</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@stop
|
||||
|
|
@ -346,6 +346,55 @@
|
|||
</tbody>
|
||||
</table>
|
||||
|
||||
<h2>
|
||||
{{ $__t('Barcode Details') }}
|
||||
<a id="barcode-add-button" class="btn btn-outline-dark" href="#">
|
||||
<i class="fas fa-plus"></i> {{ $__t('Add') }}
|
||||
</a>
|
||||
</h2>
|
||||
<h5 id="barcode-headline-info" class="text-muted font-italic"></h5>
|
||||
<table id="barcode-table" class="table table-sm table-striped dt-responsive">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="border-right"></th>
|
||||
<th>{{ $__t('Barcode') }}</th>
|
||||
<th>{{ $__t('QU Factor Purchase To Stock') }}</th>
|
||||
<th>{{ $__t('Shopping Location') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="d-none">
|
||||
@if($mode == "edit")
|
||||
@foreach($barcodes as $barcode)
|
||||
@if($barcode->product_id == $product->id || $barcode->product_id == null)
|
||||
<tr>
|
||||
<td class="fit-content border-right">
|
||||
<a class="btn btn-sm btn-info barcode-edit-button @if($barcode->product_id == null) disabled @endif" href="#" data-barcode-id="{{ $barcode->id }}">
|
||||
<i class="fas fa-edit"></i>
|
||||
</a>
|
||||
<a class="btn btn-sm btn-danger barcode-delete-button @if($barcode->product_id == null) disabled @endif" href="#" data-barcode-id="{{ $barcode->id }}" data-barcode="{{ $barcode->barcode }}" data-product-barcode="{{ $product->barcode }}" data-product-id="{{ $product->id }}">
|
||||
<i class="fas fa-trash"></i>
|
||||
</a>
|
||||
</td>
|
||||
<td>
|
||||
{{ $barcode->barcode }}
|
||||
</td>
|
||||
<td>
|
||||
<span class="locale-number locale-number-quantity-amount">{{ $barcode->qu_factor_purchase_to_stock }}</span>
|
||||
</td>
|
||||
@if(GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
|
||||
<td id="barcode-shopping-location">
|
||||
@if (FindObjectInArrayByPropertyValue($shoppinglocations, 'id', $barcode->shopping_location_id) !== null)
|
||||
{{ FindObjectInArrayByPropertyValue($shoppinglocations, 'id', $barcode->shopping_location_id)->name }}
|
||||
@endif
|
||||
</td>
|
||||
@endif
|
||||
</tr>
|
||||
@endif
|
||||
@endforeach
|
||||
@endif
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="pt-5">
|
||||
<label class="mt-2">{{ $__t('Picture') }}</label>
|
||||
<button id="delete-current-product-picture-button" class="btn btn-sm btn-danger @if(empty($product->picture_file_name)) disabled @endif"><i class="fas fa-trash"></i> {{ $__t('Delete') }}</button>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user