TimeTrex integration: Moved discussion from previous post

I am moving a discussion from my previous post "HTML Viewer Widget on POS screen does not support Adobe FLex:

Here is how to fully open up the TimeTrex API to support my intentions:

The free edition of TimeTrex needed some minor adjustments to work since the free version’s API doesn’t have a working implemention of getUserPunch();

This will open TimeTrex up to login as the first user entered into TimeTrex so it can switchusers.


Open: …/classes/modules/api/unauthenticated/APIAuthentication.class.php

Replace the two instances of line (One is located under “newSession” the other under “switchUser”):

if ( $this->getPermissionObject()->Check(‘company’,‘view’) AND
$this->getPermissionObject()->Check(‘company’,‘login_other_user’) )
{

With:

if ( $this->getCurrentUserObject()->getId() == ‘1’) {

Open: …/classes/modules/api/punch/APIPunch.class.php

Replace:

if ( is_object($current_station) AND $current_station->checkAllowed( $user_id, $station_id, $station_type ) == TRUE ) {

With:

if ( ‘1’  == ‘1’ ) {

And comment out the line:

$data[‘station_id’] = $current_station->getId();

As:

//$data[‘station_id’] = $current_station->getId();

I was scouring their forums to see if anyone had made a script already that I could modify to work how I want and the closest thing I could find was a script that punches via email but it uses an outside service to handle to email so I may have to just write one from scratch.

I could use the email method and call a program to handle sending the email but that sounds unnecessary and it would over complicate what I am trying to do.

Now my big question: Is it possible to read the login info from SambaPOS and send it to TimeTrex via a script? I already know I can use rules for “User Login”

Or maybe I am over thinking it.

I need to figure out how to read the User Login info when logging into Samba and send it as a punch to TimeTrex. I know how to code the script in TimeTrex to read the info and execute the punch I just dont know how to integrate it at Login and Logout with Samba yet.

Ideally I would want my admin password immune to the TimeTrex integration as it would not be Clocked in or out as an employee… which I can do via the TimeTrex side.

I’m not sure if it helps for or not but it might be helpful for such integrations.

We have URL Printer module. It basically posts print output to an URL. You need to activate module, set printer type as Custom Printer and choose URL printer as printer. We are using it to integrate SambaPOS to SMS sending services.

http://sambapos.org/wiki/doku.php/en/custom_package_delivery_system_part_3

1 Like

That will probably work I can then program TimeTrex to read the output.

Save to File printer might work too if I can get it to save to a .php file and make the input basically print a script. I could then possibly make automation commands for Clock In and Clock out and have them start a script for TimeTrex telling it to read the .php file

Ok so the file printer works it saves a file clock_in.php and it prints a script that i entered into the template. I tested it and it works beautifully now only thing left to do is have an action/rule linked that calls a program telling timetrex to read the php file.

Well ive been working pretty hard to get this going. I have a script that can clock a person in and clock them out when they hit a command button on a custom entity screen. So to them it looks integrated into Samba.

So far my route to get it working involved me making a .bat file to execute the script for TimeTrex. Its not the most graceful approach I know but the API in TimeTrex is confusing as hell because they also have a paid professional version and alot of the API is disabled in the free version but there is no documentation explaining whats disabled.

So anyway I was successful at getting it to do a punch via command button executing a .bat file. Now what I need to figure out is how can I get Samba to execute a different .bat file depending on who is logged in. In otherwords I need Samba to know when USER A logs in that Batch file A is executed via the command button. If USER B logs in then BATCH file B is executed when they hit the button.

There is probably an easier way to do this but my brain has been swelling pretty bad trying to figure out the API of TimeTrex it took me couple days just to work around the fact that the free version is not suposed to support quick punching like I have implemented.

If anyone wants to take a jab at a better solution. Here is the API I setup to get quick punching working in TimeTrex.

It would be nice If I could somehow just get Samba to link with the API directly. This code creates a timeclock when you run it in command prompt. I used a script to auto insert the input for the time clock. It was designed to run on a rasberypi hooked to a scanner. I adapted it to work with samba but it was very complex and I know there has to be a better solution. I had to use a lot of .bat files and use a hotkey script to get it working with Samba.

I spent alot of time trying to just get a direct API call that simply did a punch based on username…but that code is restricted for the professional version so I had to do alot of workarounds.

<?php


require_once('../../classes/modules/api/client/TimeTrexClientAPI.class.php');

//admin login info
$TIMETREX_URL = 'http://DOMAIN_NAME/api/soap/api.php';
$TIMETREX_USERNAME = 'ADMIN';
$TIMETREX_PASSWORD = 'PASSWORD';

$colors = new Colors();

reconnect:

$TIMETREX_SESSION_ID= FALSE;
echo "\n" . $colors->getColoredString("Reconnecting to server.....", "white", "red"). "\n";
$api_session = new TimeTrexClientAPI();
$api_session->Login( $TIMETREX_USERNAME, $TIMETREX_PASSWORD );

if ( $TIMETREX_SESSION_ID == FALSE ) {
    sleep(10);
    goto reconnect;
}


newscan:
$punch_obj = new TimeTrexClientAPI( 'Punch' );
$punchtime = time();
$punchtype = "Normal";
$punchstatus = "Auto";
$punchid = 0;
$firsttime = TRUE;
while ($punchid == 0) {

    if (time() - $punchtime > 10) {
        $punchtype = "Normal";
        $punchstatus = "Auto";
    }

    //get time from server and print it
    $result = $punch_obj->getUserPunch();
    if ($result == false) {
        echo $colors->getColoredString("CONNECTION ERROR", "white", "red");
        echo "\n";
        goto reconnect;
    }
    $punch_data = $result->getResult();
    if ($punch_data == false) {
        echo $colors->getColoredString("CONNECTION ERROR", "white", "red");
        echo "\n";
        goto reconnect;
    }
    echo "\n\n";
    echo $punch_data['time_stamp'];
    echo "\n\nScanner Ready\n\n";

    if ($punchtype != "Normal") {
        echo $colors->getColoredString("Punch Type: " . $punchtype , "white", "red");
    } else {
        echo "Punch Type: ". $punchtype ;
    }
    echo "\n";
    if ($punchstatus != "Auto") {
        echo $colors->getColoredString("Punch Direction: " . $punchstatus , "white", "red");
    } else {
        echo "Punch Direction: ". $punchstatus;
    }

    echo "\n\n";

    //skip the padding to leave the last report on the screen
    if ($firsttime == FALSE) {
        echo "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
    }

    //get scanner input if over 10 seconds, refresh screen
    $input = fgets_u(STDIN,10);

    if (strpos($input,"IN") !== FALSE) {
        $punchstatus = "In";
        $punchtime = time();
    }
    if (strpos($input,"OUT") !== FALSE) {
        $punchstatus = "Out";
        $punchtime = time();
    }

    if (strpos($input,"BREAK") !== FALSE) {
        $punchtype = "Break";
        $punchstatus = "Out";
        $punchtime = time();
    }
    if (strpos($input,"LUNCH") !== FALSE) {
        $punchtype = "Lunch";
        $punchstatus = "Out";
        $punchtime = time();
    }

    if (strpos($input,"NORMAL") !== FALSE || strpos($input,"RESET") !== FALSE ||  strpos($input,"CLEAR") !== FALSE) {
        $punchtype = "Normal";
        $punchstatus = "Auto";
    }

    if (strpos($input,"REPORT") !== FALSE) {
        $punchtype = "Report";
        $punchtime = time();
    }

    if (is_numeric($input)) {
        $punchid = (int)$input;
    }

    $firsttime = FALSE;
}

$user_obj = new TimeTrexClientAPI( 'User' );
if ($user_obj  == false) {
    echo $colors->getColoredString("CONNECTION ERROR - user_obj\n", "white", "red");
    goto reconnect;
}

$result = $user_obj->getUser(array('filter_data' => array('employee_number' => $punchid)));
if ($result == false) {
    echo $colors->getColoredString("CONNECTION ERROR - getUser", "white", "red");
    echo "\n";
    goto reconnect;
}
$user_data = $result->getResult();
if ($user_data  == false) {
    echo $colors->getColoredString("CONNECTION ERROR- getResult -> getUser", "white", "red");
    echo "\n";
    goto reconnect;
}
if (is_bool($user_data) == TRUE) {
    echo "\n\n";
    echo $colors->getColoredString("USER NOT FOUND IN DATABASE!", "white", "red");
    echo "\n\n";
    goto newscan;
}

echo "\nUser found.  Working...\n";

if ($punchtype != "Report") {


   //Switch to the User we scanned in
    $auth_obj = new TimeTrexClientAPI( 'Authentication' );
    if ($auth_obj  == false) {
        echo $colors->getColoredString("CONNECTION ERROR - auth_obj", "white", "red");
        echo "\n";
        goto reconnect;
    }
    $auth_obj->switchUser( $user_data[0]['id'] );
    if ($auth_obj  == false) {
        echo $colors->getColoredString("CONNECTION ERROR - switchUser", "white", "red");
        echo "\n";
        goto reconnect;
    }

    $punch_obj = new TimeTrexClientAPI( 'Punch' );
    if ($punch_obj  == false) {
        echo $colors->getColoredString("CONNECTION ERROR - punch_obj", "white", "red");
        echo "\n";
        goto reconnect;
    }

    $result = $punch_obj->getUserPunch();
    if ($result  == false) {
        echo $colors->getColoredString("CONNECTION ERROR - getUserPunch", "white", "red");
        echo "\n";
        goto reconnect;
    }

    $punch_data = $result->getResult();
    if ($punch_data  == false) {
        echo $colors->getColoredString("CONNECTION ERROR - getResult -> getUserPunch", "white", "red");
        echo "\n";
        goto reconnect;
    }

   //override the automaticlly generated punch
    if ($punchstatus == "In") {
        $punch_data['status_id']=10;
    }
    if ($punchstatus == "Out") {
        $punch_data['status_id']=20;
    }

    if ($punchtype == "Break") {
        $punch_data['type_id']=30;
    }
    if ($punchtype == "Lunch") {
        $punch_data['type_id']=20;
    }

    if ($punchtype != "Report") {

        $result = $punch_obj->setUserPunch($punch_data);
        if ($result  == false) {
            echo $colors->getColoredString("CONNECTION ERROR - setUserPunch", "white", "red");
            echo "\n";
            goto reconnect;
        }

        $result = $result->getResult();

        if ($result == false) {
            echo $colors->getColoredString("\nPLEASE WAIT AT LEAST ONE MINUTE BETWEEN PUNCHES!", "white", "red");
            echo "\n";
            goto reconnect;
        } else {
            echo "#{$user_data[0]['employee_number']} Punch Complete\n";
        }

    }

    $api_session->Logout();
}

//Generate the punch report
//log back in as admin

$api_session->Login( $TIMETREX_USERNAME, $TIMETREX_PASSWORD );
if ( $TIMETREX_SESSION_ID == FALSE ) {
    echo $colors->getColoredString("CONNECTION ERROR - REPORT LOGIN FAILED", "white", "red");
    echo "\n";
    goto reconnect;
}

//get report
echo "____________________\n\n";
echo "Punch Summary Report\n";
echo "____________________\n\n";
$report_obj = new TimeTrexClientAPI( 'PunchSummaryReport' );
if ($report_obj  == false) {
    echo $colors->getColoredString("CONNECTION ERROR - report_obj", "white", "red");
    echo "\n";
    goto reconnect;
}

$config = $report_obj->getTemplate('by_employee+punch_summary+total_time')->getResult();
if ($config  == false) {
    echo $colors->getColoredString("CONNECTION ERROR - getTemplate", "white", "red");
    echo "\n";
    goto reconnect;
}

//adjust template
$config['-1010-time_period']['time_period'] = 'this_pay_period';
$config['employee_number'] = $user_data[0]['employee_number'];


//let the server generate the report
$result = $report_obj->getPunchSummaryReport($config , 'csv' );
if ($result  == false) {
    echo $colors->getColoredString("CONNECTION ERROR - getPunchSummaryReport", "white", "red");
    echo "\n";
    goto reconnect;
}

$result = $result->getResult();
if ($result  == false) {
    echo $colors->getColoredString("CONNECTION ERROR - getResult -> getPunchSummaryReport", "white", "red");
    echo "\n";
    goto reconnect;
}

//parse and display the CSV file

//decode report
$input = base64_decode($result['data']);
//split the line breaks
$input = str_replace("\"", "", $input);
$csvData = explode( "\n", $input);
//split at the commas
foreach ($csvData as &$value) {
    $value = explode(',', $value);
}

$total = 0;  // tally of total hours worked

//print table headers
echo "#{$user_data[0]['employee_number']}: {$user_data[0]['last_name']}, {$user_data[0]['first_name']}\n\n";

//echo each line of data
foreach ($csvData as &$value) {
    if (isset($value[2]) && isset($value[3]) &&isset($value[4]) && isset($value[5]) && isset($value[6])) {
        echo str_pad ($value[2], 10);
        echo str_pad ($value[3], 20);
        echo str_pad ($value[4], 10);
        echo str_pad ($value[5], 20);
        if (is_numeric($value[6]) ) {
            $total = $total + (float)$value[6];
            if ((float)$value[6] != 0 ) {
                printf( "% 6.3f", $value[6]);
            }
        } else {
            echo str_pad($value[6], 10);
        }
        echo "\n";
    }
}
//print total hours worked
echo str_pad (" ", 60);
echo $colors->getColoredString(sprintf( "% 6.3f",  $total), "white", "green");
//done
goto newscan;


function fgets_u($pStdn,$delay) {
    $pArr = array($pStdn);
    if (false === ($num_changed_streams = stream_select($pArr, $write = NULL, $except = NULL,$delay ))) {
        print("\$ 001 Socket Error : UNABLE TO WATCH STDIN.\n");
        return FALSE;
    }
    elseif ($num_changed_streams > 0) {
        return trim(fgets($pStdn, 1024));
    }
}
?>

<?php

class Colors {
    private $foreground_colors = array();
    private $background_colors = array();

    public function __construct() {
        // Set up shell colors
        $this->foreground_colors['black'] = '0;30';
        $this->foreground_colors['dark_gray'] = '1;30';
        $this->foreground_colors['blue'] = '0;34';
        $this->foreground_colors['light_blue'] = '1;34';
        $this->foreground_colors['green'] = '0;32';
        $this->foreground_colors['light_green'] = '1;32';
        $this->foreground_colors['cyan'] = '0;36';
        $this->foreground_colors['light_cyan'] = '1;36';
        $this->foreground_colors['red'] = '0;31';
        $this->foreground_colors['light_red'] = '1;31';
        $this->foreground_colors['purple'] = '0;35';
        $this->foreground_colors['light_purple'] = '1;35';
        $this->foreground_colors['brown'] = '0;33';
        $this->foreground_colors['yellow'] = '1;33';
        $this->foreground_colors['light_gray'] = '0;37';
        $this->foreground_colors['white'] = '1;37';

        $this->background_colors['black'] = '40';
        $this->background_colors['red'] = '41';
        $this->background_colors['green'] = '42';
        $this->background_colors['yellow'] = '43';
        $this->background_colors['blue'] = '44';
        $this->background_colors['magenta'] = '45';
        $this->background_colors['cyan'] = '46';
        $this->background_colors['light_gray'] = '47';
    }

    // Returns colored string
    public function getColoredString($string, $foreground_color = null, $background_color = null) {
        $colored_string = "";

        // Check if given foreground color found
        if (isset($this->foreground_colors[$foreground_color])) {
            $colored_string .= "\033[" . $this->foreground_colors[$foreground_color] . "m";
        }
        // Check if given background color found
        if (isset($this->background_colors[$background_color])) {
            $colored_string .= "\033[" . $this->background_colors[$background_color] . "m";
        }

        // Add string and end coloring
        $colored_string .=  $string . "\033[0m";

        return $colored_string;
    }

    // Returns all foreground color names
    public function getForegroundColors() {
        return array_keys($this->foreground_colors);
    }

    // Returns all background color names
    public function getBackgroundColors() {
        return array_keys($this->background_colors);
    }
}


?>

The event for User Login has access to [:UserName] and/or {USER NAME}, so you could use separate Rules, or duplicate Actions with Constraints to specify the BAT file for Start Process Action, or even base the BAT file name using the User Name.

1 Like

Thanks thats exactly what I needed. I am going to get it working like this for now and then work harder on a more solid API solution. But at least I have gotten it to work.

Basically I was struggling getting the API to communicate to TimeTrex to do the auto punches because they want you to buy the pro version and use their API solution for it. The capability is there I am just not getting any support so I am having to figure it out myself through trial and error and looking through the code.

I found a way through some other users implementations of a stand alone punch station using a monitor, Raspberry Pi and a usb scanner. I took his code and modified it to run a prompt through batch files and scripted key presses (not how i would want it working honestly). Basically when someone presses Clock In command button on the Time Clock custom entity screen it executes a .bat file that executes a specific script instance with their own TimeTrex login/password. It also executes a key-press script because it needs to enter their employee number to trigger the clock in. After all of that it closes the Command Prompt so no further access to type commands is there and it does all of this hidden behind samba screen so the employee never sees it.

Personally I don’t like it and its a sloppy way to do what I need. But at least its a working implementation so now i can focus on a better solution.

I am self taught and a novice at coding but i learn very fast. I started in college with computer science and switched to business but I wish I had finished at least more foundational basics with computer science because I end up needing it the most lol.

I would try to assist, but I’m not interested in installing it or putting any time into it until the stand-alone HTML5 version is available. :stuck_out_tongue_winking_eye:

I don’t like cloud-based services or applications for the simple reason that internet here isn’t reliable enough. Besides, I like my data to be local, where I know what’s happening to it, not out there somewhere in the ether. Same goes for Applications - If I buy something, I want a hard-copy, even if it’s just the Setup files… it’s mine after all.

Ok so the only thing preventing me from using it at login is:

If the user logs out so another employee can log in it will execute it again punching them again when they log in again to make a sale. That is why I was going to use the command buttons instead of triggering it on login.

Aparently the Execute Automation Command does not have access to [:UserName]

I understand completely. I am actually building my system on the local install since I am not live yet. Everything works except the entity screen for login and I plan to only allow them access to that to check schedule the clock in and out will be performed by the buttons.

There are very few events that expose [:UserName], unfortunately.

Could you possibly still capture User Login, fire the BAT and set a Program Setting for something like LoggedinStatus (you would need 1 setting for each user)? Then on subsequent Logout/Login, check the Program Setting to determine whether or not to run the BAT.

So are you running the Flex version via a Browser rather than the HTML Widget?

{:CURRENTUSER} should work with execute automation command.

1 Like

Thanks I had already worked a workaround… very elaborate chain of custom .bat files for each user lol. Using currentuser will cut all that clutter out.

QMcKay I will not go live with the flex version i am just running it to build my integration… The API is minimally changing from FLEX to HTML5. I have discussed that with one of the developers. I did not want to spend all my time coming up with a solution if it didnt port to the HTML 5 version once its out.

I am just not using the entity screen atm only the command buttons. Once HTML5 version comes out I will install it make sure my system still works with the API and then enable my custom screen.

1 Like

{:CURRENTUSER} does not seem to work. For it to work I would need to specify where the .bat file is… IE: e:{:CURRENTUSER}.bat

but it sends back could not find file. I have a .bat file in that dir with the name as username.bat.

Does it not support plugging {:CURRENTUSER} between e:\ and .bat?