DEV Community

Imam Ali Mustofa
Imam Ali Mustofa

Posted on

Build Warehouse and Store Management System - Pt. 4 - Final

Then how do I make it work for real? ️😎

Ok, I will describe in detail (bismillah). The first step is to prepare the libraries needed to be able to work with the flow above.

  • ESCPOS PHP from Michael Billington
  • Workerman from Walkor
  • And a cup of ☕

Install ESCPOS PHP

composer require mike42/escpos-php
Enter fullscreen mode Exit fullscreen mode

This library implements a subset of Epson's ESC/POS protocols for thermal receipt printers. It allows you to create and print receipts in basic, cut, and barcode formats on a compatible printer.

Install Workerman

composer require workerman/workerman
Enter fullscreen mode Exit fullscreen mode

This library is used to create a websocket server which will later provide a bridge between the browser and the client computer. So that the website application can trigger the printer.

Creating Websocket Server

By using workerman we can create a websocket server very easily, here is the code to create websocket

<?php

use Workerman\Worker;

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();
Enter fullscreen mode Exit fullscreen mode

The code above is an example of creating a websocket server from the usage example on the workerman/workerman page on github.

Departing from the websocket server example, we can easily create a printer server to communicate with online website applications in the browser.

There are some adjustments of course that must be done, namely our needs when using the printer:

  • The printer is used to print receipt
  • Display purchase data from database to receipt
  • Sends a signal to the browser when the receipt is printed
  • Sending a connection indication signal between the printer server and the website application

Creating a Printer Server with Websocket

<?php

use Workerman\Worker;

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();
Enter fullscreen mode Exit fullscreen mode

Save it with the name workerman-printer.php and run it in the terminal with the command:

php workerman-printer.php start
Enter fullscreen mode Exit fullscreen mode

Tampilan Konsol Terminal

Terminal Console View

And please test the websocket connection using Simple WebSocket Client.

  • Connect Server Location to Address ws://127.0.0.1:PORT
  • Trying to send data to WebSocket Server with Workerman message.
  • View results in Message Log

View Simple WebSocket Client by Fenjin Wang

Display Simple WebSocket Client by Fenjin Wang

Creating WebSocket Server for Printers went well, now heading to the sales note format to be printed from the Online Application on the Browser.

Make Receipt Format When Printed

I will give an example of the receipt format in ESCPOS-PHP. First of all what we need to create is a Wrapper Class to set item name & price into columns:

<?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$right\n";
    }
}
Enter fullscreen mode Exit fullscreen mode

Then we create a function to wrap the command 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 Mike42\Escpos\PrintConnectors\FilePrintConnector("/dev/usb/lp0");
     break;
     default:
                // Dynamic printer name
        $connector = new Mike42\Escpos\PrintConnectors\WindowsPrintConnector($arrData['printer_name']);
     break;
  }

    $printer = new Mike42\Escpos\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 INVOICE\n");
    $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 ExampleMart\n");
    $printer -> text("For trading hours, please visit example.com\n");
    $printer -> feed(2);
    $printer -> text($date . "\n");

    /* Cut the receipt and open the cash drawer */
    $printer -> cut();
    $printer -> pulse();

    $printer -> close();
}
Enter fullscreen mode Exit fullscreen mode

Making the receipt format to be printed is sufficient and we can see the code if it is combined into one in the workerman-printer.php file. Like this:

<?php

use Workerman\Worker;
use Mike42\Escpos\Printer;
use Mike42\Escpos\PrintConnectors\FilePrintConnector;
use Mike42\Escpos\PrintConnectors\WindowsPrintConnector;

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 INVOICE\n");
    $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 ExampleMart\n");
    $printer -> text("For trading hours, please visit example.com\n");
    $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$right\n";
    }
}

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();
Enter fullscreen mode Exit fullscreen mode

With the workerman-printer.php file you can try to test it with the tools described above. The format of the data sent from the WebSocket Client to the Server is as follows:

{
    "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"
}
Enter fullscreen mode Exit fullscreen mode

Don't forget! WebSocket only sends String so use JSON.strigify(data) if using JavaScript.

Run the workerman-printer.php file in the terminal with the following command:

# run with DEBUG
php workerman-printer.php start

# or run in background
php workerman-printer.php start -d
Enter fullscreen mode Exit fullscreen mode

This is the final post in the Building a Warehouse and Store Management System series. Thanks for reading.

If you find this post useful, you can make a donation for me
via Saweria (Indonesia) or via BuyMeCoffee, PayPal

Discussion (0)