Skip to content

Commit

Permalink
Add support for printing shoppinglist with thermal printer (#1273)
Browse files Browse the repository at this point in the history
* Added escpos-php library

* Added button to shoppinglist print menu

* Added to translation

* Added basic printing logic and API call

* Working implementation for printing with the API

* Added openapi json

* Correctly parsing boolean parameter

* Working button in UI

* Change to grocy formatting

* Add Date

* Only show thermal print button when Feature Flag is set

* Fixed API call and added error message parsing

* Undo translation

* Add flag to print quantities as well

* Added printing notes

* Added quantity conversion

* Increse feed

* Fixed that checkbox was undefined, as dialog was already closed

* Added padding

* Formatting

* Added note about user permission

* Fixed error when using notes instead of products

* Review

- Default FEATURE_FLAG_THERMAL_PRINTER to disabled
- Added missing localization strings (and slightly adjusted one)

* Fixed merge conflicts

Co-authored-by: Bernd Bestel <bernd@berrnd.de>
  • Loading branch information
Forceu and berrnd committed Jun 18, 2021
1 parent fe59fac commit eb135ae
Show file tree
Hide file tree
Showing 14 changed files with 410 additions and 46 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"gumlet/php-image-resize": "^1.9",
"ezyang/htmlpurifier": "^4.13",
"jucksearm/php-barcode": "^1.0",
"guzzlehttp/guzzle": "^7.0"
"guzzlehttp/guzzle": "^7.0",
"mike42/escpos-php": "^3.0"
},
"autoload": {
"psr-4": {
Expand Down
17 changes: 17 additions & 0 deletions config-dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,22 @@
// see the file controllers/Users/User.php for possible values
Setting('DEFAULT_PERMISSIONS', ['ADMIN']);

// When using a thermal printer (thermal printers are receipt printers, not regular printers)
// The printer must support the ESC/POS protocol, see https://github.com/mike42/escpos-php
Setting('TPRINTER_IS_NETWORK_PRINTER', false); // Set to true if it is a network printer
Setting('TPRINTER_PRINT_QUANTITY_NAME', true); // Set to false if you do not want to print the quantity names
Setting('TPRINTER_PRINT_NOTES', true); // Set to false if you do not want to print notes

//Configuration below for network printers. If you are using a USB/serial printer, skip to next section
Setting('TPRINTER_IP', '127.0.0.1'); // IP of the network printer
Setting('TPRINTER_PORT', 9100); // Port of printer, eg. 9100
//Configuration below if you are using a USB or serial printer
Setting('TPRINTER_CONNECTOR', '/dev/usb/lp0'); // Location of printer. For USB on Linux this is often '/dev/usb/lp0',
// for serial printers it could be similar to '/dev/ttyS0'
// Make sure that the user that runs the webserver has permissions to write to the printer!
// On Linux add your webserver user to the LP group with usermod -a -G lp www-data


// Default user settings
// These settings can be changed per user, here the defaults
// are defined which are used when the user has not changed the setting so far
Expand Down Expand Up @@ -198,6 +214,7 @@
Setting('FEATURE_FLAG_STOCK_BEST_BEFORE_DATE_FIELD_NUMBER_PAD', true); // Activate the number pad in due date fields on (supported) mobile browsers
Setting('FEATURE_FLAG_SHOPPINGLIST_MULTIPLE_LISTS', true);
Setting('FEATURE_FLAG_CHORES_ASSIGNMENTS', true);
Setting('FEATURE_FLAG_THERMAL_PRINTER', false);

// Feature settings
Setting('FEATURE_SETTING_STOCK_COUNT_OPENED_PRODUCTS_AGAINST_MINIMUM_STOCK_AMOUNT', true); // When set to true, opened items will be counted as missing for calculating if a product is below its minimum stock amount
Expand Down
7 changes: 7 additions & 0 deletions controllers/BaseController.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
use Grocy\Services\DatabaseService;
use Grocy\Services\FilesService;
use Grocy\Services\LocalizationService;
use Grocy\Services\PrintService;
use Grocy\Services\RecipesService;
use Grocy\Services\SessionService;
use Grocy\Services\StockService;
Expand Down Expand Up @@ -93,6 +94,12 @@ protected function getStockService()
return StockService::getInstance();
}

protected function getPrintService()
{
return PrintService::getInstance();
}


protected function getTasksService()
{
return TasksService::getInstance();
Expand Down
41 changes: 41 additions & 0 deletions controllers/PrintApiController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<?php

namespace Grocy\Controllers;

use Grocy\Controllers\Users\User;
use Grocy\Services\StockService;

class PrintApiController extends BaseApiController
{

public function PrintShoppingListThermal(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args) {

try
{
User::checkPermission($request, User::PERMISSION_SHOPPINGLIST);

$params = $request->getQueryParams();

$listId = 1;
if (isset($params['list'])) {
$listId = $params['list'];
}

$printHeader = true;
if (isset($params['printHeader'])) {
$printHeader = ($params['printHeader'] === "true");
}
$items = $this->getStockService()->GetShoppinglistInPrintableStrings($listId);
return $this->ApiResponse($response, $this->getPrintService()->printShoppingList($printHeader, $items));
}
catch (\Exception $ex)
{
return $this->GenericErrorResponse($response, $ex->getMessage());
}
}

public function __construct(\DI\Container $container)
{
parent::__construct($container);
}
}
1 change: 0 additions & 1 deletion controllers/StockApiController.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<?php

namespace Grocy\Controllers;

use Grocy\Controllers\Users\User;
Expand Down
1 change: 1 addition & 0 deletions controllers/StockController.php
Original file line number Diff line number Diff line change
Expand Up @@ -512,6 +512,7 @@ public function StockSettings(\Psr\Http\Message\ServerRequestInterface $request,
]);
}


