diff --git a/controllers/StockController.php b/controllers/StockController.php
index 6e3a33a1..d42d9a36 100644
--- a/controllers/StockController.php
+++ b/controllers/StockController.php
@@ -172,6 +172,34 @@ class StockController extends BaseController
}
}
+ public function ProductGrocycodeImage(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
+ {
+ $product = $this->getDatabase()->products($args['productId']);
+
+ $gc = new Grocycode(Grocycode::PRODUCT, $product->id);
+ $png = (new DatamatrixFactory())->setCode((string) $gc)->getDatamatrixPngData();
+
+ $isDownload = $request->getQueryParam('download', false);
+
+ if ($isDownload)
+ {
+ $response = $response->withHeader('Content-Type', 'application/octet-stream')
+ ->withHeader('Content-Disposition', 'attachment; filename=grocycode.png')
+ ->withHeader('Content-Length', strlen($png))
+ ->withHeader('Cache-Control', 'no-cache')
+ ->withHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT');
+ }
+ else
+ {
+ $response = $response->withHeader('Content-Type', 'image/png')
+ ->withHeader('Content-Length', strlen($png))
+ ->withHeader('Cache-Control', 'no-cache')
+ ->withHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT');
+ }
+ $response->getBody()->write($png);
+ return $response;
+ }
+
public function ProductGroupEditForm(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
if ($args['productGroupId'] == 'new')
@@ -436,11 +464,24 @@ class StockController extends BaseController
$gc = new Grocycode(Grocycode::PRODUCT, $stockEntry->product_id, [$stockEntry->stock_id]);
$png = (new DatamatrixFactory())->setCode((string) $gc)->getDatamatrixPngData();
- $response = $response->withHeader('Content-Type', 'image/png')
+ $isDownload = $request->getQueryParam('download', false);
+
+ if ($isDownload)
+ {
+ $response = $response->withHeader('Content-Type', 'application/octet-stream')
+ ->withHeader('Content-Disposition', 'attachment; filename=grocycode.png')
->withHeader('Content-Length', strlen($png))
->withHeader('Cache-Control', 'no-cache')
->withHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT');
- $response->getBody()->write($png);
+ }
+ else
+ {
+ $response = $response->withHeader('Content-Type', 'image/png')
+ ->withHeader('Content-Length', strlen($png))
+ ->withHeader('Cache-Control', 'no-cache')
+ ->withHeader('Last-Modified', gmdate('D, d M Y H:i:s') . ' GMT');
+ $response->getBody()->write($png);
+ }
return $response;
}
diff --git a/localization/de/strings.po b/localization/de/strings.po
index 66135bc1..048556dd 100644
--- a/localization/de/strings.po
+++ b/localization/de/strings.po
@@ -2374,3 +2374,18 @@ msgstr "Druckoptionen"
msgid "A product or a note is required"
msgstr "Ein Produkt oder eine Notiz ist erforderlich"
+
+msgid "Grocycode"
+msgstr "Grocycode"
+
+msgid "Download"
+msgstr "Herunterladen"
+
+msgid "Download stock entry grocycode"
+msgstr "Bestandseingrags-Grocycode herunterladen"
+
+msgid "Download product grocycode"
+msgstr "Produkt-Grocycode herunterladen"
+
+msgid "Grocycode is a unique referer to this product in your grocy instance. Print it onto a label and scan it like any other barcode!"
+msgstr "Grocycode ist eine eindeutige Referenz zu diesem Produkt in dieser Grocy-Instanz. Ausgedruckt kann es wie jeder andere Barcode gescannt werden!"
\ No newline at end of file
diff --git a/localization/strings.pot b/localization/strings.pot
index 6ce941ba..b32ec66d 100644
--- a/localization/strings.pot
+++ b/localization/strings.pot
@@ -2077,3 +2077,26 @@ msgstr ""
msgid "A product or a note is required"
msgstr ""
+
+msgid "Grocycode"
+msgstr ""
+
+msgid "Download"
+msgstr ""
+
+msgid "Download stock entry grocycode"
+msgstr ""
+
+msgid "Download product grocycode"
+msgstr ""
+
+msgid ""
+"If enabled, the min. stock amount of sub products will be accumulated into this product, means the sub product will never be \"\n"
+"\t\t\t\t\t\t\tmissing\",\n"
+"\t\t\t\t\t\t\tonly\n"
+"\t\t\t\t\t\t\tthis\n"
+"\t\t\t\t\t\t\tproduct"
+msgstr ""
+
+msgid "Grocycode is a unique referer to this product in your grocy instance. Print it onto a label and scan it like any other barcode!"
+msgstr ""
diff --git a/public/viewjs/components/productcard.js b/public/viewjs/components/productcard.js
index f9bf3215..aaa00669 100644
--- a/public/viewjs/components/productcard.js
+++ b/public/viewjs/components/productcard.js
@@ -23,6 +23,10 @@ Grocy.Components.ProductCard.Refresh = function(productId)
}
$('#productcard-product-spoil-rate').text((parseFloat(productDetails.spoil_rate_percent) / 100).toLocaleString(undefined, { style: "percent" }));
+ // grocycode
+ $("#productcard-product-grocycode-image").attr("src", U('/product/' + productDetails.product.id + '/grocycode'));
+ $("#productcard-product-grocycode-image-link").attr("href", U('/product/' + productDetails.product.id + '/grocycode?download=true'));
+
if (productDetails.is_aggregated_amount == 1)
{
$('#productcard-product-stock-amount-aggregated').text(productDetails.stock_amount_aggregated);
diff --git a/routes.php b/routes.php
index 8b5dcc23..5a146491 100644
--- a/routes.php
+++ b/routes.php
@@ -5,8 +5,7 @@ use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Routing\RouteCollectorProxy;
-$app->group('', function (RouteCollectorProxy $group)
-{
+$app->group('', function (RouteCollectorProxy $group) {
// System routes
$group->get('/', '\Grocy\Controllers\SystemController:Root')->setName('root');
$group->get('/about', '\Grocy\Controllers\SystemController:About');
@@ -41,6 +40,7 @@ $app->group('', function (RouteCollectorProxy $group)
$group->get('/quantityunitconversion/{quConversionId}', '\Grocy\Controllers\StockController:QuantityUnitConversionEditForm');
$group->get('/productgroups', '\Grocy\Controllers\StockController:ProductGroupsList');
$group->get('/productgroup/{productGroupId}', '\Grocy\Controllers\StockController:ProductGroupEditForm');
+ $group->get('/product/{productId}/grocycode', '\Grocy\Controllers\StockController:ProductGrocycodeImage');
// Stock handling routes
if (GROCY_FEATURE_FLAG_STOCK)
@@ -140,8 +140,7 @@ $app->group('', function (RouteCollectorProxy $group)
$group->get('/manageapikeys/new', '\Grocy\Controllers\OpenApiController:CreateNewApiKey');
});
-$app->group('/api', function (RouteCollectorProxy $group)
-{
+$app->group('/api', function (RouteCollectorProxy $group) {
// OpenAPI
$group->get('/openapi/specification', '\Grocy\Controllers\OpenApiController:DocumentationSpec');
@@ -247,7 +246,6 @@ $app->group('/api', function (RouteCollectorProxy $group)
})->add(JsonMiddleware::class);
// Handle CORS preflight OPTIONS requests
-$app->options('/api/{routes:.+}', function (Request $request, Response $response): Response
-{
+$app->options('/api/{routes:.+}', function (Request $request, Response $response): Response {
return $response->withStatus(204);
});
diff --git a/views/components/productcard.blade.php b/views/components/productcard.blade.php
index f159af70..7da09ef9 100644
--- a/views/components/productcard.blade.php
+++ b/views/components/productcard.blade.php
@@ -55,6 +55,11 @@
@if (GROCY_FEATURE_FLAG_STOCK_PRICE_TRACKING){{ $__t('Average price') }}:
@endif
@if (GROCY_FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_TRACKING){{ $__t('Average shelf life') }}:
@endif
{{ $__t('Spoil rate') }}:
+
{{ $__t('Grocycode') }}:
{{ $__t('Download') }}
{{ $__t('No price history available') }}
@endif
-
+
\ No newline at end of file
diff --git a/views/productform.blade.php b/views/productform.blade.php
index 144ed05e..7670b324 100644
--- a/views/productform.blade.php
+++ b/views/productform.blade.php
@@ -25,7 +25,11 @@
@if($mode == 'edit')
@if(!empty($product->picture_file_name))
@@ -145,7 +149,12 @@
for="cumulate_min_stock_amount_of_sub_products">{{ $__t('Accumulate sub products min. stock amount') }}
+ title="{{ $__t('If enabled, the min. stock amount of sub products will be accumulated into this product, means the sub product will never be "
+ missing",
+ only
+ this
+ product')
+ }}">
@@ -426,7 +435,24 @@