Build Warehouse and Store Management System - Pt. 4 - Final
Tags: showdev, php, productivity, webdev
Published: 2022-05-11 04:45:36

{% link https://dev.to/darkterminal/build-warehouse-and-store-management-system-pt-3-3jdo %}

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

```bash
composer require mike42/escpos-php
```

> 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

```bash
composer require workerman/workerman
```

> 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
<?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();
```

The code above is an example of creating a *websocket server* from the usage example on the [workerman/workerman](https://github.com/walkor/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
<?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();
```

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

```php
php workerman-printer.php start
```

![Tampilan Konsol Terminal](https://i.imgur.com/l7Y6HRC.png)

Terminal Console View

And please test the websocket connection using **[Simple WebSocket Client](https://addons.mozilla.org/en-US/firefox/addon/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](https://i.imgur.com/0yluVkH.png)

Display **Simple WebSocket Client by [Fenjin Wang](https://addons.mozilla.org/en-US/firefox/user/12635702/)**

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](https://github.com/mike42/escpos-php/blob/f414320fc510afcacd4cbb75902f827399dee429/example/receipt-with-logo.php#L78). First of all what we need to create is a Wrapper Class to set item name & price into columns:

```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$right\n";
    }
}
```

Then we create a function to wrap the command **ESCPOS-PHP**

```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();
}
```

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
<?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();
```

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:

```json
{
	"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"
}
```

**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:

```bash
# run with DEBUG
php workerman-printer.php start

# or run in background
php workerman-printer.php start -d
```

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](https://saweria.co/darkterminal) (Indonesia) or via [BuyMeCoffee](https://www.buymeacoffee.com/darkterminal), [PayPal](https://www.paypal.me/lazarusalhambra)