GPS Tracking With Scheduler Event

Good day Everyone

I created a new Client Script to fetch the GPS Coordinates for the (Employee Checkin) Doctype, and it’s working well. Now, I need to keep track of all the Employees who Check-in all day long.

How can I keep track of the Employees’ GPS Coordinates?

I tried creating a Scheduler Event in Server Script in order to register the GPS Data of the Employees on an hourly basis, but it doesn’t seem to be working or rather I don’t know how to make it work.

Here is the code (JavaScript - Client Script)

frappe.ui.form.on('Employee Checkin', {
	onload(frm) {
	    function onPositionRecieved(position){
	        var longitude= position.coords.longitude;
	        var latitude= position.coords.latitude;
	        frm.set_value('longitude',longitude);
	        frm.set_value('latitude',latitude);
	        console.log(longitude);
	        console.log(latitude);
	        fetch('https://api.opencagedata.com/geocode/v1/json?q='+latitude+'+'+longitude+'&key=813661b481fe4ee8b2f7fcf770c06a30')
	         .then(response => response.json())
            .then(data => {
                var city=data['results'][0].components.city;
                var state=data['results'][0].components.state;
                var area=data['results'][0].components.residential;
                frm.set_value('city',city);
                frm.set_value('state',state);
                frm.set_value('area',area);
                console.log(data);
            })
            .catch(err => console.log(err));
	        frm.set_df_property('my_location','options','<div class="mapouter"><div class="gmap_canvas"><iframe width=100% height="300" id="gmap_canvas" src="https://maps.google.com/maps?q='+latitude+','+longitude+'&t=&z=17&ie=UTF8&iwloc=&output=embed" frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe><a href="https://yt2.org/youtube-to-mp3-ALeKk00qEW0sxByTDSpzaRvl8WxdMAeMytQ1611842368056QMMlSYKLwAsWUsAfLipqwCA2ahUKEwiikKDe5L7uAhVFCuwKHUuFBoYQ8tMDegUAQCSAQCYAQCqAQdnd3Mtd2l6"></a><br><style>.mapouter{position:relative;text-align:right;height:300px;width:100%;}</style><style>.gmap_canvas {overflow:hidden;background:none!important;height:300px;width:100%;}</style></div></div>');
            frm.refresh_field('my_location');
	    }
	    
	    function locationNotRecieved(positionError){
	        console.log(positionError);
	    }
	    
	    if(frm.doc.longitude && frm.doc.latitude){
	        frm.set_df_property('my_location','options','<div class="mapouter"><div class="gmap_canvas"><iframe width=100% height="300" id="gmap_canvas" src="https://maps.google.com/maps?q='+frm.doc.latitude+','+frm.doc.longitude+'&t=&z=17&ie=UTF8&iwloc=&output=embed" frameborder="0" scrolling="no" marginheight="0" marginwidth="0"></iframe><a href="https://yt2.org/youtube-to-mp3-ALeKk00qEW0sxByTDSpzaRvl8WxdMAeMytQ1611842368056QMMlSYKLwAsWUsAfLipqwCA2ahUKEwiikKDe5L7uAhVFCuwKHUuFBoYQ8tMDegUAQCSAQCYAQCqAQdnd3Mtd2l6"></a><br><style>.mapouter{position:relative;text-align:right;height:300px;width:100%;}</style><style>.gmap_canvas {overflow:hidden;background:none!important;height:300px;width:100%;}</style></div></div>');
            frm.refresh_field('my_location');
	    } else {
	        if(navigator.geolocation){
	            navigator.geolocation.getCurrentPosition(onPositionRecieved,locationNotRecieved,{ enableHighAccuracy: true});
	        }
	    }
    }
    ,
	validate(frm){
        const R = 6371e3; // metres
        var curLong = frm.doc.longitude;
        var curLat = frm.doc.latitude;

        var brnLong = frm.doc.branch_longitude;
        var brnLat = frm.doc.branch_latitude;

        const radLong = curLat * Math.PI/180; // φ, λ in radians
        const radLat = brnLat * Math.PI/180;
        const radBrLong = (brnLat-curLat) * Math.PI/180;
        const radBrLat = (brnLong-curLong) * Math.PI/180;

        const a = Math.sin(radBrLong/2) * Math.sin(radBrLong/2) +
            Math.cos(radLong) * Math.cos(radLat) *
            Math.sin(radBrLat/2) * Math.sin(radBrLat/2);
        const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));

        var dist = R * c; // in metres
        frm.set_value("distance_in_meters", dist);
        refresh_field("distance_in_meters");

        if (frm.doc.distance_in_meters > 300.0000000 && !frm.doc.reason){
            frm.set_value('skip_auto_attendance', 1);
        }
    }
})

