Построение системы управления складом и магазином — Часть 4 — Финал

Тогда как заставить это работать по-настоящему? ️😎

Хорошо, я опишу подробно (бисмиллях). Первый шаг — это подготовка библиотек, необходимых для работы с вышеописанным потоком.

  • 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

Оцените статью
Procodings.ru
Добавить комментарий