Tool check-out/locker integration

Hi there! I was wondering if there are already implementations for hand-tool checkout/check in. I’d assume each tool would need an RFID tag attached to it, but if there’s already a similar implementation for a similar purpose, I’d much more easily be able to modify it to our purposes.

If not, what do you think would be the best practice for an expandable system that would work off yours?


We at the Happylab also thought long and hard about how we could make a rental solution with Fabman. We solved it like this:

  • We have created our own equipment category “Rental Items”.
  • Each rental item is created as equipment and assigned to the “Rental Items” category. For a better overview, we have put the rental items in a separate space.
  • The billing information is stored in the metadata of the equipment.
  • Rental fees are billed automatically via a webhook. Details on this below.

Example and Process

Here is an example of the rental item called “Universal Profile Cutter Set” (I think it’s self-explanatory what the individual parameters of the metadata mean):


To rent an item, proceed as follows:

  • Open the equipment details and click on “Track activity”.

  • The current time is already entered as the start of borrowing (and can be changed if necessary). Then confirm with “Save”.

  • In the overview you can see which items are or were on loan and when.


  • Simply select the article bar in the overview and click on “edit”.


  • Select “Set end date”. The current time is automatically suggested and can be changed if necessary. Complete the return with “Save changes”.

  • You can check the billing immediately on the affected user’s charges tab.

Billing via Webhook

We have created a webhook with the following parameters:

  • URL: https:///ChargeRental.php?active=1&resourcecategory=&secret=
  • Descriptive label: Equipment Rental
  • Kinds of events: Activity Log

Source Code in PHP (feel free to use it and adapt it to your needs, no support, no warranty):