public function Stockentries(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
{
$usersService = $this->getUsersService();
Expand Down
60 changes: 60 additions & 0 deletions grocy.openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,9 @@
},
{
"name": "Files"
},
{
"name": "Print"
}
],
"paths": {
Expand Down Expand Up @@ -4030,7 +4033,64 @@
}
}
}
},
"/print/shoppinglist/thermal": {
"get": {
"summary": "Prints the shoppinglist with a thermal printer",
"tags": [
"Print"
],
"parameters": [
{
"in": "query",
"name": "list",
"required": false,
"description": "Shopping list id",
"schema": {
"type": "integer",
"default": 1
}
},
{
"in": "query",
"name": "printHeader",
"required": false,
"description": "Prints grocy logo if true",
"schema": {
"type": "boolean",
"default": true
}
}
],
"responses": {
"200": {
"description": "Returns OK if the printing was successful",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"result": {
"type": "string"
}
}
}
}
}
},
"400": {
"description": "The operation was not successful",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Error400"
}
}
}
}
}
}
}
},
"components": {
"internalSchemas": {
Expand Down
2 changes: 1 addition & 1 deletion helpers/PrerequisiteChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ class ERequirementNotMet extends Exception
{
}

const REQUIRED_PHP_EXTENSIONS = ['fileinfo', 'pdo_sqlite', 'gd', 'ctype'];
const REQUIRED_PHP_EXTENSIONS = ['fileinfo', 'pdo_sqlite', 'gd', 'ctype', 'json', 'intl', 'zlib'];
const REQUIRED_SQLITE_VERSION = '3.9.0';

class PrerequisiteChecker
Expand Down
13 changes: 13 additions & 0 deletions localization/strings.pot
Original file line number Diff line number Diff line change
Expand Up @@ -2129,3 +2129,16 @@ msgstr ""

msgid "Open stock entry print label in new window"
msgstr ""

msgid "Thermal printer"
msgstr ""

msgid "Printing"
msgstr ""

msgid "Connecting to printer..."
msgstr ""

msgid "Unable to print"
msgstr ""

136 changes: 93 additions & 43 deletions public/viewjs/shoppinglist.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var shoppingListTable = $('#shoppinglist-table').DataTable({
var shoppingListTable = $('#shoppinglist-table').DataTable({
'order': [[1, 'asc']],
"orderFixed": [[3, 'asc']],
'columnDefs': [
Expand Down Expand Up @@ -428,56 +428,106 @@ $(document).on("click", "#print-shopping-list-button", function(e)
</label> \
</div>';

bootbox.dialog({
message: dialogHtml,
size: 'small',
backdrop: true,
closeButton: false,
className: "d-print-none",
buttons: {
cancel: {
label: __t('Cancel'),
className: 'btn-secondary',
callback: function()
{
bootbox.hideAll();
}
},
ok: {
label: __t('Print'),
className: 'btn-primary responsive-button',
callback: function()
var sizePrintDialog = 'medium';
var printButtons = {
cancel: {
label: __t('Cancel'),
className: 'btn-secondary',
callback: function()
{
bootbox.hideAll();
}
},
printtp: {
label: __t('Thermal printer'),
className: 'btn-secondary',
callback: function()
{
bootbox.hideAll();
var printHeader = $("#print-show-header").prop("checked");
var thermalPrintDialog = bootbox.dialog({
title: __t('Printing'),
message: '<p><i class="fa fa-spin fa-spinner"></i> ' + __t('Connecting to printer...') + '</p>'
});
//Delaying for one second so that the alert can be closed
setTimeout(function()
{
bootbox.hideAll();
$('.modal-backdrop').remove();

$(".print-timestamp").text(moment().format("l LT"));
Grocy.Api.Get('print/shoppinglist/thermal?list=' + $("#selected-shopping-list").val() + '&printHeader=' + printHeader,
function(result)
{
bootbox.hideAll();
},
function(xhr)
{
console.error(xhr);
var validResponse = true;
try
{
var jsonError = JSON.parse(xhr.responseText);
} catch (e)
{
validResponse = false;
}
if (validResponse)
{
thermalPrintDialog.find('.bootbox-body').html(__t('Unable to print') + '<br><pre><code>' + jsonError.error_message + '</pre></code>');
} else
{
thermalPrintDialog.find('.bootbox-body').html(__t('Unable to print') + '<br><pre><code>' + xhr.responseText + '</pre></code>');
}
}
);
}, 1000);
}
},
ok: {
label: __t('Print'),
className: 'btn-primary responsive-button',
callback: function()
{
bootbox.hideAll();
$('.modal-backdrop').remove();
$(".print-timestamp").text(moment().format("l LT"));

$("#description-for-print").html($("#description").val());
if ($("#description").text().isEmpty())
{
$("#description-for-print").parent().addClass("d-print-none");
}
$("#description-for-print").html($("#description").val());
if ($("#description").text().isEmpty())
{
$("#description-for-print").parent().addClass("d-print-none");
}

if (!$("#print-show-header").prop("checked"))
{
$("#print-header").addClass("d-none");
}
if (!$("#print-show-header").prop("checked"))
{
$("#print-header").addClass("d-none");
}

if (!$("#print-group-by-product-group").prop("checked"))
{
shoppingListPrintShadowTable.rowGroup().enable(false);
shoppingListPrintShadowTable.order.fixed({});
shoppingListPrintShadowTable.draw();
}
if (!$("#print-group-by-product-group").prop("checked"))
{
shoppingListPrintShadowTable.rowGroup().enable(false);
shoppingListPrintShadowTable.order.fixed({});
shoppingListPrintShadowTable.draw();
}

$(".print-layout-container").addClass("d-none");
$("." + $("input[name='print-layout-type']:checked").val()).removeClass("d-none");
$(".print-layout-container").addClass("d-none");
$("." + $("input[name='print-layout-type']:checked").val()).removeClass("d-none");

window.print();
}
window.print();
}
}
}

if (!Grocy.FeatureFlags["GROCY_FEATURE_FLAG_THERMAL_PRINTER"])
{
delete printButtons['printtp'];
sizePrintDialog = 'small';
}

bootbox.dialog({
message: dialogHtml,
size: sizePrintDialog,
backdrop: true,
closeButton: false,
className: "d-print-none",
buttons: printButtons
});
});

Expand Down
3 changes: 3 additions & 0 deletions routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,9 @@
$group->post('/chores/executions/{executionId}/undo', '\Grocy\Controllers\ChoresApiController:UndoChoreExecution');
$group->post('/chores/executions/calculate-next-assignments', '\Grocy\Controllers\ChoresApiController:CalculateNextExecutionAssignments');

//Printing
$group->get('/print/shoppinglist/thermal', '\Grocy\Controllers\PrintApiController:PrintShoppingListThermal');

// Batteries
$group->get('/batteries', '\Grocy\Controllers\BatteriesApiController:Current');
$group->get('/batteries/{batteryId}', '\Grocy\Controllers\BatteriesApiController:BatteryDetails');
Expand Down
6 changes: 6 additions & 0 deletions services/BaseService.php
Original file line number Diff line number Diff line change
Expand Up @@ -66,4 +66,10 @@ protected function getUsersService()
{
return UsersService::getInstance();
}

protected function getPrintService()
{
return PrintService::getInstance();
}

}
Loading

0 comments on commit eb135ae

Please sign in to comment.