The code is copied from ERPNext Forum Topic:
[Geolocation based Employee Attendance check-in system - Frappe Framework / Customization - Frappe Forum](Geolocation based Employee Attendance check-in system)

And here is the Scheduler Event Code (Python - Server Script)

check = frappe.get_doc({
'doctype': 'Employee Checkin',
'employee': 'EMP/00091',
'log_type': 'IN'
})
check. Insert()
# check.run_method('onload')
# check.queue_action('onload')
# meta = frappe.get_meta('Employee Checkin')
# meta.has_field('employee') # True
# meta.get_custom_fields()

As can be seen, I’ve tried many different codes from the Frappe Framework instructions but no luck so far. However, the rest of the docfields are being fetch automatically, such as, Shift Type, but not the GPS Coordinates.

Can someone please provide a solution for this issue?
Thank you :slight_smile:

1 Like

The Final Draft for the Scheduler Event Code (Python - Server Script)

curr_t = frappe.utils.now()
c_t = frappe.utils.cint(frappe.utils.get_time(curr_t).hour)
if c_t < 18:
    emp = frappe.db.get_list('Employee',
    filters={
        'default_shift': ['is', 'set'],
        'status': 'Active'
    }, fields=['name'])
    for row in emp:
        name = row.name
        first_checkin = frappe.db.get_value('Employee Checkin',
        {
            'employee': row.name,
            'log_type': 'IN',
            'creation': ['between', [
                frappe.utils.today(),
                frappe.utils.now()
            ]]
        },
        ['time'],
        order_by='time asc'
        )
        if first_checkin:
            check = frappe.get_doc({
            'doctype': 'Employee Checkin',
            'employee': row.name,
            'log_type': 'IN'
            })
            check. Insert()

The goal is to keep track of the Employees who checked-in for the day, keep track of their GPS Coordinates

1 Like

Hello @Mohd_RTC21,
From what I understood:

  1. You have already implemented a solution that fetches users geolocation at Employee Checkin.
  2. You now want to implement a periodic update of this with scheduler event.

I am afraid this approach won’t work. Scheduler is a server side api and is more suited for running background job on server, device’s geolocation API is in front-end and it wont’ be available in for the scheduler. You could offcourse implement a websocket based solution, the uses frappe realtime and then invoke a function in js on the websocket event to send Geolocation. But, that won’t be very efficient.

Your best bet, if in browser would be implementing service worker in the front-end. Service workers run in your browser’s background and is separate from the web page. This avoids the need to have the web page or user interaction. This can technically be used to capture GPS data as needed by you. Adding service worker to frappe is quite a pain. You may need to override the app.html template file via a custom app to add necessary updates.

The problem you are looking to solve is not something that can be ideally solved via a browser based solution. A better solution will be to write a small application on mobile/laptop to do this job periodically. And expose an API on ERPNext to accept it.

Ref:

  1. Realtime (socket.io)
  2. frappe/app.html at develop · frappe/frappe · GitHub
  3. Service Worker API - Web APIs | MDN
  4. PeriodicSyncEvent - Web APIs | MDN
1 Like