mirror of
https://github.com/grocy/grocy.git
synced 2025-04-29 09:39:57 +00:00
Add support for printing shoppinglist with thermal printer (#1273)
* 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>
This commit is contained in:
parent
fe59fac1c3
commit
eb135aee39
@ -13,7 +13,8 @@
|
|||||||
"gumlet/php-image-resize": "^1.9",
|
"gumlet/php-image-resize": "^1.9",
|
||||||
"ezyang/htmlpurifier": "^4.13",
|
"ezyang/htmlpurifier": "^4.13",
|
||||||
"jucksearm/php-barcode": "^1.0",
|
"jucksearm/php-barcode": "^1.0",
|
||||||
"guzzlehttp/guzzle": "^7.0"
|
"guzzlehttp/guzzle": "^7.0",
|
||||||
|
"mike42/escpos-php": "^3.0"
|
||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
|
@ -95,6 +95,22 @@ Setting('MEAL_PLAN_FIRST_DAY_OF_WEEK', '');
|
|||||||
// see the file controllers/Users/User.php for possible values
|
// see the file controllers/Users/User.php for possible values
|
||||||
Setting('DEFAULT_PERMISSIONS', ['ADMIN']);
|
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
|
// Default user settings
|
||||||
// These settings can be changed per user, here the defaults
|
// 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
|
// are defined which are used when the user has not changed the setting so far
|
||||||
@ -198,6 +214,7 @@ Setting('FEATURE_FLAG_STOCK_PRODUCT_FREEZING', true);
|
|||||||
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_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_SHOPPINGLIST_MULTIPLE_LISTS', true);
|
||||||
Setting('FEATURE_FLAG_CHORES_ASSIGNMENTS', true);
|
Setting('FEATURE_FLAG_CHORES_ASSIGNMENTS', true);
|
||||||
|
Setting('FEATURE_FLAG_THERMAL_PRINTER', false);
|
||||||
|
|
||||||
// Feature settings
|
// 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
|
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
|
||||||
|
@ -11,6 +11,7 @@ use Grocy\Services\ChoresService;
|
|||||||
use Grocy\Services\DatabaseService;
|
use Grocy\Services\DatabaseService;
|
||||||
use Grocy\Services\FilesService;
|
use Grocy\Services\FilesService;
|
||||||
use Grocy\Services\LocalizationService;
|
use Grocy\Services\LocalizationService;
|
||||||
|
use Grocy\Services\PrintService;
|
||||||
use Grocy\Services\RecipesService;
|
use Grocy\Services\RecipesService;
|
||||||
use Grocy\Services\SessionService;
|
use Grocy\Services\SessionService;
|
||||||
use Grocy\Services\StockService;
|
use Grocy\Services\StockService;
|
||||||
@ -93,6 +94,12 @@ class BaseController
|
|||||||
return StockService::getInstance();
|
return StockService::getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getPrintService()
|
||||||
|
{
|
||||||
|
return PrintService::getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected function getTasksService()
|
protected function getTasksService()
|
||||||
{
|
{
|
||||||
return TasksService::getInstance();
|
return TasksService::getInstance();
|
||||||
|
41
controllers/PrintApiController.php
Normal file
41
controllers/PrintApiController.php
Normal file
@ -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,5 +1,4 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Grocy\Controllers;
|
namespace Grocy\Controllers;
|
||||||
|
|
||||||
use Grocy\Controllers\Users\User;
|
use Grocy\Controllers\Users\User;
|
||||||
|
@ -512,6 +512,7 @@ class StockController extends BaseController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public function Stockentries(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
public function Stockentries(\Psr\Http\Message\ServerRequestInterface $request, \Psr\Http\Message\ResponseInterface $response, array $args)
|
||||||
{
|
{
|
||||||
$usersService = $this->getUsersService();
|
$usersService = $this->getUsersService();
|
||||||
|
@ -52,6 +52,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Files"
|
"name": "Files"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Print"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"paths": {
|
"paths": {
|
||||||
@ -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": {
|
"components": {
|
||||||
"internalSchemas": {
|
"internalSchemas": {
|
||||||
|
@ -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';
|
const REQUIRED_SQLITE_VERSION = '3.9.0';
|
||||||
|
|
||||||
class PrerequisiteChecker
|
class PrerequisiteChecker
|
||||||
|
@ -2129,3 +2129,16 @@ msgstr ""
|
|||||||
|
|
||||||
msgid "Open stock entry print label in new window"
|
msgid "Open stock entry print label in new window"
|
||||||
msgstr ""
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Thermal printer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Printing"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Connecting to printer..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
msgid "Unable to print"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
var shoppingListTable = $('#shoppinglist-table').DataTable({
|
var shoppingListTable = $('#shoppinglist-table').DataTable({
|
||||||
'order': [[1, 'asc']],
|
'order': [[1, 'asc']],
|
||||||
"orderFixed": [[3, 'asc']],
|
"orderFixed": [[3, 'asc']],
|
||||||
'columnDefs': [
|
'columnDefs': [
|
||||||
@ -428,56 +428,106 @@ $(document).on("click", "#print-shopping-list-button", function(e)
|
|||||||
</label> \
|
</label> \
|
||||||
</div>';
|
</div>';
|
||||||
|
|
||||||
|
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()
|
||||||
|
{
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
$(".print-layout-container").addClass("d-none");
|
||||||
|
$("." + $("input[name='print-layout-type']:checked").val()).removeClass("d-none");
|
||||||
|
|
||||||
|
window.print();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Grocy.FeatureFlags["GROCY_FEATURE_FLAG_THERMAL_PRINTER"])
|
||||||
|
{
|
||||||
|
delete printButtons['printtp'];
|
||||||
|
sizePrintDialog = 'small';
|
||||||
|
}
|
||||||
|
|
||||||
bootbox.dialog({
|
bootbox.dialog({
|
||||||
message: dialogHtml,
|
message: dialogHtml,
|
||||||
size: 'small',
|
size: sizePrintDialog,
|
||||||
backdrop: true,
|
backdrop: true,
|
||||||
closeButton: false,
|
closeButton: false,
|
||||||
className: "d-print-none",
|
className: "d-print-none",
|
||||||
buttons: {
|
buttons: printButtons
|
||||||
cancel: {
|
|
||||||
label: __t('Cancel'),
|
|
||||||
className: 'btn-secondary',
|
|
||||||
callback: function()
|
|
||||||
{
|
|
||||||
bootbox.hideAll();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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");
|
|
||||||
}
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
$(".print-layout-container").addClass("d-none");
|
|
||||||
$("." + $("input[name='print-layout-type']:checked").val()).removeClass("d-none");
|
|
||||||
|
|
||||||
window.print();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -232,6 +232,9 @@ $app->group('/api', function (RouteCollectorProxy $group) {
|
|||||||
$group->post('/chores/executions/{executionId}/undo', '\Grocy\Controllers\ChoresApiController:UndoChoreExecution');
|
$group->post('/chores/executions/{executionId}/undo', '\Grocy\Controllers\ChoresApiController:UndoChoreExecution');
|
||||||
$group->post('/chores/executions/calculate-next-assignments', '\Grocy\Controllers\ChoresApiController:CalculateNextExecutionAssignments');
|
$group->post('/chores/executions/calculate-next-assignments', '\Grocy\Controllers\ChoresApiController:CalculateNextExecutionAssignments');
|
||||||
|
|
||||||
|
//Printing
|
||||||
|
$group->get('/print/shoppinglist/thermal', '\Grocy\Controllers\PrintApiController:PrintShoppingListThermal');
|
||||||
|
|
||||||
// Batteries
|
// Batteries
|
||||||
$group->get('/batteries', '\Grocy\Controllers\BatteriesApiController:Current');
|
$group->get('/batteries', '\Grocy\Controllers\BatteriesApiController:Current');
|
||||||
$group->get('/batteries/{batteryId}', '\Grocy\Controllers\BatteriesApiController:BatteryDetails');
|
$group->get('/batteries/{batteryId}', '\Grocy\Controllers\BatteriesApiController:BatteryDetails');
|
||||||
|
@ -66,4 +66,10 @@ class BaseService
|
|||||||
{
|
{
|
||||||
return UsersService::getInstance();
|
return UsersService::getInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function getPrintService()
|
||||||
|
{
|
||||||
|
return PrintService::getInstance();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
84
services/PrintService.php
Normal file
84
services/PrintService.php
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Grocy\Services;
|
||||||
|
|
||||||
|
use DateTime;
|
||||||
|
use Exception;
|
||||||
|
use Mike42\Escpos\PrintConnectors\NetworkPrintConnector;
|
||||||
|
use Mike42\Escpos\PrintConnectors\FilePrintConnector;
|
||||||
|
use Mike42\Escpos\Printer;
|
||||||
|
|
||||||
|
class PrintService extends BaseService
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialises the printer
|
||||||
|
* @return Printer Printer handle
|
||||||
|
* @throws Exception If unable to connect to printer, an exception is thrown
|
||||||
|
*/
|
||||||
|
private static function getPrinterHandle()
|
||||||
|
{
|
||||||
|
if (GROCY_TPRINTER_IS_NETWORK_PRINTER) {
|
||||||
|
$connector = new NetworkPrintConnector(GROCY_TPRINTER_IP, GROCY_TPRINTER_PORT);
|
||||||
|
} else {
|
||||||
|
$connector = new FilePrintConnector(GROCY_TPRINTER_CONNECTOR);
|
||||||
|
}
|
||||||
|
return new Printer($connector);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prints the grocy logo and date
|
||||||
|
* @param Printer $printer Printer handle
|
||||||
|
*/
|
||||||
|
private static function printHeader(Printer $printer)
|
||||||
|
{
|
||||||
|
$date = new DateTime();
|
||||||
|
$dateFormatted = $date->format('d/m/Y H:i');
|
||||||
|
|
||||||
|
$printer->setJustification(Printer::JUSTIFY_CENTER);
|
||||||
|
$printer->selectPrintMode(Printer::MODE_DOUBLE_WIDTH);
|
||||||
|
$printer->setTextSize(4, 4);
|
||||||
|
$printer->setReverseColors(true);
|
||||||
|
$printer->text("grocy");
|
||||||
|
$printer->setJustification();
|
||||||
|
$printer->setTextSize(1, 1);
|
||||||
|
$printer->setReverseColors(false);
|
||||||
|
$printer->feed(2);
|
||||||
|
$printer->text($dateFormatted);
|
||||||
|
$printer->selectPrintMode();
|
||||||
|
$printer->feed(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param bool $printHeader Printing of Grocy logo
|
||||||
|
* @param string[] $lines Items to print
|
||||||
|
* @return string[] Returns array with result OK if no exception
|
||||||
|
* @throws Exception If unable to print, an exception is thrown
|
||||||
|
*/
|
||||||
|
public function printShoppingList(bool $printHeader, array $lines): array
|
||||||
|
{
|
||||||
|
$printer = self::getPrinterHandle();
|
||||||
|
if ($printer === false)
|
||||||
|
throw new Exception("Unable to connect to printer");
|
||||||
|
|
||||||
|
if ($printHeader)
|
||||||
|
{
|
||||||
|
self::printHeader($printer);
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($lines as $line)
|
||||||
|
{
|
||||||
|
$printer->text($line);
|
||||||
|
$printer->feed();
|
||||||
|
}
|
||||||
|
|
||||||
|
$printer->feed(3);
|
||||||
|
$printer->cut();
|
||||||
|
$printer->close();
|
||||||
|
return [
|
||||||
|
'result' => "OK"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
@ -970,6 +970,88 @@ class StockService extends BaseService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the shoppinglist as an array with lines for a printer
|
||||||
|
* @param int $listId ID of shopping list
|
||||||
|
* @return string[] Returns an array in the format "[amount] [name of product]"
|
||||||
|
* @throws \Exception
|
||||||
|
*/
|
||||||
|
public function GetShoppinglistInPrintableStrings($listId = 1): array
|
||||||
|
{
|
||||||
|
if (!$this->ShoppingListExists($listId))
|
||||||
|
{
|
||||||
|
throw new \Exception('Shopping list does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
$result_product = array();
|
||||||
|
$result_quantity = array();
|
||||||
|
$rowsShoppingListProducts = $this->getDatabase()->uihelper_shopping_list()->where('shopping_list_id = :1', $listId)->fetchAll();
|
||||||
|
foreach ($rowsShoppingListProducts as $row)
|
||||||
|
{
|
||||||
|
$isValidProduct = ($row->product_id != null && $row->product_id != "");
|
||||||
|
if ($isValidProduct)
|
||||||
|
{
|
||||||
|
$product = $this->getDatabase()->products()->where('id = :1', $row->product_id)->fetch();
|
||||||
|
$conversion = $this->getDatabase()->quantity_unit_conversions_resolved()->where('product_id = :1 AND from_qu_id = :2 AND to_qu_id = :3', $product->id, $product->qu_id_stock, $row->qu_id)->fetch();
|
||||||
|
$factor = 1.0;
|
||||||
|
if ($conversion != null)
|
||||||
|
{
|
||||||
|
$factor = floatval($conversion->factor);
|
||||||
|
}
|
||||||
|
$amount = round($row->amount * $factor);
|
||||||
|
$note = "";
|
||||||
|
if (GROCY_TPRINTER_PRINT_NOTES)
|
||||||
|
{
|
||||||
|
if ($row->note != "") {
|
||||||
|
$note = ' (' . $row->note . ')';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (GROCY_TPRINTER_PRINT_QUANTITY_NAME && $isValidProduct)
|
||||||
|
{
|
||||||
|
$quantityname = $row->qu_name;
|
||||||
|
if ($amount > 1)
|
||||||
|
{
|
||||||
|
$quantityname = $row->qu_name_plural;
|
||||||
|
}
|
||||||
|
array_push($result_quantity, $amount . ' ' . $quantityname);
|
||||||
|
array_push($result_product, $row->product_name . $note);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if ($isValidProduct)
|
||||||
|
{
|
||||||
|
array_push($result_quantity, $amount);
|
||||||
|
array_push($result_product, $row->product_name . $note);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
array_push($result_quantity, round($row->amount));
|
||||||
|
array_push($result_product, $row->note);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//Add padding to look nicer
|
||||||
|
$maxlength = 1;
|
||||||
|
foreach ($result_quantity as $quantity)
|
||||||
|
{
|
||||||
|
if (strlen($quantity) > $maxlength)
|
||||||
|
{
|
||||||
|
$maxlength = strlen($quantity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$result = array();
|
||||||
|
$length = count($result_quantity);
|
||||||
|
for ($i = 0; $i < $length; $i++)
|
||||||
|
{
|
||||||
|
$quantity = str_pad($result_quantity[$i], $maxlength);
|
||||||
|
array_push($result, $quantity . ' ' . $result_product[$i]);
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public function TransferProduct(int $productId, float $amount, int $locationIdFrom, int $locationIdTo, $specificStockEntryId = 'default', &$transactionId = null)
|
public function TransferProduct(int $productId, float $amount, int $locationIdFrom, int $locationIdTo, $specificStockEntryId = 'default', &$transactionId = null)
|
||||||
{
|
{
|
||||||
if (!$this->ProductExists($productId))
|
if (!$this->ProductExists($productId))
|
||||||
|
Loading…
x
Reference in New Issue
Block a user