
Построение системы управления складом и магазином — Часть 3
Имам Али Мустофа ・ Feb 18 ・ 3 min read
Тогда как заставить это работать по-настоящему? ️😎
Хорошо, я опишу подробно (бисмиллях). Первый шаг — это подготовка библиотек, необходимых для работы с вышеописанным потоком.
- ESCPOS PHP от Michael Billington
- Workerman от Walkor
- И чашка ☕
Установите ESCPOS PHP
composer require mike42/escpos-php
Эта библиотека реализует подмножество протоколов ESC/POS компании Epson для термопринтеров чеков. Она позволяет создавать и печатать квитанции в базовом формате, формате с разрезами и штрих-кодами на совместимом принтере.
Установите Workerman
composer require workerman/workerman
Эта библиотека используется для создания сервера websocket, который в дальнейшем будет служить мостом между браузером и клиентским компьютером. Таким образом, приложение веб-сайта сможет запускать принтер.
Создание сервера Websocket
С помощью workerman
мы можем легко создать сервер websocket, вот код для создания websocket
<?php
use WorkermanWorker;
require_once __DIR__ . '/vendor/autoload.php';
// Create a Websocket server
$ws_worker = new Worker('websocket://0.0.0.0:8889');
// Emitted when new connection come
$ws_worker->onConnect = function ($connection) {
echo "New connection open" . PHP_EOL;
};
// Emitted when data received
$ws_worker->onMessage = function ($connection, $message) {
// Send hallo $data
$connection->send('Hallo ' . $message);
};
// Emitted when connection closed
$ws_worker->onClose = function ($connection) {
echo "Connection closed" . PHP_EOL;
};
// Run worker
Worker::runAll();
Код выше — это пример создания сервера websocket из примера использования на странице workerman/workerman на github.
Отталкиваясь от примера создания сервера websocket, мы можем легко создать сервер принтера для взаимодействия с онлайн-приложениями веб-сайта в браузере.
Конечно, необходимо внести некоторые коррективы, а именно наши потребности при использовании принтера:
- Принтер используется для печати чека
- отображения данных о покупке из базы данных в чеке
- Отправка сигнала в браузер, когда чек распечатан
- Отправка сигнала индикации соединения между сервером принтера и приложением веб-сайта.
Создание сервера принтера с помощью Websocket
<?php
use WorkermanWorker;
require_once __DIR__ . '/vendor/autoload.php';
// Functions
function sendToAll( $connection, $message )
{
$connection->send($message);
}
function createMessage( $type = 'info', $message )
{
$timestamps = date('Y/m/d H:i:s');
$log = strtoupper($type);
return "[$timestamps][$log] $message";
}
// End Functions
echo createMessage("info", "Waiting connection...") . PHP_EOL;
// Create a Websocket server
$ws_worker = new Worker('websocket://0.0.0.0:8182');
// Emitted when new connection come
$ws_worker->onConnect = function ($connection) {
$message = createMessage("info", "New connection open") . PHP_EOL;
echo $message;
sendToAll($connection, $message);
};
// Emitted when data received
$ws_worker->onMessage = function ($connection, $message) {
// Send hello $data
$connection->send('Hello ' . $message);
};
// Emitted when connection closed
$ws_worker->onClose = function ($connection) {
echo "Connection closed" . PHP_EOL;
};
// Run worker
Worker::runAll();
Сохраните его с именем workerman-printer.php
и запустите в терминале командой:
php workerman-printer.php start
Вид консоли терминала
И, пожалуйста, протестируйте вебсокетное соединение с помощью Simple WebSocket Client.
- Connect Server Location to Address
ws://127.0.0.1:PORT
- Попытка отправить данные на WebSocket-сервер с сообщением Workerman.
- Просмотр результатов в журнале сообщений
Отображение простого WebSocket-клиента от Fenjin Wang
Создание WebSocket-сервера для принтеров прошло успешно, теперь перейдем к формату товарной накладной для печати из онлайн-приложения в браузере.
Создание формата квитанции при печати
Я приведу пример формата чека в ESCPOS-PHP. Прежде всего, нам нужно создать класс-обертку для установки названия товара и цены в столбцы:
<?php
/* A wrapper to do organise item names & prices into columns */
class item
{
private $name;
private $price;
private $dollarSign;
public function __construct($name = '', $price = '', $dollarSign = false)
{
$this -> name = $name;
$this -> price = $price;
$this -> dollarSign = $dollarSign;
}
public function __toString()
{
$rightCols = 10;
$leftCols = 38;
if ($this -> dollarSign) {
$leftCols = $leftCols / 2 - $rightCols / 2;
}
$left = str_pad($this -> name, $leftCols) ;
$sign = ($this -> dollarSign ? '$ ' : '');
$right = str_pad($sign . $this -> price, $rightCols, ' ', STR_PAD_LEFT);
return "$left$rightn";
}
}
Затем мы создадим функцию для обертывания команды ESCPOS-PHP
<?php
function print_receipt($arrData) {
switch (strtolower(php_uname('s'))) {
case "linux":
// Make sure if using /dev/usb/lp0 you have access permissions
$connector = new Mike42EscposPrintConnectorsFilePrintConnector("/dev/usb/lp0");
break;
default:
// Dynamic printer name
$connector = new Mike42EscposPrintConnectorsWindowsPrintConnector($arrData['printer_name']);
break;
}
$printer = new Mike42EscposPrinter($connector);
/* Name of shop */
$printer -> selectPrintMode(Printer::MODE_DOUBLE_WIDTH);
$printer -> text("ExampleMart Ltd.n");
$printer -> selectPrintMode();
$printer -> text("Shop No. 42.n");
$printer -> feed();
/* Title of receipt */
$printer -> setEmphasis(true);
$printer -> text("SALES INVOICEn");
$printer -> setEmphasis(false);
/* Items */
$printer -> setJustification(Printer::JUSTIFY_LEFT);
$printer -> setEmphasis(true);
$printer -> text(new item('', '$'));
$printer -> setEmphasis(false);
$subtotal = [];
foreach ($arrData['items'] as $item) {
$printer -> text(new Item($item['name'], $item['price']));
array_push($subtotal, $item['price']);
}
$printer -> setEmphasis(true);
$printer -> text(new Item('Subtotal', array_sum($subtotal)));
$printer -> setEmphasis(false);
$printer -> feed();
/* Tax and total */
$total = array_sum($subtotal) + $arrData['tax'];
$printer -> text(new Item('A Local Tax', $arrData['tax']));
$printer -> selectPrintMode(Printer::MODE_DOUBLE_WIDTH);
$printer -> text(new Item('Total', $total));
$printer -> selectPrintMode();
/* Footer */
$date = date('l jS of F Y h:i:s A');
$printer -> feed(2);
$printer -> setJustification(Printer::JUSTIFY_CENTER);
$printer -> text("Thank you for shopping at ExampleMartn");
$printer -> text("For trading hours, please visit example.comn");
$printer -> feed(2);
$printer -> text($date . "n");
/* Cut the receipt and open the cash drawer */
$printer -> cut();
$printer -> pulse();
$printer -> close();
}
Достаточно сделать формат чека для печати, и мы можем увидеть код, если объединить их в один в файле workerman-printer.php
. Например, так:
<?php
use WorkermanWorker;
use Mike42EscposPrinter;
use Mike42EscposPrintConnectorsFilePrintConnector;
use Mike42EscposPrintConnectorsWindowsPrintConnector;
require_once __DIR__ . '/vendor/autoload.php';
// Functions
function sendToAll( $connection, $message )
{
$connection->send($message);
}
function createMessage( $type = 'info', $message )
{
$timestamps = date('Y/m/d H:i:s');
$log = strtoupper($type);
return "[$timestamps][$log] $message";
}
function print_receipt($arrData) {
switch (strtolower(php_uname('s'))) {
case "linux":
// Make sure if using /dev/usb/lp0 you have access permissions
$connector = new FilePrintConnector("/dev/usb/lp0");
break;
default:
// Dynamic printer name
$connector = new WindowsPrintConnector($arrData['printer_name']);
break;
}
$printer = new Printer($connector);
/* Name of shop */
$printer -> selectPrintMode(Printer::MODE_DOUBLE_WIDTH);
$printer -> text("ExampleMart Ltd.n");
$printer -> selectPrintMode();
$printer -> text("Shop No. 42.n");
$printer -> feed();
/* Title of receipt */
$printer -> setEmphasis(true);
$printer -> text("SALES INVOICEn");
$printer -> setEmphasis(false);
/* Items */
$printer -> setJustification(Printer::JUSTIFY_LEFT);
$printer -> setEmphasis(true);
$printer -> text(new item('', '$'));
$printer -> setEmphasis(false);
$subtotal = [];
foreach ($arrData['items'] as $item) {
$printer -> text(new Item($item['name'], $item['price']));
array_push($subtotal, $item['price']);
}
$printer -> setEmphasis(true);
$printer -> text(new Item('Subtotal', array_sum($subtotal)));
$printer -> setEmphasis(false);
$printer -> feed();
/* Tax and total */
$total = array_sum($subtotal) + $arrData['tax'];
$printer -> text(new Item('A Local Tax', $arrData['tax']));
$printer -> selectPrintMode(Printer::MODE_DOUBLE_WIDTH);
$printer -> text(new Item('Total', $total));
$printer -> selectPrintMode();
/* Footer */
$date = date('l jS of F Y h:i:s A');
$printer -> feed(2);
$printer -> setJustification(Printer::JUSTIFY_CENTER);
$printer -> text("Thank you for shopping at ExampleMartn");
$printer -> text("For trading hours, please visit example.comn");
$printer -> feed(2);
$printer -> text($date . "n");
/* Cut the receipt and open the cash drawer */
$printer -> cut();
$printer -> pulse();
$printer -> close();
}
// End Functions
/* A wrapper to do organise item names & prices into columns */
class item
{
private $name;
private $price;
private $dollarSign;
public function __construct($name = '', $price = '', $dollarSign = false)
{
$this -> name = $name;
$this -> price = $price;
$this -> dollarSign = $dollarSign;
}
public function __toString()
{
$rightCols = 10;
$leftCols = 38;
if ($this -> dollarSign) {
$leftCols = $leftCols / 2 - $rightCols / 2;
}
$left = str_pad($this -> name, $leftCols) ;
$sign = ($this -> dollarSign ? '$ ' : '');
$right = str_pad($sign . $this -> price, $rightCols, ' ', STR_PAD_LEFT);
return "$left$rightn";
}
}
echo createMessage("info", "Waiting connection...") . PHP_EOL;
// Create a Websocket server
$ws_worker = new Worker('websocket://0.0.0.0:8182');
// Emitted when new connection come
$ws_worker->onConnect = function ($connection) {
$message = createMessage("info", "New connection open") . PHP_EOL;
echo $message;
sendToAll($connection, $message);
};
// Emitted when data received
$ws_worker->onMessage = function ($connection, $message) {
$data = json_decode($message, true);
echo '> printing from: ' . $data['from'] . PHP_EOL; // to console
sendToAll( $connection, createMessage('info', 'printing_from: ' . $data['from']) ); // to browser
echo '> activer_printer: ' . $data['printer_name'] . PHP_EOL; // to console
sendToAll( $connection, createMessage('info', 'active_printer: ' . $data['printer_name']) ); // to browser
echo '< echo', "n"; // to console
try {
print_receipt($data);
echo "[INFO][" . date('Y-m-d h:i:s') . "] Staff " . $data['staff_fullname'] . " print transaction number " . $data['trx_number'] . PHP_EOL; // to console
sendToAll( $connection, createMessage('info', "notifiy: Kasir " . $data['staff_fullname'] . " print transaction number " . $data['trx_number']) ); // to browser
} catch (Exception $e) {
echo "Couldn't print to this printer: " . $e->getMessage() . "n"; // to console
sendToAll( $connection, createMessage('error', "Couldn't print to this printer: " . $e->getMessage()) ); // to browser
}
};
// Emitted when connection closed
$ws_worker->onClose = function ($connection) {
echo "Connection closed" . PHP_EOL;// to console
};
// Run worker
Worker::runAll();
Имея файл workerman-printer.php
, можно попробовать протестировать его с помощью описанных выше инструментов. Формат данных, передаваемых от WebSocket-клиента к серверу, выглядит следующим образом:
{
"trx_number": "TRX-00017845",
"items": [
{"name": "An Example Item #1", "price": 1.45},
{"name": "Another Item", "price": 3.45},
{"name": "Something else", "price": 2.45},
{"name": "Final Item", "price": 1.50}
],
"tax": 1.30,
"from": "Application Name",
"printer_name": "ESPON TM-U220",
"staff_fullname": "Ryan Oz"
}
Не забывайте! WebSocket отправляет только String
, поэтому используйте JSON.strigify(data)
при использовании JavaScript.
Запустите файл workerman-printer.php
в терминале с помощью следующей команды:
# run with DEBUG
php workerman-printer.php start
# or run in background
php workerman-printer.php start -d
Это заключительный пост в серии статей «Создание системы управления складом и магазином». Спасибо за прочтение.
Если вы считаете этот пост полезным, вы можете сделать для меня пожертвование
через Saweria (Индонезия) или через BuyMeCoffee, PayPal