if ($_GET['secret'] != $token) {
$token = "<PutYourTokenHere>";
    print "Webhook not executed due to wrong token (url?secret=token)\n";

if (isset($_GET['active'])) {
    if ($_GET['active'] == "0" or strtolower($_GET['active']) == "false") {
        print "Webhook is not active.\n";

// only charge for eqipments of the right resource category        
if (isset($_GET['resourcecategory'])) {
    $resourcecategory = $_GET['resourcecategory'];
} else {
    echo "resourcecategory has to be set like: ?resourcecategory=237\n";

$obj = json_decode(file_get_contents("php://input"));

echo "\n\n";

$APIurl = "";

if ($obj->{'details'}->{'resource'}->{'type'} != $resourcecategory) {
    echo "resourcecategory is ".$obj->{'details'}->{'resource'}->{'type'}." but should be $resourcecategory\n";
    echo "Resource is not a rental item.\n";

if (!isset($obj->{'details'}->{'log'}->{'stopType'})) {
	echo "stopType not set.\n";

// read accounting parameter from metadata
$metadata = $obj->{'details'}->{'resource'}->{'metadata'};

echo ($obj->{'details'}->{'log'}->{'createdAt'}."\n");
$start = new DateTime($obj->{'details'}->{'log'}->{'createdAt'}, new DateTimeZone("UTC"));
$start->setTimezone(new DateTimeZone("Europe/Vienna"));

echo ($obj->{'details'}->{'log'}->{'stoppedAt'}."\n");
$stop = new DateTime($obj->{'details'}->{'log'}->{'stoppedAt'}, new DateTimeZone("UTC"));
$stop->setTimezone(new DateTimeZone("Europe/Vienna"));

$duration = strtotime($obj->{'details'}->{'log'}->{'stoppedAt'}) - strtotime($obj->{'details'}->{'log'}->{'createdAt'});
echo ("Dauer: $duration\n");

$accountingUnits = ceil($duration / $metadata->{'AccountingUnit'}->{'Seconds'});
echo ("accountingUnits: $accountingUnits\n");

$price = $metadata->{'Pricing'}->{'PricePerTransaction'} + $accountingUnits * $metadata->{'Pricing'}->{'PricePerUnit'};

if (isset($metadata->{'DisplayUnit'})) {       
    $unitName = $metadata->{'DisplayUnit'}->{'Name'};
    $units = round($accountingUnits * $metadata->{'AccountingUnit'}->{'Seconds'} / $metadata->{'DisplayUnit'}->{'Seconds'},4);
    $unitPrice = $metadata->{'Pricing'}->{'PricePerUnit'} * $metadata->{'DisplayUnit'}->{'Seconds'} / $metadata->{'AccountingUnit'}->{'Seconds'};
} else {
    $unitName = $metadata->{'AccountingUnit'}->{'Name'};
    $units = $accountingUnits;
    $unitPrice = $metadata->{'Pricing'}->{'PricePerUnit'};

$cha_text = $units." ".$unitName." á € ".number_format($unitPrice,2,",",".")." - ".$obj->{'details'}->{'resource'}->{'name'}." (".$start->format("Y-m-d H:i")." - ".$stop->format("Y-m-d H:i").")";
# delete previous charges on update

# create charge
if (isset($metadata->{'Pricing'}->{'MaxPrice'})) {
    $price = min($price, $metadata->{'Pricing'}->{'MaxPrice'});
if ($price > 0) 
    $usr_fabman_id = $obj->{'details'}->{'log'}->{'member'};
    echo ("$cha_text : ".$price);
    createCharge($usr_fabman_id, substr($obj->{'details'}->{'log'}->{'stoppedAt'}, 0, 19), $cha_text, $price, false, $obj->{'details'}->{'log'}->{'id'});		
    echo " -> Charge in Fabman created.\n";

/* Functions */

function callAPI($method, $url, $data = false)
	global $APIurl;
	$url = $APIurl . $url;
	#echo "CALL API: $url\n";
	$path_to_fabman_cookie = "fabmancookie";
	$curl = curl_init(); 
	curl_setopt($curl, CURLOPT_COOKIEJAR, $path_to_fabman_cookie);
	curl_setopt($curl, CURLOPT_COOKIEFILE, $path_to_fabman_cookie); 
	// ignore SSL Zertifikat
	curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, false);
	curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);

	switch ($method)
		case "POST":
			curl_setopt($curl, CURLOPT_POST, 1);

			if ($data) {
				curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
				curl_setopt($curl, CURLOPT_HTTPHEADER, array(
					'Content-Type: application/json',
					'Content-Length: ' . strlen($data))

		case "PUT":
			curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "PUT");
			if ($data) {
				curl_setopt($curl, CURLOPT_POSTFIELDS, $data);
				curl_setopt($curl, CURLOPT_HTTPHEADER, array(
					'Content-Type: application/json',
					'Content-Length: ' . strlen($data))

		case "DELETE":
			curl_setopt($curl, CURLOPT_CUSTOMREQUEST, "DELETE");
			if ($data)
				$url = sprintf("%s?%s", $url, http_build_query($data));

	#print ($url);
	curl_setopt($curl, CURLOPT_URL, $url);
	curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

	$result = curl_exec($curl);
	$http_code = curl_getinfo($curl, CURLINFO_HTTP_CODE);

	return array("http_code" => $http_code, "data" => json_decode($result));

function createCharge($member,$date,$description,$price,$taxPercent,$resourceLogId = NULL) {
    if (strlen($date) === 10) {
        $date .= "T00:00:00";
    if ($taxPercent) {
        $data = array("member" => $member, "dateTime" => $date, "description" => $description, "price" => $price, "taxPercent" => $taxPercent);            
    } else {
        $data = array("member" => $member, "dateTime" => $date, "description" => $description, "price" => $price);                        
	if ($resourceLogId != NULL) {
		$data['resourceLog'] = $resourceLogId;
	$result = callAPI("POST", "charges", json_encode($data));
	return $result; // error if $result['http_code'] != 200

function deleteAllChargesFromActivity($resourceLog) {
    global $obj;

    $result = callAPI("GET", "charges?limit=50&resourceLog=$resourceLog&onlyUninvoiced=true");

    if ($result['http_code'] != 200) { // error if $result['http_code'] != 200
        echo "ERROR: http_code = ".$result['http_code']."\n";
        return false;
    } else {
        foreach ($result['data'] as $charge) {
1 Like

Absolute cheers and many thanks for the quick and detailed response, this is way more perfect than I could have ever hoped for!