Build Warehouse and Store Management System - Pt. 3
Imam Ali Mustofa ・ Feb 18 '22
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
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
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();
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();
Save it with the name workerman-printer.php
and run it in the terminal with the command:
php workerman-printer.php start
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
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";
}
}
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();
}
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();
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"
}
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
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
Top comments (1)
Great posts, thanks! It highlights the importance of such a system in optimizing inventory management, order processing, and overall operational efficiency.
Building a customized Warehouse and Store Management System allows businesses to tailor the solution to their specific needs, ensuring seamless integration with existing processes and technologies.
For those interested in exploring more about smart warehouse management systems and their benefits, I recommend visiting Cleveroad. It provides comprehensive insights into implementing advanced WMS solutions that can significantly enhance warehouse and store management operations.
If you have any questions or want to discuss specific aspects of building a Warehouse and Store Management System, feel free to share your thoughts.