Grocycode: Webhooks & Labelprinter Feature

This commit is contained in:
Katharina Bogad 2021-06-08 16:26:12 +02:00
parent 3bcb0e696a
commit 074f045f40
12 changed files with 434 additions and 9 deletions

View File

@ -12,7 +12,8 @@
"erusev/parsedown": "^1.7",
"gumlet/php-image-resize": "^1.9",
"ezyang/htmlpurifier": "^4.13",
"jucksearm/php-barcode": "^1.0"
"jucksearm/php-barcode": "^1.0",
"guzzlehttp/guzzle": "^7.0"
},
"autoload": {
"psr-4": {

287
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "2f15993a36a55cfc2018969a144c73b0",
"content-hash": "b8b8e77618038c44c21edac2c31c4b67",
"packages": [
{
"name": "doctrine/inflector",
@ -508,6 +508,239 @@
},
"time": "2019-01-01T13:53:00+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "7.3.0",
"source": {
"type": "git",
"url": "https://github.com/guzzle/guzzle.git",
"reference": "7008573787b430c1c1f650e3722d9bba59967628"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/guzzle/zipball/7008573787b430c1c1f650e3722d9bba59967628",
"reference": "7008573787b430c1c1f650e3722d9bba59967628",
"shasum": ""
},
"require": {
"ext-json": "*",
"guzzlehttp/promises": "^1.4",
"guzzlehttp/psr7": "^1.7 || ^2.0",
"php": "^7.2.5 || ^8.0",
"psr/http-client": "^1.0"
},
"provide": {
"psr/http-client-implementation": "1.0"
},
"require-dev": {
"bamarni/composer-bin-plugin": "^1.4.1",
"ext-curl": "*",
"php-http/client-integration-tests": "^3.0",
"phpunit/phpunit": "^8.5.5 || ^9.3.5",
"psr/log": "^1.1"
},
"suggest": {
"ext-curl": "Required for CURL handler support",
"ext-intl": "Required for Internationalized Domain Name (IDN) support",
"psr/log": "Required for using the Log middleware"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "7.3-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Márk Sági-Kazár",
"email": "mark.sagikazar@gmail.com",
"homepage": "https://sagikazarmark.hu"
}
],
"description": "Guzzle is a PHP HTTP client library",
"homepage": "http://guzzlephp.org/",
"keywords": [
"client",
"curl",
"framework",
"http",
"http client",
"psr-18",
"psr-7",
"rest",
"web service"
],
"support": {
"issues": "https://github.com/guzzle/guzzle/issues",
"source": "https://github.com/guzzle/guzzle/tree/7.3.0"
},
"funding": [
{
"url": "https://github.com/GrahamCampbell",
"type": "github"
},
{
"url": "https://github.com/Nyholm",
"type": "github"
},
{
"url": "https://github.com/alexeyshockov",
"type": "github"
},
{
"url": "https://github.com/gmponos",
"type": "github"
}
],
"time": "2021-03-23T11:33:13+00:00"
},
{
"name": "guzzlehttp/promises",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/guzzle/promises.git",
"reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/promises/zipball/8e7d04f1f6450fef59366c399cfad4b9383aa30d",
"reference": "8e7d04f1f6450fef59366c399cfad4b9383aa30d",
"shasum": ""
},
"require": {
"php": ">=5.5"
},
"require-dev": {
"symfony/phpunit-bridge": "^4.4 || ^5.1"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.4-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Promise\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
}
],
"description": "Guzzle promises library",
"keywords": [
"promise"
],
"support": {
"issues": "https://github.com/guzzle/promises/issues",
"source": "https://github.com/guzzle/promises/tree/1.4.1"
},
"time": "2021-03-07T09:25:29+00:00"
},
{
"name": "guzzlehttp/psr7",
"version": "1.8.2",
"source": {
"type": "git",
"url": "https://github.com/guzzle/psr7.git",
"reference": "dc960a912984efb74d0a90222870c72c87f10c91"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/guzzle/psr7/zipball/dc960a912984efb74d0a90222870c72c87f10c91",
"reference": "dc960a912984efb74d0a90222870c72c87f10c91",
"shasum": ""
},
"require": {
"php": ">=5.4.0",
"psr/http-message": "~1.0",
"ralouphie/getallheaders": "^2.0.5 || ^3.0.0"
},
"provide": {
"psr/http-message-implementation": "1.0"
},
"require-dev": {
"ext-zlib": "*",
"phpunit/phpunit": "~4.8.36 || ^5.7.27 || ^6.5.14 || ^7.5.20 || ^8.5.8 || ^9.3.10"
},
"suggest": {
"laminas/laminas-httphandlerrunner": "Emit PSR-7 responses"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.7-dev"
}
},
"autoload": {
"psr-4": {
"GuzzleHttp\\Psr7\\": "src/"
},
"files": [
"src/functions_include.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Michael Dowling",
"email": "mtdowling@gmail.com",
"homepage": "https://github.com/mtdowling"
},
{
"name": "Tobias Schultze",
"homepage": "https://github.com/Tobion"
}
],
"description": "PSR-7 message implementation that also provides common utility methods",
"keywords": [
"http",
"message",
"psr-7",
"request",
"response",
"stream",
"uri",
"url"
],
"support": {
"issues": "https://github.com/guzzle/psr7/issues",
"source": "https://github.com/guzzle/psr7/tree/1.8.2"
},
"time": "2021-04-26T09:17:50+00:00"
},
{
"name": "illuminate/container",
"version": "v5.8.36",
@ -1412,6 +1645,58 @@
},
"time": "2017-02-14T16:28:37+00:00"
},
{
"name": "psr/http-client",
"version": "1.0.1",
"source": {
"type": "git",
"url": "https://github.com/php-fig/http-client.git",
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/http-client/zipball/2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
"reference": "2dfb5f6c5eff0e91e20e913f8c5452ed95b86621",
"shasum": ""
},
"require": {
"php": "^7.0 || ^8.0",
"psr/http-message": "^1.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Http\\Client\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "http://www.php-fig.org/"
}
],
"description": "Common interface for HTTP clients",
"homepage": "https://github.com/php-fig/http-client",
"keywords": [
"http",
"http-client",
"psr",
"psr-18"
],
"support": {
"source": "https://github.com/php-fig/http-client/tree/master"
},
"time": "2020-06-29T06:28:15+00:00"
},
{
"name": "psr/http-factory",
"version": "1.0.1",

View File

@ -159,6 +159,21 @@ DefaultUserSetting('quagga2_patchsize', 'medium');
DefaultUserSetting('quagga2_frequency', 10);
DefaultUserSetting('quagga2_debug', true);
// Label Printer Settings
// This is the URI that grocy will POST to when asked to print a label.
Setting('LABEL_PRINTER_WEBHOOK', '');
// This setting decides whether the webhook will be called server- or clientside.
// If the machine grocy runs on has a network connection to the host
// the webhook receiver is on, this is probably a good idea.
// If, for example, grocy runs in the cloud and your printer daemon
// runs locally to you, set this to false to let your browser call
// the webhook instead.
Setting('LABEL_PRINTER_RUN_SERVER', true);
// Additional Parameters supplied to the webhook.
Setting('LABEL_PRINTER_PARAMS', ['font_family' => 'Source Sans Pro (Regular)']);
// Use JSON or normal POST request variables?
Setting('LABEL_PRINTER_HOOK_JSON', false);
// Feature flags
// grocy was initially about "stock management for your household", many other things
// came and still come by, because they are useful - here you can disable the parts
@ -172,6 +187,7 @@ Setting('FEATURE_FLAG_TASKS', true);
Setting('FEATURE_FLAG_BATTERIES', true);
Setting('FEATURE_FLAG_EQUIPMENT', true);
Setting('FEATURE_FLAG_CALENDAR', true);
Setting('FEATURE_FLAG_LABELPRINTER', true);
// Sub feature flags
Setting('FEATURE_FLAG_STOCK_PRICE_TRACKING', true);

View File

@ -4,6 +4,7 @@ namespace Grocy\Controllers;
use Grocy\Controllers\Users\User;
use Grocy\Services\StockService;
use Grocy\Helpers\WebhookRunner;
class StockApiController extends BaseApiController
{
@ -138,8 +139,14 @@ class StockApiController extends BaseApiController
{
$transactionType = $requestBody['transactiontype'];
}
$runPrinterWebhook = false;
if (array_key_exists('print_stock_label', $requestBody) && boolval($requestBody['print_stock_label']))
{
$runPrinterWebhook = true;
}
$transactionId = $this->getStockService()->AddProduct($args['productId'], $requestBody['amount'], $bestBeforeDate, $transactionType, $purchasedDate, $price, $locationId, $shoppingLocationId, $unusedTransactionId, $runPrinterWebhook);
$transactionId = $this->getStockService()->AddProduct($args['productId'], $requestBody['amount'], $bestBeforeDate, $transactionType, $purchasedDate, $price, $locationId, $shoppingLocationId);
$args['transactionId'] = $transactionId;
return $this->StockTransactions($request, $response, $args);
}

48
helpers/WebhookRunner.php Normal file
View File

@ -0,0 +1,48 @@
<?php
namespace Grocy\Helpers;
use GuzzleHttp\Client;
use GuzzleHttp\ExceptionRequestException;
use Psr\Http\Message\ResponseInterface;
class WebhookRunner
{
private $client;
public function __construct()
{
$this->client = new Client(['timeout' => 2.0]);
}
public function run($url, $args, $json = false)
{
$reqArgs = [];
if ($json)
{
$reqArgs = ['json' => $args];
}
else
{
$reqArgs = ['form_params' => $args];
}
try
{
file_put_contents('php://stderr', 'Running Webhook: ' . $url . "\n" . print_r($reqArgs, true));
$this->client->request('POST', $url, $reqArgs);
}
catch (RequestException $e)
{
file_put_contents('php://stderr', 'Webhook failed: ' . $url . "\n" . $e->getMessage());
}
}
public function runAll($urls, $args)
{
foreach ($urls as $url)
{
$this->run($url, $args);
}
}
}

View File

@ -2394,4 +2394,7 @@ msgid "Open print label in new Window"
msgstr "Druck-Label in neuem Fenster öffnen"
msgid "DD"
msgstr "MHD"
msgstr "MHD"
msgid "Print stock label"
msgstr "Etikett drucken"

View File

@ -2106,3 +2106,6 @@ msgstr ""
msgid "DD"
msgstr ""
msgid "Print stock label"
msgstr ""

View File

@ -476,6 +476,16 @@ Grocy.FrontendHelpers.DeleteUserSetting = function(settingsKey, reloadPageOnSucc
);
}
Grocy.FrontendHelpers.RunWebhook = function(webhook, data)
{
Object.assign(data, webhook.extra_data);
$.post(webhook.hook, data).fail(function(req, status, errorThrown)
{
Grocy.FrontendHelpers.ShowGenericError(__t("Unable to connect to webhook.", { "status": status, "errorThrown": errorThrown }));
});
}
$(document).on("keyup paste change", "input, textarea", function()
{
$(this).closest("form").addClass("is-dirty");

View File

@ -23,6 +23,7 @@ $('#save-purchase-button').on('click', function(e)
{
var jsonData = {};
jsonData.amount = jsonForm.amount;
jsonData.print_stock_label = jsonForm.print_stock_label
if (!Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING)
{
@ -116,6 +117,21 @@ $('#save-purchase-button').on('click', function(e)
}
var successMessage = __t('Added %1$s of %2$s to stock', amountMessage + " " + __n(amountMessage, productDetails.quantity_unit_stock.name, productDetails.quantity_unit_stock.name_plural), productDetails.product.name) + '<br><a class="btn btn-secondary btn-sm mt-2" href="#" onclick="UndoStockTransaction(\'' + result[0].transaction_id + '\')"><i class="fas fa-undo"></i> ' + __t("Undo") + '</a>';
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_LABELPRINTER)
{
if (Grocy.Webhooks.labelprinter !== undefined)
{
var post_data = {};
post_data.product = productDetails.product.name;
post_data.grocycode = 'grcy:p:' + jsonForm.product_id + ":" + result[0].stock_id
if (Grocy.FeatureFlags.GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING)
{
post_data.duedate = __t('DD') + ': ' + result[0].best_before_date
}
Grocy.FrontendHelpers.RunWebhook(Grocy.Webhooks.labelprinter, post_data);
}
}
if (GetUriParam("embedded") !== undefined)
{
window.parent.postMessage(WindowMessageBag("ProductChanged", jsonForm.product_id), Grocy.BaseUrl);

View File

@ -1,7 +1,9 @@
<?php
namespace Grocy\Services;
use Grocy\Helpers\Grocycode;
use Grocy\Helpers\WebhookRunner;
class StockService extends BaseService
{
@ -34,7 +36,7 @@ class StockService extends BaseService
if ($alreadyExistingEntry)
{ // Update
if ($alreadyExistingEntry->amount < $amountToAdd)
{
{
$alreadyExistingEntry->update([
'amount' => $amountToAdd,
'shopping_list_id' => $listId
@ -103,7 +105,7 @@ class StockService extends BaseService
}
}
public function AddProduct(int $productId, float $amount, $bestBeforeDate, $transactionType, $purchasedDate, $price, $locationId = null, $shoppingLocationId = null, &$transactionId = null)
public function AddProduct(int $productId, float $amount, $bestBeforeDate, $transactionType, $purchasedDate, $price, $locationId = null, $shoppingLocationId = null, &$transactionId = null, $runWebhook = false, &$webhookData = null)
{
if (!$this->ProductExists($productId))
{
@ -178,6 +180,17 @@ class StockService extends BaseService
]);
$stockRow->save();
if (GROCY_FEATURE_FLAG_LABELPRINTER && GROCY_LABEL_PRINTER_RUN_SERVER && $runWebhook)
{
$webhookData = array_merge([
'product' => $productDetails->product->name,
'grocycode' => (string)(new Grocycode(Grocycode::PRODUCT, $productId, [$stockId])),
'duedate' => $this->getLocalizationService()->__t('DD') . ': ' . $bestBeforeDate,
], GROCY_LABEL_PRINTER_PARAMS);
(new WebhookRunner())->run(GROCY_LABEL_PRINTER_WEBHOOK, $webhookData, GROCY_LABEL_PRINTER_HOOK_JSON);
}
return $transactionId;
}
else
@ -452,7 +465,7 @@ class StockService extends BaseService
if ($pluginOutput !== null)
{ // Lookup was successful
if ($addFoundProduct === true)
{
{
// Add product to database and include new product id in output
$newRow = $this->getDatabase()->products()->createRow($pluginOutput);
$newRow->save();

View File

@ -95,6 +95,14 @@
Grocy.CalendarShowWeekNumbers = {{ BoolToString(GROCY_CALENDAR_SHOW_WEEK_OF_YEAR) }};
Grocy.GettextPo = {!! $GettextPo !!};
Grocy.FeatureFlags = {!! json_encode($featureFlags) !!};
Grocy.Webhooks = {
@if(GROCY_FEATURE_FLAG_LABELPRINTER && !GROCY_LABEL_PRINTER_RUN_SERVER)
"labelprinter" : {
"hook" : "{{ GROCY_LABEL_PRINTER_WEBHOOK}}",
"extra_data" : {!! json_encode(GROCY_LABEL_PRINTER_PARAMS) !!}
}
@endif
};
@if (GROCY_AUTHENTICATED)
Grocy.UserSettings = {!! json_encode($userSettings) !!};

View File

@ -10,9 +10,9 @@
@section('content')
<script>
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
Grocy.QuantityUnits = {!! json_encode($quantityUnits) !!};
Grocy.QuantityUnitConversionsResolved = {!! json_encode($quantityUnitConversionsResolved) !!};
Grocy.DefaultMinAmount = '{{$DEFAULT_MIN_AMOUNT}}';
Grocy.DefaultMinAmount = '{{ $DEFAULT_MIN_AMOUNT }}';
</script>
<div class="row">
@ -148,6 +148,21 @@
))
@endif
@if(GROCY_FEATURE_FLAG_LABELPRINTER)
<div class="form-group mt-n2">
<div class="custom-control custom-checkbox">
<input class="form-check-input custom-control-input"
type="checkbox"
id="print_stock_label"
name="print_stock_label"
value="1">
<label class="form-check-label custom-control-label"
for="print_stock_label">{{ $__t("Print stock label") }}
</label>
</div>
</div>
@endif
<button id="save-purchase-button"
class="btn btn-success d-block">{{ $__t('OK') }}</button>