mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 01:32:38 +00:00
Implemented frontend external barcode lookup workflow + a plugin for Open Food Facts (closes #158)
This commit is contained in:
parent
a2c2049037
commit
c9ffe4885d
@ -131,7 +131,9 @@ Products can be directly added to the database via looking them up against exter
|
|||||||
|
|
||||||
This can be done in-place using the product picker workflow "External barcode lookup (via plugin)" (the workflow dialog is displayed when entering something unknown in any product input field).
|
This can be done in-place using the product picker workflow "External barcode lookup (via plugin)" (the workflow dialog is displayed when entering something unknown in any product input field).
|
||||||
|
|
||||||
There is no plugin included for any service, see the reference implementation in `data/plugins/DemoBarcodeLookupPlugin.php`.
|
A plugin for [Open Food Facts](https://world.openfoodfacts.org/) is included and used by default (see the `data/config.php` option `STOCK_BARCODE_LOOKUP_PLUGIN`).
|
||||||
|
|
||||||
|
See that plugin or the reference implementation in `data/plugins/DemoBarcodeLookupPlugin.php` if you want to build a plugin.
|
||||||
|
|
||||||
### Database migrations
|
### Database migrations
|
||||||
|
|
||||||
|
@ -13,7 +13,9 @@
|
|||||||
- Added a new product picker workflow "External barcode lookup (via plugin)"
|
- Added a new product picker workflow "External barcode lookup (via plugin)"
|
||||||
- This executes the configured barcode lookup plugin with the given barcode
|
- This executes the configured barcode lookup plugin with the given barcode
|
||||||
- If the lookup was successful, the product edit page of the created product is displayed, where the product setup can be completed (if required)
|
- If the lookup was successful, the product edit page of the created product is displayed, where the product setup can be completed (if required)
|
||||||
- After that, the transaction is continued with that product
|
- After that, the transaction is continued with that product as usual
|
||||||
|
- A plugin for [Open Food Facts](https://world.openfoodfacts.org/) is now included and used by default (see the `config.php` option `STOCK_BARCODE_LOOKUP_PLUGIN` and maybe change it as needed)
|
||||||
|
- The product name and image (and of course the barcode itself) are taken over from Open Food Facts to the product being looked up
|
||||||
- Optimized that when moving a product to a freezer location (so when freezing it) the due date will no longer be replaced when the product option "Default due days after freezing" is set to `0`
|
- Optimized that when moving a product to a freezer location (so when freezing it) the due date will no longer be replaced when the product option "Default due days after freezing" is set to `0`
|
||||||
|
|
||||||
### Shopping list
|
### Shopping list
|
||||||
|
@ -66,7 +66,7 @@ Setting('BASE_URL', '/');
|
|||||||
// The plugin to use for external barcode lookups,
|
// The plugin to use for external barcode lookups,
|
||||||
// must be the filename (folder /data/plugins) without the .php extension,
|
// must be the filename (folder /data/plugins) without the .php extension,
|
||||||
// see /data/plugins/DemoBarcodeLookupPlugin.php for an example implementation
|
// see /data/plugins/DemoBarcodeLookupPlugin.php for an example implementation
|
||||||
Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'DemoBarcodeLookupPlugin');
|
Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'OpenFoodFactsBarcodeLookupPlugin');
|
||||||
|
|
||||||
// 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);
|
Setting('DISABLE_URL_REWRITING', false);
|
||||||
|
@ -44,17 +44,23 @@ class DemoBarcodeLookupPlugin extends BaseBarcodeLookupPlugin
|
|||||||
/*
|
/*
|
||||||
This class must implement the protected abstract function ExecuteLookup($barcode),
|
This class must implement the protected abstract function ExecuteLookup($barcode),
|
||||||
which is called with the barcode that needs to be looked up and must return an
|
which is called with the barcode that needs to be looked up and must return an
|
||||||
associative array of the product model or null, when nothing was found for the barcode.
|
associative array of the product model or null, when nothing was found for the barcode
|
||||||
|
|
||||||
The returned array must contain at least these properties:
|
The returned array must be a valid product object (see the "products" database table for all available properties/columns):
|
||||||
array(
|
[
|
||||||
|
// Required properties:
|
||||||
'name' => '',
|
'name' => '',
|
||||||
'location_id' => 1, // A valid id of a location object, check against $this->Locations
|
'location_id' => 1, // A valid id of a location object, check against $this->Locations
|
||||||
'qu_id_purchase' => 1, // A valid id of quantity unit object, check against $this->QuantityUnits
|
'qu_id_purchase' => 1, // A valid id of a quantity unit object, check against $this->QuantityUnits
|
||||||
'qu_id_stock' => 1, // A valid id of quantity unit object, check against $this->QuantityUnits
|
'qu_id_stock' => 1, // A valid id of a quantity unit object, check against $this->QuantityUnits
|
||||||
|
|
||||||
|
// These are virtual properties (not part of the product object, will be automatically handled as needed)
|
||||||
'qu_factor_purchase_to_stock' => 1, // Normally 1 when quantity unit stock and purchase is the same
|
'qu_factor_purchase_to_stock' => 1, // Normally 1 when quantity unit stock and purchase is the same
|
||||||
'barcode' => $barcode // The barcode of the product, maybe just pass through $barcode or manipulate it if necessary
|
'barcode' => $barcode // The barcode of the product, maybe just pass through $barcode or manipulate it if necessary
|
||||||
)
|
|
||||||
|
// Optional virtual properties
|
||||||
|
'image_url' => '' // When provided, the corresponding image will be downloaded and set as the product picture
|
||||||
|
]
|
||||||
*/
|
*/
|
||||||
protected function ExecuteLookup($barcode)
|
protected function ExecuteLookup($barcode)
|
||||||
{
|
{
|
||||||
@ -72,9 +78,9 @@ class DemoBarcodeLookupPlugin extends BaseBarcodeLookupPlugin
|
|||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'name' => 'LookedUpProduct_' . RandomString(5),
|
'name' => 'LookedUpProduct_' . RandomString(5),
|
||||||
'location_id' => $this->Locations[0]->id,
|
'location_id' => $this->Locations[0]->id, // Take the first location as a default
|
||||||
'qu_id_purchase' => $this->QuantityUnits[0]->id,
|
'qu_id_purchase' => $this->QuantityUnits[0]->id, // Take the first QU as a default
|
||||||
'qu_id_stock' => $this->QuantityUnits[0]->id,
|
'qu_id_stock' => $this->QuantityUnits[0]->id, // Take the first QU as a default
|
||||||
'qu_factor_purchase_to_stock' => 1,
|
'qu_factor_purchase_to_stock' => 1,
|
||||||
'barcode' => $barcode
|
'barcode' => $barcode
|
||||||
];
|
];
|
||||||
|
46
data/plugins/OpenFoodFactsBarcodeLookupPlugin.php
Normal file
46
data/plugins/OpenFoodFactsBarcodeLookupPlugin.php
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Grocy\Helpers\BaseBarcodeLookupPlugin;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
|
||||||
|
/*
|
||||||
|
To use this plugin, configure it in data/config.php like this:
|
||||||
|
Setting('STOCK_BARCODE_LOOKUP_PLUGIN', 'OpenFoodFactsBarcodeLookupPlugin');
|
||||||
|
*/
|
||||||
|
|
||||||
|
class OpenFoodFactsBarcodeLookupPlugin extends BaseBarcodeLookupPlugin
|
||||||
|
{
|
||||||
|
protected function ExecuteLookup($barcode)
|
||||||
|
{
|
||||||
|
$webClient = new Client(['http_errors' => false]);
|
||||||
|
$response = $webClient->request('GET', "https://world.openfoodfacts.net/api/v2/product/$barcode?fields=product_name,image_url");
|
||||||
|
$statusCode = $response->getStatusCode();
|
||||||
|
|
||||||
|
// Guzzle throws exceptions for connection errors, so nothing to do on that here
|
||||||
|
|
||||||
|
$data = json_decode($response->getBody());
|
||||||
|
if ($statusCode == 404 || $data->status != 1)
|
||||||
|
{
|
||||||
|
// Nothing found for the given barcode
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
$imageUrl = '';
|
||||||
|
if (isset($data->product->image_url) && !empty($data->product->image_url))
|
||||||
|
{
|
||||||
|
$imageUrl = $data->product->image_url;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'name' => $data->product->product_name,
|
||||||
|
'location_id' => $this->Locations[0]->id,
|
||||||
|
'qu_id_purchase' => $this->QuantityUnits[0]->id,
|
||||||
|
'qu_id_stock' => $this->QuantityUnits[0]->id,
|
||||||
|
'qu_factor_purchase_to_stock' => 1,
|
||||||
|
'barcode' => $barcode,
|
||||||
|
'image_url' => $imageUrl
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -403,7 +403,8 @@ Grocy.FrontendHelpers.ShowGenericError = function(message, exception)
|
|||||||
bootbox.alert({
|
bootbox.alert({
|
||||||
title: __t('Error details'),
|
title: __t('Error details'),
|
||||||
message: '<p class="text-monospace my-0">' + errorDetails + '</p>',
|
message: '<p class="text-monospace my-0">' + errorDetails + '</p>',
|
||||||
closeButton: false
|
closeButton: false,
|
||||||
|
className: "wider"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -257,6 +257,7 @@ $('#product_id_text_input').on('blur', function(e)
|
|||||||
callback: function()
|
callback: function()
|
||||||
{
|
{
|
||||||
Grocy.Components.ProductPicker.PopupOpen = false;
|
Grocy.Components.ProductPicker.PopupOpen = false;
|
||||||
|
Grocy.FrontendHelpers.BeginUiBusy($("form").first().attr("id"));
|
||||||
|
|
||||||
Grocy.Api.Get("stock/barcodes/external-lookup/" + encodeURIComponent(input) + "?add=true",
|
Grocy.Api.Get("stock/barcodes/external-lookup/" + encodeURIComponent(input) + "?add=true",
|
||||||
function(pluginResponse)
|
function(pluginResponse)
|
||||||
@ -264,15 +265,17 @@ $('#product_id_text_input').on('blur', function(e)
|
|||||||
if (pluginResponse == null)
|
if (pluginResponse == null)
|
||||||
{
|
{
|
||||||
toastr.warning(__t("Nothing was found for the given barcode"));
|
toastr.warning(__t("Nothing was found for the given barcode"));
|
||||||
|
Grocy.FrontendHelpers.EndUiBusy($("form").first().attr("id"));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
window.location.href = U("/product/" + pluginResponse.id + "?flow=InplaceNewProductByPlugin&returnto=" + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceNewProductWithName&" + embedded) + "&" + embedded);
|
window.location.href = U("/product/" + pluginResponse.id + "?flow=InplaceNewProductByExternalBarcodeLookupPlugin&returnto=" + encodeURIComponent(Grocy.CurrentUrlRelative + "?flow=InplaceNewProductWithName&" + embedded) + "&" + embedded);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
function(xhr)
|
function(xhr)
|
||||||
{
|
{
|
||||||
Grocy.FrontendHelpers.ShowGenericError("Error while executing the barcode lookup plugin", xhr.response);
|
Grocy.FrontendHelpers.ShowGenericError("Error while executing the barcode lookup plugin", xhr.response);
|
||||||
|
Grocy.FrontendHelpers.EndUiBusy($("form").first().attr("id"));
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ class BaseService
|
|||||||
return LocalizationService::getInstance(GROCY_LOCALE);
|
return LocalizationService::getInstance(GROCY_LOCALE);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getStockservice()
|
protected function getStockService()
|
||||||
{
|
{
|
||||||
return StockService::getInstance();
|
return StockService::getInstance();
|
||||||
}
|
}
|
||||||
@ -66,4 +66,9 @@ class BaseService
|
|||||||
{
|
{
|
||||||
return PrintService::getInstance();
|
return PrintService::getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getFilesService()
|
||||||
|
{
|
||||||
|
return FilesService::getInstance();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@ namespace Grocy\Services;
|
|||||||
|
|
||||||
use Grocy\Helpers\Grocycode;
|
use Grocy\Helpers\Grocycode;
|
||||||
use Grocy\Helpers\WebhookRunner;
|
use Grocy\Helpers\WebhookRunner;
|
||||||
|
use GuzzleHttp\Client;
|
||||||
|
|
||||||
class StockService extends BaseService
|
class StockService extends BaseService
|
||||||
{
|
{
|
||||||
@ -603,7 +604,26 @@ class StockService extends BaseService
|
|||||||
{
|
{
|
||||||
// Add product to database and include new product id in output
|
// Add product to database and include new product id in output
|
||||||
$productData = $pluginOutput;
|
$productData = $pluginOutput;
|
||||||
unset($productData['barcode'], $productData['qu_factor_purchase_to_stock']);
|
unset($productData['barcode'], $productData['qu_factor_purchase_to_stock'], $productData['image_url']); // Virtual lookup plugin properties
|
||||||
|
|
||||||
|
// Download and save image if provided
|
||||||
|
if (isset($pluginOutput['image_url']) && !empty($pluginOutput['image_url']))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
$webClient = new Client();
|
||||||
|
$response = $webClient->request('GET', $pluginOutput['image_url']);
|
||||||
|
$fileName = $pluginOutput['barcode'] . '.' . pathinfo($pluginOutput['image_url'], PATHINFO_EXTENSION);
|
||||||
|
$fileHandle = fopen($this->getFilesService()->GetFilePath('productpictures', $fileName), 'wb');
|
||||||
|
fwrite($fileHandle, $response->getBody());
|
||||||
|
fclose($fileHandle);
|
||||||
|
$productData['picture_file_name'] = $fileName;
|
||||||
|
}
|
||||||
|
catch (\Exception)
|
||||||
|
{
|
||||||
|
// Ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$newProductRow = $this->getDatabase()->products()->createRow($productData);
|
$newProductRow = $this->getDatabase()->products()->createRow($productData);
|
||||||
$newProductRow->save();
|
$newProductRow->save();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user