Merge branch '270-final-cmb-fixes' of gitlab.com:jankstudio/resq into 311-cmb-refactor-rapicontroller-into-a-service

This commit is contained in:
Korina Cordero 2020-01-30 06:16:02 +00:00
commit 74c239cfd6
44 changed files with 1512 additions and 375 deletions

2
README.md Normal file
View file

@ -0,0 +1,2 @@
MQTT format for location updates:
`<latitude>:<longitude>`

View file

@ -17,6 +17,7 @@
"predis/predis": "^1.1", "predis/predis": "^1.1",
"sensio/framework-extra-bundle": "^5.1", "sensio/framework-extra-bundle": "^5.1",
"setasign/fpdf": "^1.8", "setasign/fpdf": "^1.8",
"symfony/asset": "^4.0",
"symfony/console": "^4.0", "symfony/console": "^4.0",
"symfony/debug": "^4.0", "symfony/debug": "^4.0",
"symfony/filesystem": "^4.0", "symfony/filesystem": "^4.0",

62
composer.lock generated
View file

@ -1,10 +1,10 @@
{ {
"_readme": [ "_readme": [
"This file locks the dependencies of your project to a known state", "This file locks the dependencies of your project to a known state",
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "4873ae3fd18db755bc9bf395bbbfb141", "content-hash": "b101ecfbc1f6f2270f0e8ad326035b7e",
"packages": [ "packages": [
{ {
"name": "catalyst/auth-bundle", "name": "catalyst/auth-bundle",
@ -2411,6 +2411,62 @@
], ],
"time": "2016-01-01T17:47:15+00:00" "time": "2016-01-01T17:47:15+00:00"
}, },
{
"name": "symfony/asset",
"version": "v4.4.3",
"source": {
"type": "git",
"url": "https://github.com/symfony/asset.git",
"reference": "2c67c89d064bfb689ea6bc41217c87100bb94c17"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/asset/zipball/2c67c89d064bfb689ea6bc41217c87100bb94c17",
"reference": "2c67c89d064bfb689ea6bc41217c87100bb94c17",
"shasum": ""
},
"require": {
"php": "^7.1.3"
},
"require-dev": {
"symfony/http-foundation": "^3.4|^4.0|^5.0",
"symfony/http-kernel": "^3.4|^4.0|^5.0"
},
"suggest": {
"symfony/http-foundation": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "4.4-dev"
}
},
"autoload": {
"psr-4": {
"Symfony\\Component\\Asset\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony Asset Component",
"homepage": "https://symfony.com",
"time": "2020-01-04T13:00:46+00:00"
},
{ {
"name": "symfony/cache", "name": "symfony/cache",
"version": "v4.3.1", "version": "v4.3.1",
@ -5344,6 +5400,7 @@
"code", "code",
"zf2" "zf2"
], ],
"abandoned": "laminas/laminas-code",
"time": "2018-08-13T20:36:59+00:00" "time": "2018-08-13T20:36:59+00:00"
}, },
{ {
@ -5398,6 +5455,7 @@
"events", "events",
"zf2" "zf2"
], ],
"abandoned": "laminas/laminas-eventmanager",
"time": "2018-04-25T15:33:34+00:00" "time": "2018-04-25T15:33:34+00:00"
} }
], ],

View file

@ -21,6 +21,11 @@ security:
methods: [GET] methods: [GET]
security: false security: false
tracker:
pattern: ^\/track\/
methods: [GET]
security: false
api: api:
pattern: ^\/api\/ pattern: ^\/api\/
security: false security: false

View file

@ -201,3 +201,8 @@ jo_ajax_popup:
controller: App\Controller\JobOrderController::popupInfo controller: App\Controller\JobOrderController::popupInfo
methods: [GET] methods: [GET]
jo_tracker:
path: /track/{id}
controller: App\Controller\JobOrderController::tracker
methods: [GET]

View file

@ -76,6 +76,7 @@ services:
App\Service\MQTTClient: App\Service\MQTTClient:
arguments: arguments:
$redis_client: "@App\\Service\\RedisClientProvider" $redis_client: "@App\\Service\\RedisClientProvider"
$key: "mqtt_events"
App\Service\APNSClient: App\Service\APNSClient:
arguments: arguments:
@ -87,7 +88,6 @@ services:
$host: "%env(REDIS_CLIENT_HOST)%" $host: "%env(REDIS_CLIENT_HOST)%"
$port: "%env(REDIS_CLIENT_PORT)%" $port: "%env(REDIS_CLIENT_PORT)%"
$password: "%env(REDIS_CLIENT_PASSWORD)%" $password: "%env(REDIS_CLIENT_PASSWORD)%"
$env_flag: "dev"
App\Service\GeofenceTracker: App\Service\GeofenceTracker:
arguments: arguments:
@ -205,3 +205,28 @@ services:
App\Service\GISManagerInterface: "@App\\Service\\GISManager\\OpenStreet" App\Service\GISManagerInterface: "@App\\Service\\GISManager\\OpenStreet"
#App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Google" #App\Service\GISManagerInterface: "@App\\Service\\GISManager\\Google"
App\EventListener\JobOrderActiveCacheListener:
arguments:
$jo_cache: "@App\\Service\\JobOrderCache"
$mqtt: "@App\\Service\\MQTTClient"
tags:
- name: 'doctrine.orm.entity_listener'
event: 'postUpdate'
entity: 'App\Entity\JobOrder'
- name: 'doctrine.orm.entity_listener'
event: 'postRemove'
entity: 'App\Entity\JobOrder'
- name: 'doctrine.orm.entity_listener'
event: 'postPersist'
entity: 'App\Entity\JobOrder'
App\Service\JobOrderCache:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
$active_jo_key: "%env(LOCATION_JO_ACTIVE_KEY)%"
App\Service\RiderCache:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
$loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%"
$status_key: "%env(STATUS_RIDER_KEY)%"

View file

@ -331,4 +331,31 @@ span.has-danger,
.map-div-icon i.awesome { .map-div-icon i.awesome {
margin: 12px auto; margin: 12px auto;
font-size: 17px; font-size: 17px;
}
.map-info {
position: absolute;
bottom: 0;
left: 50%;
transform: translateX(-50%);
z-index: 9999;
padding: 1.5em;
width: 100%;
}
.map-info > .m-portlet {
margin-bottom: 0;
}
.map-info .m-portlet__body {
padding: 1.5rem;
}
.map-info .rider-image {
width: 4.8rem;
border-radius: 50%;
}
.map-info .m-badge {
border-radius: 0;
}

View file

@ -0,0 +1,171 @@
class DashboardMap {
constructor(options, rider_markers, cust_markers) {
this.options = options;
this.rider_markers = rider_markers;
this.cust_markers = cust_markers;
// layer groups
this.layer_groups = {
'rider_available': L.layerGroup(),
'rider_active_jo': L.layerGroup(),
'customer': L.layerGroup()
};
}
initialize() {
// main map
this.map = L.map(this.options.div_id).setView(
[this.options.center_lat, this.options.center_lng],
this.options.zoom
);
// add tile layer
var streets = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
maxZoom: 18,
id: 'mapbox/streets-v11',
accessToken: this.options.access_token
}).addTo(this.map);
// layer groups
this.layer_groups.rider_available.addTo(this.map);
this.layer_groups.rider_active_jo.addTo(this.map);
this.layer_groups.customer.addTo(this.map);
// base layer
var baseMaps = {
'Streets': streets
};
if (this.options.display_overlay) {
// overlay layer
var overlayMaps = {
'Available Riders' : this.layer_groups.rider_available,
'JO Riders' : this.layer_groups.rider_active_jo,
'Customers' : this.layer_groups.customer
}
L.control.layers(baseMaps, overlayMaps).addTo(this.map);
}
return this.map;
}
putMarker(id, lat, lng, markers, icon, layer_group, popup_url) {
var my = this;
// existing marker
if (markers.hasOwnProperty(id)) {
markers[id].setLatLng(L.latLng(lat, lng));
return;
}
// new marker
markers[id] = L.marker(
[lat, lng],
{ icon: icon }
).addTo(layer_group);
if (my.options.enable_popup) {
markers[id].bindPopup('Loading...');
// bind ajax for popup
markers[id].on('click', function(e) {
var popup = e.target.getPopup();
var url = popup_url.replace('[id]', id);
console.log(url);
$.get(url).done(function(data) {
popup.setContent(data);
popup.update();
});
});
}
}
putCustomerMarker(id, lat, lng) {
this.putMarker(
id,
lat,
lng,
this.cust_markers,
this.options.icons.customer,
this.layer_groups.customer,
this.options.cust_popup_url
);
}
removeCustomerMarker(id) {
console.log('removing customer marker for ' + id);
var layer_group = this.layer_groups.customer;
var markers = this.cust_markers;
// no customer marker with that id
if (!markers.hasOwnProperty(id)) {
console.log('no such marker to remove');
return;
}
layer_group.removeLayer(markers[id]);
}
putRiderAvailableMarker(id, lat, lng) {
this.putMarker(
id,
lat,
lng,
this.rider_markers,
this.options.icons.rider_available,
this.layer_groups.rider_available,
this.options.rider_popup_url
);
}
putRiderActiveJOMarker(id, lat, lng) {
this.putMarker(
id,
lat,
lng,
this.rider_markers,
this.options.icons.rider_active_jo,
this.layer_groups.rider_active_jo,
this.options.rider_popup_url
);
}
loadLocations(location_url) {
console.log(this.rider_markers);
var my = this;
$.ajax({
url: location_url,
}).done(function(response) {
// clear all markers
my.layer_groups.rider_available.clearLayers();
my.layer_groups.rider_active_jo.clearLayers();
my.layer_groups.customer.clearLayers();
// get riders and job orders
var riders = response.riders;
var jos = response.jos;
// job orders
$.each(jos, function(id, data) {
var lat = data.latitude;
var lng = data.longitude;
my.putCustomerMarker(id, lat, lng);
});
// riders
$.each(riders, function(id, data) {
var lat = data.latitude;
var lng = data.longitude;
if (data.has_jo)
my.putRiderActiveJOMarker(id, lat, lng);
else
my.putRiderAvailableMarker(id, lat, lng);
});
// console.log(rider_markers);
});
}
}

View file

@ -0,0 +1,114 @@
class MapEventHandler {
constructor(options, dashmap) {
this.options = options;
this.dashmap = dashmap;
}
connect(user_id, host, port) {
var d = new Date();
var client_id = "dash-" + user_id + "-" + d.getMonth() + "-" + d.getDate() + "-" + d.getHours() + "-" + d.getMinutes() + "-" + d.getSeconds() + "-" + d.getMilliseconds();
console.log(client_id);
this.mqtt = new Paho.MQTT.Client(host, port, client_id);
var options = {
// useSSL: true,
timeout: 3,
invocationContext: this,
onSuccess: this.onConnect.bind(this),
};
this.mqtt.onMessageArrived = this.onMessage.bind(this);
console.log('connecting to mqtt server...');
this.mqtt.connect(options);
}
onConnect(icontext) {
console.log('mqtt connected!');
var my = icontext.invocationContext;
// subscribe to rider locations
if (my.options.track_rider) {
console.log('subscribing to ' + my.options.channels.rider_location);
my.mqtt.subscribe(my.options.channels.rider_location);
}
// subscribe to jo locations
if (my.options.track_jo) {
console.log('subscribing to ' + my.options.channels.jo_location);
my.mqtt.subscribe(my.options.channels.jo_location);
my.mqtt.subscribe(my.options.channels.jo_status);
}
}
onMessage(msg) {
// console.log(msg);
console.log('received message');
var channel = msg.destinationName;
var chan_split = channel.split('/');
var payload = msg.payloadString;
// handle different channels
switch (chan_split[0]) {
case "rider":
this.handleRider(chan_split, payload);
break;
case "jo":
this.handleJobOrder(chan_split, payload);
break;
}
}
handleRider(chan_split, payload) {
console.log("rider message");
switch (chan_split[2]) {
case "location":
console.log("got location for rider " + chan_split[1] + " - " + payload);
var pl_split = payload.split(':');
console.log(pl_split);
// check for correct format
if (pl_split.length != 2)
break;
var lat = parseFloat(pl_split[0]);
var lng = parseFloat(pl_split[1]);
this.dashmap.putRiderAvailableMarker(chan_split[1], lat, lng);
break;
}
}
handleJobOrder(chan_split, payload) {
console.log("jo message");
var id = chan_split[1];
switch (chan_split[2]) {
case "location":
// var my = this;
console.log("got location for jo " + id + " - " + payload);
var pl_split = payload.split(':');
// check for correct format
if (pl_split.length != 2)
break;
var lat = parseFloat(pl_split[0]);
var lng = parseFloat(pl_split[1]);
// move marker
console.log(lat + ' - ' + lng);
this.dashmap.putCustomerMarker(id, lat, lng);
break;
case "status":
switch (payload) {
case 'cancel':
case 'fulfill':
case 'delete':
this.dashmap.removeCustomerMarker(id);
break;
}
}
}
}

View file

@ -313,17 +313,14 @@ class CreateCustomerFromWarrantyCommand extends Command
protected function loadCustomers() protected function loadCustomers()
{ {
error_log('starting query...');
// get all customers // get all customers
$customers = $this->em->getRepository(Customer::class)->findAll(); $customers = $this->em->getRepository(Customer::class)->findAll();
$cust_q = $this->em->createQuery('select c from App\Entity\Customer c'); $cust_q = $this->em->createQuery('select c from App\Entity\Customer c');
$cust_iter = $q->iterate(); $cust_iter = $q->iterate();
error_log('looping through query...');
$this->cust_index = []; $this->cust_index = [];
foreach ($cust_iter as $customer) foreach ($cust_iter as $customer)
{ {
error_log('here');
$mobile = trim($customer->getPhoneMobile()); $mobile = trim($customer->getPhoneMobile());
if (!empty($mobile)) if (!empty($mobile))
{ {

View file

@ -0,0 +1,68 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\DBAL\Connection;
use Doctrine\Common\Persistence\ObjectManager;
use App\Service\JobOrderCache;
use App\Entity\JobOrder;
use App\Ramcar\JOStatus;
use DateTime;
class RefreshJobOrderCacheCommand extends Command
{
protected $em;
protected $jo_cache;
public function __construct(ObjectManager $om, JobOrderCache $jo_cache)
{
$this->em = $om;
$this->jo_cache = $jo_cache;
parent::__construct();
}
protected function configure()
{
$this->setName('joborder:refresh_cache')
->setDescription('Refresh active job order cache from database.')
->setHelp('Refresh active job order cache from database.');
}
protected function execute(InputInterface $input, OutputInterface $output)
{
$date = new DateTime();
$date->modify('-3 day');
$status_list = [
JOStatus::PENDING,
JOStatus::RIDER_ASSIGN,
JOStatus::ASSIGNED,
JOStatus::IN_TRANSIT,
JOStatus::IN_PROGRESS,
];
$qb = $this->em->getRepository(JobOrder::class)
->createQueryBuilder('jo');
$res = $qb->select('jo')
->where('jo.status IN (:statuses)')
->andWhere('jo.date_schedule >= :date')
->setParameter('statuses', $status_list, Connection::PARAM_STR_ARRAY)
->setParameter('date', $date)
->getQuery()
->execute();
// fulfill each
foreach ($res as $jo)
{
$this->jo_cache->addActiveJobOrder($jo);
}
}
}

View file

@ -9,6 +9,8 @@ use Doctrine\ORM\EntityManagerInterface;
use App\Service\RiderTracker; use App\Service\RiderTracker;
use App\Service\GISManagerInterface; use App\Service\GISManagerInterface;
use App\Service\JobOrderCache;
use App\Service\RiderCache;
use App\Entity\Rider; use App\Entity\Rider;
@ -18,8 +20,11 @@ class HomeController extends Controller
/** /**
* @Menu(selected="home") * @Menu(selected="home")
*/ */
public function index(EntityManagerInterface $em, RiderTracker $rider_tracker, public function index(
GISManagerInterface $gis_manager) EntityManagerInterface $em,
RiderTracker $rider_tracker,
GISManagerInterface $gis_manager
)
{ {
// get map // get map
$params['map_js_file'] = $gis_manager->getJSInitFile(); $params['map_js_file'] = $gis_manager->getJSInitFile();
@ -27,11 +32,40 @@ class HomeController extends Controller
return $this->render('home.html.twig', $params); return $this->render('home.html.twig', $params);
} }
public function getRiderLocations(EntityManagerInterface $em, RiderTracker $rider_tracker) public function getMapLocations(JobOrderCache $jo_cache)
{ {
// TODO: get active riders from cache $active_jos = $jo_cache->getAllActiveJobOrders();
// TODO: get active JOs from cache
// get active JOs from cache
}
public function getRiderLocations(JobOrderCache $jo_cache, RiderCache $rider_cache, EntityManagerInterface $em, RiderTracker $rider_tracker)
{
// get active JOs from cache
$active_jos = $jo_cache->getAllActiveJobOrders();
$riders = $rider_cache->getAllActiveRiders();
// TODO: optimize this
// get all riders and figure out if they have active jos
foreach ($riders as $rider_id => $rider_data)
{
$rider = $em->getRepository(Rider::class)->find($rider_id);
if ($rider == null)
{
unset($riders[$rider_id]);
continue;
}
$jo = $rider->getActiveJobOrder();
if ($jo == null)
$riders[$rider_id]['has_jo'] = false;
else
$riders[$rider_id]['has_jo'] = true;
}
// get active riders from cache
// get all riders // get all riders
/*
$riders = $em->getRepository(Rider::class)->findAll(); $riders = $em->getRepository(Rider::class)->findAll();
$locations = []; $locations = [];
@ -81,9 +115,11 @@ class HomeController extends Controller
]; ];
} }
*/
return $this->json([ return $this->json([
'riders' => $locations, 'jos' => $active_jos,
'riders' => $riders,
]); ]);
} }

View file

@ -166,7 +166,7 @@ class HubController extends Controller
{ {
$this->denyAccessUnlessGranted('hub.add', null, 'No access.'); $this->denyAccessUnlessGranted('hub.add', null, 'No access.');
error_log($req->request->get('time_open')); //error_log($req->request->get('time_open'));
// create new object // create new object
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
@ -314,6 +314,7 @@ class HubController extends Controller
'name' => $hub->getName(), 'name' => $hub->getName(),
'branch' => $hub->getBranch(), 'branch' => $hub->getBranch(),
'cnum' => $hub->getContactNumbers(), 'cnum' => $hub->getContactNumbers(),
'distance' => $hub_res['distance'],
]; ];
} }

View file

@ -4,6 +4,7 @@ namespace App\Controller;
use App\Ramcar\JOStatus; use App\Ramcar\JOStatus;
use App\Ramcar\InvoiceCriteria; use App\Ramcar\InvoiceCriteria;
use App\Ramcar\CMBServiceType;
use App\Entity\CustomerVehicle; use App\Entity\CustomerVehicle;
use App\Entity\Promo; use App\Entity\Promo;
@ -23,6 +24,9 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter; use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Doctrine\ORM\EntityManagerInterface;
use App\Service\RiderTracker;
use Catalyst\MenuBundle\Annotation\Menu; use Catalyst\MenuBundle\Annotation\Menu;
class JobOrderController extends Controller class JobOrderController extends Controller
@ -922,4 +926,29 @@ class JobOrderController extends Controller
return $this->render('job-order/popup.html.twig', [ 'jo' => $jo ]); return $this->render('job-order/popup.html.twig', [ 'jo' => $jo ]);
} }
/**
* @ParamConverter("jo", class="App\Entity\JobOrder")
*/
public function tracker(
EntityManagerInterface $em,
RiderTracker $rider_tracker,
GISManagerInterface $gis_manager,
JobOrder $jo
)
{
if ($jo === null)
return new Response('No job order data');
$rider = $jo->getRider();
// get map
$params['jo'] = $jo;
$params['rider'] = $rider;
$params['rider_pos'] = $rider_tracker->getRiderLocation($rider->getID());
$params['service_type'] = CMBServiceType::getName($jo->getServiceType());
$params['map_js_file'] = $gis_manager->getJSInitFile();
return $this->render('job-order/tracker.html.twig', $params);
}
} }

View file

@ -31,6 +31,7 @@ use App\Service\InvoiceGeneratorInterface;
use App\Service\MQTTClient; use App\Service\MQTTClient;
use App\Service\WarrantyHandler; use App\Service\WarrantyHandler;
use App\Service\RedisClientProvider; use App\Service\RedisClientProvider;
use App\Service\RiderCache;
use App\Entity\RiderSession; use App\Entity\RiderSession;
use App\Entity\Customer; use App\Entity\Customer;
@ -213,7 +214,7 @@ class RAPIController extends Controller
return $res->getReturnResponse(); return $res->getReturnResponse();
} }
public function login(Request $req, EncoderFactoryInterface $ef, RedisClientProvider $redis) public function login(Request $req, EncoderFactoryInterface $ef, RedisClientProvider $redis, RiderCache $rcache)
{ {
$required_params = [ $required_params = [
'user', 'user',
@ -255,6 +256,11 @@ class RAPIController extends Controller
$rider->setAvailable(true); $rider->setAvailable(true);
$rider_id = $rider->getID();
// cache rider location (default to hub)
// TODO: figure out longitude / latitude default
$rcache->addActiveRider($rider_id, 0, 0);
// TODO: log rider logging in // TODO: log rider logging in
$em->flush(); $em->flush();
@ -293,7 +299,7 @@ class RAPIController extends Controller
return $res->getReturnResponse(); return $res->getReturnResponse();
} }
public function logout(Request $req) public function logout(Request $req, RiderCache $rcache)
{ {
$required_params = []; $required_params = [];
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
@ -305,6 +311,9 @@ class RAPIController extends Controller
$rider = $this->session->getRider(); $rider = $this->session->getRider();
$rider->setAvailable(false); $rider->setAvailable(false);
// remove from cache
$rcache->removeActiveRider($rider->getID());
// remove rider from session // remove rider from session
$this->session->setRider(null); $this->session->setRider(null);
@ -936,7 +945,6 @@ class RAPIController extends Controller
// check if new battery // check if new battery
if (($jo->getServiceType() != ServiceType::BATTERY_REPLACEMENT_NEW) || if (($jo->getServiceType() != ServiceType::BATTERY_REPLACEMENT_NEW) ||
($jo->getServiceType() != CMBServiceType::BATTERY_REPLACEMENT_NEW)) ($jo->getServiceType() != CMBServiceType::BATTERY_REPLACEMENT_NEW))
return; return;
// customer vehicle // customer vehicle

View file

@ -148,7 +148,6 @@ class StaticContentController extends Controller
$result = $em->getRepository(StaticContent::class)->find($id); $result = $em->getRepository(StaticContent::class)->find($id);
if ($result != null) if ($result != null)
{ {
error_log($id);
$error_array['id'] = 'Duplicate ID exists.'; $error_array['id'] = 'Duplicate ID exists.';
} }
@ -226,7 +225,6 @@ class StaticContentController extends Controller
$result = $em->getRepository(StaticContent::class)->find($id); $result = $em->getRepository(StaticContent::class)->find($id);
if ($result != null) if ($result != null)
{ {
error_log($id);
$error_array['id'] = 'Duplicate ID exists.'; $error_array['id'] = 'Duplicate ID exists.';
} }

View file

@ -241,6 +241,9 @@ class VehicleController extends Controller
if (empty($row)) if (empty($row))
throw $this->createNotFoundException('The item does not exist'); throw $this->createNotFoundException('The item does not exist');
// get current batteries of vehicle to be updated
$current_batteries = $row->getBatteries();
// set and save values // set and save values
$row->setMake($req->request->get('make')) $row->setMake($req->request->get('make'))
->setModelYearFrom($req->request->get('model_year_from')) ->setModelYearFrom($req->request->get('model_year_from'))
@ -251,7 +254,6 @@ class VehicleController extends Controller
else else
$row->setDisplayMobile(false); $row->setDisplayMobile(false);
// validate // validate
$errors = $validator->validate($row); $errors = $validator->validate($row);
@ -272,6 +274,50 @@ class VehicleController extends Controller
else else
$row->setManufacturer($manufacturer); $row->setManufacturer($manufacturer);
// custom validation for batteries
$batteries = $req->request->get('batteries');
if (!empty($batteries))
{
// need to check if a battery has been removed
if (count($current_batteries) > count($batteries))
{
// battery/batteries have been removed
foreach ($current_batteries as $cbatt)
{
$cbatt_id = $cbatt->getID();
if (in_array($cbatt_id, $batteries))
{
// do nothing, move to next element
continue;
}
else
{
// cbatt_id has been deleted
$battery = $em->getRepository(Battery::class)->find($cbatt_id);
if (!empty($battery))
{
$battery->removeVehicle($row);
}
}
}
}
}
else
{
// no more battery compatible with vehicle
foreach ($current_batteries as $c_battery)
{
$cbatt_id = $c_battery->getID();
$battery = $em->getRepository(Battery::class)->find($cbatt_id);
if (!empty($battery))
{
$battery->removeVehicle($row);
}
}
}
// check if any errors were found // check if any errors were found
if (!empty($error_array)) { if (!empty($error_array)) {
// return validation failure response // return validation failure response

View file

@ -567,7 +567,7 @@ class WarrantyController extends Controller
{ {
// call service to check if warranty details is incomplete and then update warranty // call service to check if warranty details is incomplete and then update warranty
// using details from csv file // using details from csv file
error_log('Updating warranty for ' . $warr->getID()); // error_log('Updating warranty for ' . $warr->getID());
$wh->updateWarranty($warr, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase); $wh->updateWarranty($warr, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase);
} }
} }
@ -582,7 +582,7 @@ class WarrantyController extends Controller
continue; continue;
} }
error_log('Creating warranty for serial ' . $serial . ' and plate number ' . $plate_number); // error_log('Creating warranty for serial ' . $serial . ' and plate number ' . $plate_number);
$wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class); $wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class);
} }

View file

@ -0,0 +1,105 @@
<?php
namespace App\EventListener;
use Doctrine\Common\Persistence\Event\LifecycleEventArgs;
use App\Service\JobOrderCache;
use App\Ramcar\JOStatus;
use App\Entity\JobOrder;
class JobOrderActiveCacheListener
{
protected $key;
protected $mqtt;
public function __construct(JobOrderCache $jo_cache, $mqtt)
{
$this->jo_cache = $jo_cache;
$this->mqtt = $mqtt;
}
// when a new job order comes in
public function postPersist(JobOrder $jo, LifecycleEventArgs $args)
{
$status = $jo->getStatus();
switch ($status)
{
// active
case JOStatus::PENDING:
case JOStatus::RIDER_ASSIGN:
case JOStatus::ASSIGNED:
case JOStatus::IN_TRANSIT:
case JOStatus::IN_PROGRESS:
$this->processActiveJO($jo);
break;
// inactive
case JOStatus::CANCELLED:
$this->processInactiveJO($jo, 'cancel');
break;
case JOStatus::FULFILLED:
$this->processInactiveJO($jo, 'fulfill');
break;
}
}
// when a job order is updated
public function postUpdate(JobOrder $jo, LifecycleEventArgs $args)
{
$status = $jo->getStatus();
switch ($status)
{
// active
case JOStatus::PENDING:
case JOStatus::RIDER_ASSIGN:
case JOStatus::ASSIGNED:
case JOStatus::IN_TRANSIT:
case JOStatus::IN_PROGRESS:
$this->processActiveJO($jo);
break;
// inactive
case JOStatus::CANCELLED:
$this->processInactiveJO($jo, 'cancel');
break;
case JOStatus::FULFILLED:
$this->processInactiveJO($jo, 'fulfill');
break;
}
}
// when a job order is deleted
public function postRemove(JobOrder $jo, LifecycleEventArgs $args)
{
$this->processInactiveJO($jo, 'delete');
}
protected function processActiveJO($jo)
{
// save in cache
$this->jo_cache->addActiveJobOrder($jo);
// publish to mqtt
$coords = $jo->getCoordinates();
// TODO: do we put the key in config?
$this->mqtt->publish(
'jo/' . $jo->getID() . '/location',
$coords->getLatitude() . ':' . $coords->getLongitude()
);
}
protected function processInactiveJO($jo, $status = 'cancel')
{
// remove from redis cache
$this->jo_cache->removeActiveJobOrder($jo);
// TODO: publich to mqtt
$this->mqtt->publish(
'jo/' . $jo->getID() . '/status',
$status
);
}
}

View file

@ -594,7 +594,7 @@ class ResqInvoiceGenerator implements InvoiceGeneratorInterface
$diesel_price = self::REFUEL_FEE_DIESEL; $diesel_price = self::REFUEL_FEE_DIESEL;
$fuel = new InvoiceItem(); $fuel = new InvoiceItem();
error_log('fuel type - ' . $ftype); //error_log('fuel type - ' . $ftype);
switch ($ftype) switch ($ftype)
{ {
case FuelType::GAS: case FuelType::GAS:
@ -631,4 +631,77 @@ class ResqInvoiceGenerator implements InvoiceGeneratorInterface
$total['vat'] = $vat; $total['vat'] = $vat;
} }
public function processCriteria(InvoiceCriteria $criteria)
{
// initialize
$invoice = new Invoice();
$total = [
'sell_price' => 0.0,
'vat' => 0.0,
'vat_ex_price' => 0.0,
'ti_rate' => 0.0,
'total_price' => 0.0,
'discount' => 0.0,
];
$stype = $criteria->getServiceType();
$cv = $criteria->getCustomerVehicle();
$has_coolant = $criteria->hasCoolant();
// error_log($stype);
switch ($stype)
{
case ServiceType::JUMPSTART_TROUBLESHOOT:
$this->processJumpstart($total, $invoice);
break;
case ServiceType::JUMPSTART_WARRANTY:
$this->processJumpstartWarranty($total, $invoice);
case ServiceType::BATTERY_REPLACEMENT_NEW:
$this->processEntries($total, $criteria, $invoice);
/*
$this->processBatteries($total, $criteria, $invoice);
$this->processTradeIns($total, $criteria, $invoice);
*/
$this->processDiscount($total, $criteria, $invoice);
break;
case ServiceType::BATTERY_REPLACEMENT_WARRANTY:
$this->processWarranty($total, $criteria, $invoice);
break;
case ServiceType::POST_RECHARGED:
$this->processRecharge($total, $invoice);
break;
case ServiceType::POST_REPLACEMENT:
$this->processReplacement($total, $invoice);
break;
case ServiceType::TIRE_REPAIR:
$this->processTireRepair($total, $invoice, $cv);
// $this->processOtherServices($total, $invoice, $stype);
break;
case ServiceType::OVERHEAT_ASSISTANCE:
$this->processOverheat($total, $invoice, $cv, $has_coolant);
break;
case ServiceType::EMERGENCY_REFUEL:
//error_log('processing refuel');
$ftype = $criteria->getCustomerVehicle()->getFuelType();
$this->processRefuel($total, $invoice, $cv);
break;
}
// TODO: check if any promo is applied
// apply discounts
$promos = $criteria->getPromos();
$invoice->setTotalPrice($total['total_price'])
->setVATExclusivePrice($total['vat_ex_price'])
->setVAT($total['vat'])
->setDiscount($total['discount'])
->setTradeIn($total['ti_rate']);
// dump
//Debug::dump($invoice, 1);
return $invoice;
}
} }

View file

@ -0,0 +1,66 @@
<?php
namespace App\Service;
use App\Service\RedisClientProvider;
use App\Entity\JobOrder;
class JobOrderCache
{
protected $redis;
protected $active_jo_key;
public function __construct(RedisClientProvider $redis_prov, $active_jo_key)
{
$this->redis = $redis_prov->getRedisClient();
$this->active_jo_key = $active_jo_key;
}
public function addActiveJobOrder(JobOrder $jo)
{
$coords = $jo->getCoordinates();
$this->redis->geoadd(
$this->active_jo_key,
$coords->getLongitude(),
$coords->getLatitude(),
$jo->getID()
);
}
public function getAllActiveJobOrders()
{
$all_jo = $this->redis->georadius(
$this->active_jo_key,
0,
0,
41000,
'km',
['WITHCOORD' => true]
);
$jo_locs = [];
foreach ($all_jo as $jo_data)
{
$id = $jo_data[0];
$lng = $jo_data[1][0];
$lat = $jo_data[1][1];
$jo_locs[$id] = [
'longitude' => $lng,
'latitude' => $lat,
];
}
// error_log(print_r($all_jo, true));
return $jo_locs;
}
public function removeActiveJobOrder(JobOrder $jo)
{
$this->redis->zrem(
$this->active_jo_key,
$jo->getID()
);
}
}

View file

@ -164,7 +164,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$row['delivery_address'] = $orow->getDeliveryAddress(); $row['delivery_address'] = $orow->getDeliveryAddress();
$row['date_schedule'] = $orow->getDateSchedule()->format("d M Y g:i A"); $row['date_schedule'] = $orow->getDateSchedule()->format("d M Y g:i A");
$row['type'] = $orow->isAdvanceOrder() ? 'Advanced Order' : 'Immediate'; $row['type'] = $orow->isAdvanceOrder() ? 'Advanced Order' : 'Immediate';
$row['service_type'] = $service_types[$orow->getServiceType()]; $row['service_type'] = $service_types[$orow->getServiceType()] ?? 'Unknown';
$row['status'] = $statuses[$orow->getStatus()]; $row['status'] = $statuses[$orow->getStatus()];
$row['flag_advance'] = $orow->isAdvanceOrder(); $row['flag_advance'] = $orow->isAdvanceOrder();
$row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber(); $row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber();

View file

@ -9,14 +9,15 @@ class MQTTClient
{ {
const PREFIX = 'motolite.control.'; const PREFIX = 'motolite.control.';
const RIDER_PREFIX = 'motorider_'; const RIDER_PREFIX = 'motorider_';
const REDIS_KEY = 'events';
// protected $mclient; // protected $mclient;
protected $redis; protected $redis;
protected $key;
public function __construct(RedisClientProvider $redis_client) public function __construct(RedisClientProvider $redis_client, $key)
{ {
$this->redis = $redis_client->getRedisClient(); $this->redis = $redis_client->getRedisClient();
$this->key = $key;
} }
public function __destruct() public function __destruct()
@ -29,7 +30,7 @@ class MQTTClient
// $this->mclient->publish($channel, $message); // $this->mclient->publish($channel, $message);
$data = $channel . '|' . $message; $data = $channel . '|' . $message;
$this->redis->lpush(self::REDIS_KEY, $data); $this->redis->lpush($this->key, $data);
} }
public function sendEvent(JobOrder $job_order, $payload) public function sendEvent(JobOrder $job_order, $payload)

View file

@ -84,10 +84,23 @@ class MapTools
{ {
//error_log($row[0]->getName() . ' - ' . $row['dist']); //error_log($row[0]->getName() . ' - ' . $row['dist']);
$hubs[] = $row[0]; $hubs[] = $row[0];
// get coordinates of hub
$hub_coordinates = $row[0]->getCoordinates();
$cust_lat = $point->getLatitude();
$cust_lng = $point->getLongitude();
$hub_lat = $hub_coordinates->getLatitude();
$hub_lng = $hub_coordinates->getLongitude();
// get distance in kilometers from customer point to hub point
$dist = $this->distance($cust_lat, $cust_lng, $hub_lat, $hub_lng);
$final_data[] = [ $final_data[] = [
'hub' => $row[0], 'hub' => $row[0],
'db_distance' => $row['dist'], 'db_distance' => $row['dist'],
'distance' => 0, 'distance' => $dist,
'duration' => 0, 'duration' => 0,
]; ];
} }
@ -135,4 +148,18 @@ class MapTools
return $final_data; return $final_data;
*/ */
} }
protected function distance($lat1, $lon1, $lat2, $lon2)
{
if (($lat1 == $lat2) && ($lon1 == $lon2))
return 0;
$theta = $lon1 - $lon2;
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;
return round(($miles * 1.609344), 1);
}
} }

View file

@ -11,34 +11,46 @@ class RedisClientProvider
protected $host; protected $host;
protected $port; protected $port;
protected $password; protected $password;
protected $env_flag;
public function __construct($scheme, $host, $port, $password, $env_flag) public function __construct($scheme, $host, $port, $password)
{ {
$this->scheme = $scheme; $this->scheme = $scheme;
$this->host = $host; $this->host = $host;
$this->port = $port; $this->port = $port;
$this->password = $password; $this->password = $password;
$this->env_flag = $env_flag; $this->redis = null;
$this->connect();
}
protected function connect()
{
// already connected
if ($this->redis != null)
return $this->redis;
// if password is specified attempt connection
if (strlen($this->password) > 0)
{
$this->redis = new PredisClient([
"scheme" => $this->scheme,
"host" => $this->host,
"port" => $this->port,
"password" => $this->password]);
return $this->redis;
}
$this->redis = new PredisClient([
"scheme" => $this->scheme,
"host" => $this->host,
"port" => $this->port]);
return $this->redis;
} }
public function getRedisClient() public function getRedisClient()
{ {
if ($this->env_flag == 'dev')
{
$this->redis = new PredisClient([
"scheme"=>$this->scheme,
"host"=>$this->host,
"port"=>$this->port]);
}
else
{
$this->redis = new PredisClient([
"scheme"=>$this->scheme,
"host"=>$this->host,
"port"=>$this->port,
"password"=>$this->password]);
}
return $this->redis; return $this->redis;
} }
} }

View file

@ -0,0 +1,76 @@
<?php
namespace App\Service;
use App\Service\RedisClientProvider;
use App\Entity\Rider;
class RiderCache
{
protected $redis;
protected $loc_key;
protected $status_key;
public function __construct(RedisClientProvider $redis_prov, $loc_key, $status_key)
{
$this->redis = $redis_prov->getRedisClient();
$this->loc_key = $loc_key;
$this->status_key = $status_key;
}
public function addActiveRider($id, $lat, $lng)
{
$this->redis->geoadd(
$this->loc_key,
$lng,
$lat,
$id
);
}
public function getAllActiveRiders()
{
$all_riders = $this->redis->georadius(
$this->loc_key,
0,
0,
41000,
'km',
['WITHCOORD' => true]
);
$locs = [];
foreach ($all_riders as $data)
{
$id = $data[0];
$lng = $data[1][0];
$lat = $data[1][1];
$locs[$id] = [
'longitude' => $lng,
'latitude' => $lat,
];
}
// error_log(print_r($all_riders, true));
return $locs;
}
public function removeActiveRider($id)
{
$this->redis->zrem(
$this->loc_key,
$id
);
}
public function incJobOrderCount($id, $status)
{
$this->redis->hincrby($this->status_key, $id, 1);
}
public function decJobOrderCount($id, $status)
{
$this->redis->hincrby($this->status_key, $id, -1);
}
}

View file

@ -93,7 +93,7 @@ class WarrantyHandler
public function updateCustomerVehicle($serial, $batteries, $plate_number, $date_expire) public function updateCustomerVehicle($serial, $batteries, $plate_number, $date_expire)
{ {
// find customer vehicle using plate number // find customer vehicle using plate number
error_log('Finding customer vehicle with plate number ' . $plate_number); // error_log('Finding customer vehicle with plate number ' . $plate_number);
$cv_q = $this->em->createQuery('select count(cv) from App\Entity\CustomerVehicle cv where cv.plate_number = :plate_number') $cv_q = $this->em->createQuery('select count(cv) from App\Entity\CustomerVehicle cv where cv.plate_number = :plate_number')
->setParameter('plate_number', $plate_number); ->setParameter('plate_number', $plate_number);
$cv_result = $cv_q->getSingleScalarResult(); $cv_result = $cv_q->getSingleScalarResult();
@ -288,7 +288,7 @@ class WarrantyHandler
if (empty($warranty_class)) if (empty($warranty_class))
{ {
error_log('Warranty class is empty for warranty id ' . $warr->getID()); //error_log('Warranty class is empty for warranty id ' . $warr->getID());
return null; return null;
} }
@ -301,12 +301,12 @@ class WarrantyHandler
{ {
if ($batt_model == null) if ($batt_model == null)
{ {
error_log('Battery model is null for warranty id ' . $warr->getID()); //error_log('Battery model is null for warranty id ' . $warr->getID());
return null; return null;
} }
if ($batt_size == null) if ($batt_size == null)
{ {
error_log('Battery size is null for warranty id ' . $warr->getID()); //error_log('Battery size is null for warranty id ' . $warr->getID());
return null; return null;
} }

View file

@ -152,6 +152,9 @@
"setasign/fpdf": { "setasign/fpdf": {
"version": "1.8.1" "version": "1.8.1"
}, },
"symfony/asset": {
"version": "v4.4.3"
},
"symfony/cache": { "symfony/cache": {
"version": "v4.0.2" "version": "v4.0.2"
}, },

View file

@ -0,0 +1,70 @@
{% import 'menu.html.twig' as menu %}
<!DOCTYPE html>
<html lang="en">
<!-- begin::Head -->
<head>
<meta charset="UTF-8">
<title>{% block title %}{% trans %}block_title{% endtrans %}{% endblock %}</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5">
<!--begin::Web font -->
<script src="https://ajax.googleapis.com/ajax/libs/webfont/1.6.16/webfont.js"></script>
<script>
WebFont.load({
google: {"families":["Poppins:300,400,500,600,700","Roboto:300,400,500,600,700"]},
active: function() {
sessionStorage.fonts = true;
}
});
</script>
<!--end::Web font -->
<!--begin::Base Styles -->
<!--begin::Page Vendors -->
<link href="/assets/vendors/custom/fullcalendar/fullcalendar.bundle.css" rel="stylesheet" type="text/css" />
<!--end::Page Vendors -->
<link href="/assets/vendors/base/vendors.bundle.css" rel="stylesheet" type="text/css" />
<link href="/assets/demo/default/base/style.bundle.css" rel="stylesheet" type="text/css" />
<link href="/assets/css/style.css" rel="stylesheet" type="text/css" />
<!--end::Base Styles -->
<link rel="apple-touch-icon" sizes="180x180" href="/assets/images/favicon/apple-touch-icon.png">
<link rel="icon" type="image/png" sizes="32x32" href="{% trans %}icon_base_32x32{% endtrans %}">
<link rel="icon" type="image/png" sizes="16x16" href="{% trans %}icon_base_16x16{% endtrans %}">
<link rel="manifest" href="/assets/images/favicon/manifest.json">
<meta name="theme-color" content="#ffffff">
<!--begin::Extra Styles -->
{% block stylesheets %}
<link rel="stylesheet" href="https://unpkg.com/leaflet@1.6.0/dist/leaflet.css"
integrity="sha512-xwE/Az9zrjBIphAcBb3F6JVqxf46+CDLwfLMHloNu6KEQCAWi6HcDUbeOfBIptF7tcCzusKFjFw2yuvEpDL9wQ=="
crossorigin=""/>
{% endblock %}
<!--end::Extra Styles -->
</head>
<!-- end::Head -->
<!-- end::Body -->
<body class="m-page--fluid m--skin- m-content--skin-light2 m-aside-left--enabled m-aside-left--skin-dark m-aside-left--offcanvas m-aside--offcanvas-default">
<!-- begin:: Page -->
{% block body %}{% endblock %}
<!-- end:: Page -->
<!--begin::Base Scripts -->
<script src="/assets/vendors/base/vendors.bundle.js" type="text/javascript"></script>
<script src="/assets/demo/default/base/scripts.bundle.js" type="text/javascript"></script>
<!--end::Base Scripts -->
<!--begin::Page Vendors -->
<script src="/assets/vendors/custom/fullcalendar/fullcalendar.bundle.js" type="text/javascript"></script>
<!--end::Page Vendors -->
<!--begin::Page Snippets -->
<script src="/assets/app/js/dashboard.js" type="text/javascript"></script>
<script src="/assets/js/common.js" type="text/javascript"></script>
<!--end::Page Snippets -->
<!--begin::Extra Scripts -->
{% block scripts %}{% endblock %}
<!--end::Extra Scripts -->
</body>
</html>

View file

@ -13,111 +13,78 @@
{% block scripts %} {% block scripts %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
<script src="{{ asset('assets/js/dashboard_map.js') }}"></script>
<script src="{{ asset('assets/js/map_mqtt.js') }}"></script>
{{ include('map/' ~ map_js_file) }}
<script> <script>
var map; function initMap(r_markers, c_markers, icons) {
function initMap() {
var default_lat = {% trans %}default_lat{% endtrans %}; var default_lat = {% trans %}default_lat{% endtrans %};
var default_lng = {% trans %}default_long{% endtrans %}; var default_lng = {% trans %}default_long{% endtrans %};
var rider_popup_url = '/riders/[id]/popup';
var cust_popup_url = '/job-order/[id]/popup';
map = mapCreate('dashboard_map', default_lat, default_lng, 'road', 13, rider_popup_url, cust_popup_url);
}
</script>
{{ include('map/' ~ map_js_file) }}
<script>
// TODO: put this in .env
var mqtt;
var timeout = 2000;
var host = '{{ mqtt_host }}';
var port = {{ mqtt_port }};
var icon_rider_available = L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#00FF00;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
});
function onConnect() {
console.log('connected!');
mqtt.subscribe('rider/+/location');
}
function onMessage(msg) {
// console.log(msg);
console.log('received message');
var channel = msg.destinationName;
var chan_split = channel.split('/');
var payload = msg.payloadString;
// handle different channels
switch (chan_split[0]) {
case "rider":
handleRider(chan_split, payload);
break;
}
}
function handleRider(chan_split, payload) {
console.log("rider message");
switch (chan_split[2]) {
case "location":
console.log("got location for rider " + chan_split[1] + " - " + payload);
pl_split = payload.split(':');
console.log(pl_split);
// check for correct format
if (pl_split.length != 2)
break;
var lat = parseFloat(pl_split[0]);
var lng = parseFloat(pl_split[1]);
// move marker
console.log(rider_markers[chan_split[1]]);
// check if marker exists
if (rider_markers.hasOwnProperty(chan_split[1])) {
// marker's there, move it
rider_markers[chan_split[1]].setLatLng(L.latLng(lat, lng));
} else {
// no marker, make one
console.log('creating marker');
// TODO: make it add to the correct map layer
rider_markers[chan_split[1]]= L.marker([lat, lng], { icon: icon_rider_available }).addTo(map);;
}
break;
}
}
function mqttConnect() {
var d = new Date();
var client_id = "dash-{{ app.user.getID }}-" + d.getMonth() + "-" + d.getDate() + "-" + d.getHours() + "-" + d.getMinutes() + "-" + d.getSeconds() + "-" + d.getMilliseconds();
console.log(client_id);
mqtt = new Paho.MQTT.Client(host, port, client_id);
var options = { var options = {
useSSL: true, 'display_overlay': true,
timeout: 3, 'enable_popup': true,
onSuccess: onConnect, 'access_token': 'pk.eyJ1Ijoia2NvcmRlcm8iLCJhIjoiY2szbzA3ZHdsMDZxdTNsbGl4ZGNnN2VxaSJ9.LRzAe3RlV8sIP1N1x0chdw',
'div_id': 'dashboard_map',
'center_lat': default_lat,
'center_lng': default_lng,
'map_type': 'road',
'zoom': 13,
'rider_popup_url': '/riders/[id]/popup',
'cust_popup_url': '/job-order/[id]/popup',
'icons': icons
}; };
mqtt.onMessageArrived = onMessage; var dashmap = new DashboardMap(options, r_markers, c_markers);
dashmap.initialize();
dashmap.loadLocations('{{ path('rider_locations') }}');
console.log('connecting to mqtt server...'); return dashmap;
mqtt.connect(options);
} }
mqttConnect(); function initEventHandler(dashmap) {
var options = {
'track_jo': true,
'track_rider': true,
'channels': {
'rider_location': 'rider/+/location',
'jo_location': 'jo/+/location',
'jo_status': 'jo/+/status'
},
};
var event_handler = new MapEventHandler(options, dashmap);
event_handler.connect('{{ app.user.getID }}', '{{ mqtt_host }}', {{ mqtt_port }});
}
// create icons
var icons = {
'rider_active_jo': L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#FF0000;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
}),
'rider_available': L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#00FF00;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
}),
'customer': L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#0055FF;' class='marker-pin'></div><i class='fa fa-user awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
})
};
var r_markers = {};
var c_markers = {};
var dashmap = initMap(r_markers, c_markers, icons);
initEventHandler(dashmap, icons);
</script> </script>
{% endblock %} {% endblock %}

View file

@ -381,18 +381,21 @@
<th>Hub</th> <th>Hub</th>
<th>Branch</th> <th>Branch</th>
<th>Contact Numbers</th> <th>Contact Numbers</th>
<th>Distance in KM</th>
<th>Action</th> <th>Action</th>
</tr> </tr>
</thead> </thead>
<tbody id="nearest_hubs"> <tbody id="nearest_hubs">
{% if mode in ['onestep-edit'] %} <!-- {% if mode in ['onestep-edit'] %}
{% for hub in hubs %} {% for hub in hubs %}
<tr data-id="{{ hub.hub.getID }}"{{ obj.getHub and obj.getHub.getID == hub.hub.getID ? ' class="m-table__row--primary"' }}> <tr data-id="{{ hub.hub.getID }}"{{ obj.getHub and obj.getHub.getID == hub.hub.getID ? ' class="m-table__row--primary"' }}>
<td>{{ hub.hub.getName }}</td> <td>{{ hub.hub.getName }}</td>
<td>{{ hub.hub.getBranch }}</td> <td>{{ hub.hub.getBranch }}</td>
<td>{{ hub.hub.getContactNumbers }}</td> <td>{{ hub.hub.getContactNumbers }}</td>
<td></span></td>
<td></td>
{% endfor %} {% endfor %}
{% endif %} {% endif %} -->
</tbody> </tbody>
</table> </table>
</div> </div>
@ -582,6 +585,9 @@
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<button type="submit" class="btn btn-success">Submit</button> <button type="submit" class="btn btn-success">Submit</button>
{% if ftags.set_map_coordinate and is_granted('joborder.cancel') and not obj.isCancelled %}
<a href="{{ url('jo_cancel', {'id': obj.getID}) }}" class="btn btn-danger btn-cancel-job-order">Cancel Job Order</a>
{% endif %}
<a href="{{ return_url }}" class="btn btn-secondary">Back</a> <a href="{{ return_url }}" class="btn btn-secondary">Back</a>
</div> </div>
</div> </div>
@ -626,7 +632,7 @@ $(function() {
}); });
var icon_hub = L.divIcon({ var icon_hub = L.divIcon({
className: 'map-div-icon', className: 'map-div-icon',
html: "<div style='background-color:#00FF00;' class='marker-pin'></div><i class='fa fa-home awesome'>", html: "<div style='background-color:#FFFF00;' class='marker-pin'></div><i class='fa fa-home awesome'>",
iconSize: [39, 42], iconSize: [39, 42],
iconAnchor: [15, 42] iconAnchor: [15, 42]
}); });
@ -659,6 +665,7 @@ $(function() {
hub_table += '<td>' + hub['name'] + '</td>'; hub_table += '<td>' + hub['name'] + '</td>';
hub_table += '<td>' + hub['branch'] + '</td>'; hub_table += '<td>' + hub['branch'] + '</td>';
hub_table += '<td>' + hub['cnum'] + '</td>'; hub_table += '<td>' + hub['cnum'] + '</td>';
hub_table += '<td>' + hub['distance'] + '</td>';
hub_table += '<td></td>'; hub_table += '<td></td>';
hub_table += '</tr>'; hub_table += '</tr>';
} }
@ -670,13 +677,30 @@ $(function() {
{% if mode in ['onestep-edit'] %} {% if mode in ['onestep-edit'] %}
// get nearest hubs ajax // get nearest hubs ajax
var hub_table = '';
$.getJSON("{{ url('hub_nearest') }}?lat=" + lat + "&long=" + lng, function(data) { $.getJSON("{{ url('hub_nearest') }}?lat=" + lat + "&long=" + lng, function(data) {
var hubs = data['hubs']; var hubs = data['hubs'];
for (i in hubs) { for (i in hubs) {
var hub = hubs[i]; var hub = hubs[i];
var hub_marker = L.marker([hub['lat'], hub['long']], { icon: icon_hub }); var hub_marker = L.marker([hub['lat'], hub['long']], { icon: icon_hub });
hubLayerGroup.addLayer(hub_marker); hubLayerGroup.addLayer(hub_marker);
if (selected_hub == hub['id']) {
hub_table += '<tr data-id=' + hub['id'] + ' class="m-table__row--primary"' + '>';
}
else {
hub_table += '<tr data-id=' + hub['id'] + '>';
}
hub_table += '<td>' + hub['name'] + '</td>';
hub_table += '<td>' + hub['branch'] + '</td>';
hub_table += '<td>' + hub['cnum'] + '</td>';
hub_table += '<td>' + hub['distance'] + '</td>';
hub_table += '<td></td>';
hub_table += '</tr>';
} }
$('#nearest_hubs').html(hub_table);
}); });
{% endif %} {% endif %}
@ -743,14 +767,6 @@ $(function() {
}); });
$(function() { $(function() {
{% if mode in ['onestep-edit'] %}
selected_hub = '{{ obj.getHub ? obj.getHub.getID: "" }}';
$('#hub-field').val(selected_hub);
{% endif %}
{% if mode in ['onestep'] %}
selected_hub = '';
{% endif %}
$('#hubs-table').on('click', 'tr', function() { $('#hubs-table').on('click', 'tr', function() {
var id = $(this).data('id'); var id = $(this).data('id');
@ -806,13 +822,6 @@ $(function() {
}); });
$(function() { $(function() {
{% if mode in ['onestep-edit'] %}
selected_rider = '{{ obj.getRider ? obj.getRider.getID: "" }}';
$('#rider-field').val(selected_rider);
{% endif %}
{% if mode in ['onestep'] %}
selected_rider = '';
{% endif %}
$('#rider-table').on('click', 'tr', function() { $('#rider-table').on('click', 'tr', function() {
var id = $(this).data('id'); var id = $(this).data('id');
@ -830,12 +839,30 @@ $(function() {
{% if mode in ['onestep-edit'] %} {% if mode in ['onestep-edit'] %}
var lat = {{ obj.getCoordinates.getLatitude }}; var lat = {{ obj.getCoordinates.getLatitude }};
var lng = {{ obj.getCoordinates.getLongitude }}; var lng = {{ obj.getCoordinates.getLongitude }};
var hub = {{ obj.getHub.getID }};
var rider = {{ obj.getRider.getID }}; selected_hub = '{{ obj.getHub ? obj.getHub.getID: "" }}';
$('#hub-field').val(selected_hub);
selected_rider = '{{ obj.getRider ? obj.getRider.getID: "" }}';
$('#rider-field').val(selected_rider);
selectPoint(lat, lng); selectPoint(lat, lng);
// TODO: find a way to highlight the set hub // need to put selected rider on map. selected_hub is already on map because of selectPoint
riderLayerGroup.clearLayers();
$.getJSON("{{ url('hub_riders') }}?id=" + selected_hub, function(data) {
var riders = data['riders'];
for (i in riders) {
var rider = riders[i];
if (selected_rider == rider['id']) {
var rider_lat = rider['location'][0];
var rider_lng = rider['location'][1];
var rider_marker = L.marker([rider_lat, rider_lng], { icon: icon_rider_available });
riderLayerGroup.addLayer(rider_marker);
}
}
});
{% endif %} {% endif %}
{% if mode in ['update-processing', 'update-reassign-hub'] %} {% if mode in ['update-processing', 'update-reassign-hub'] %}

View file

@ -36,7 +36,9 @@
<div class="m-form__section m-form__section--first"> <div class="m-form__section m-form__section--first">
<div class="form-group m-form__group row"> <div class="form-group m-form__group row">
<div class="col-lg-6"> <div class="col-lg-6">
<label data-field="customer_vehicle">Select a vehicle:</label> <label data-field="customer_vehicle">Select a vehicle:
<span style="color:red"> *</span>
</label>
<select class="form-control m-select2" id="customer-vehicle" name="customer_vehicle"></select> <select class="form-control m-select2" id="customer-vehicle" name="customer_vehicle"></select>
<div class="form-control-feedback hide" data-field="customer_vehicle"></div> <div class="form-control-feedback hide" data-field="customer_vehicle"></div>
</div> </div>
@ -326,7 +328,9 @@
</div> </div>
<div class="form-group m-form__group row"> <div class="form-group m-form__group row">
<div class="col-lg-6"> <div class="col-lg-6">
<label data-field="delivery_address">Delivery Address</label> <label data-field="delivery_address">Delivery Address
<span style="color:red"> *</span>
</label>
<textarea name="delivery_address" class="form-control m-input" rows="4">{{ obj.getDeliveryAddress }}</textarea> <textarea name="delivery_address" class="form-control m-input" rows="4">{{ obj.getDeliveryAddress }}</textarea>
<div class="form-control-feedback hide" data-field="delivery_address"></div> <div class="form-control-feedback hide" data-field="delivery_address"></div>
</div> </div>
@ -338,7 +342,9 @@
</div> </div>
<div class="form-group m-form__group row"> <div class="form-group m-form__group row">
<div class="col-lg-12"> <div class="col-lg-12">
<label name="coordinates" data-field="coordinates">Coordinates</label> <label name="coordinates" data-field="coordinates">Coordinates
<span style="color:red"> *</span>
</label>
<div class="form-control-feedback hide" data-field="coordinates"></div> <div class="form-control-feedback hide" data-field="coordinates"></div>
<input type="hidden" id="map_lat" name="coord_lat" value=""> <input type="hidden" id="map_lat" name="coord_lat" value="">
<input type="hidden" id="map_lng" name="coord_lng" value=""> <input type="hidden" id="map_lng" name="coord_lng" value="">
@ -363,7 +369,9 @@
</div> </div>
<div class="form-group m-form__group row"> <div class="form-group m-form__group row">
<div class="col-lg-12"> <div class="col-lg-12">
<label name="hub" data-field="hub">Click on a row to select a hub</label> <label name="hub" data-field="hub">Click on a row to select a hub
<span style="color:red"> *</span>
</label>
<div class="form-control-feedback hide" data-field="hub"></div> <div class="form-control-feedback hide" data-field="hub"></div>
<input type="hidden" id="hub-field" name="hub_id" value=""> <input type="hidden" id="hub-field" name="hub_id" value="">
<div class="table-frame" data-name="hub"> <div class="table-frame" data-name="hub">
@ -372,25 +380,22 @@
<tr> <tr>
<th>Hub</th> <th>Hub</th>
<th>Branch</th> <th>Branch</th>
<!--
<th class="text-right">Distance</th>
<th class="text-right">Travel Time</th>
<th class="text-right">Available Riders</th>
<th class="text-right">Jobs For Assignment</th>
-->
<th>Contact Numbers</th> <th>Contact Numbers</th>
<th>Distance in KM</th>
<th>Action</th> <th>Action</th>
</tr> </tr>
</thead> </thead>
<tbody id="nearest_hubs"> <tbody id="nearest_hubs">
{% if mode in ['onestep-edit'] %} <!-- {% if mode in ['onestep-edit'] %}
{% for hub in hubs %} {% for hub in hubs %}
<tr data-id="{{ hub.hub.getID }}"{{ obj.getHub and obj.getHub.getID == hub.hub.getID ? ' class="m-table__row--primary"' }}> <tr data-id="{{ hub.hub.getID }}"{{ obj.getHub and obj.getHub.getID == hub.hub.getID ? ' class="m-table__row--primary"' }}>
<td>{{ hub.hub.getName }}</td> <td>{{ hub.hub.getName }}</td>
<td>{{ hub.hub.getBranch }}</td> <td>{{ hub.hub.getBranch }}</td>
<td>{{ hub.hub.getContactNumbers }}</td> <td>{{ hub.hub.getContactNumbers }}</td>
<td></span></td>
<td></td>
{% endfor %} {% endfor %}
{% endif %} {% endif %} -->
</tbody> </tbody>
</table> </table>
</div> </div>
@ -405,7 +410,9 @@
</div> </div>
<div class="form-group m-form__group row"> <div class="form-group m-form__group row">
<div class="col-lg-12"> <div class="col-lg-12">
<label name="rider" data-field="rider">Click on a row to select a rider</label> <label name="rider" data-field="rider">Click on a row to select a rider
<span style="color:red"> *</span>
</label>
<div class="form-control-feedback hide" data-field="rider"></div> <div class="form-control-feedback hide" data-field="rider"></div>
<input type="hidden" id="rider-field" name="rider_id" value=""> <input type="hidden" id="rider-field" name="rider_id" value="">
<div class="table-frame" data-name="rider"> <div class="table-frame" data-name="rider">
@ -578,6 +585,9 @@
<div class="row"> <div class="row">
<div class="col-lg-12"> <div class="col-lg-12">
<button type="submit" class="btn btn-success">Submit</button> <button type="submit" class="btn btn-success">Submit</button>
{% if ftags.set_map_coordinate and is_granted('joborder.cancel') and not obj.isCancelled %}
<a href="{{ url('jo_cancel', {'id': obj.getID}) }}" class="btn btn-danger btn-cancel-job-order">Cancel Job Order</a>
{% endif %}
<a href="{{ return_url }}" class="btn btn-secondary">Back</a> <a href="{{ return_url }}" class="btn btn-secondary">Back</a>
</div> </div>
</div> </div>
@ -622,7 +632,7 @@ $(function() {
}); });
var icon_hub = L.divIcon({ var icon_hub = L.divIcon({
className: 'map-div-icon', className: 'map-div-icon',
html: "<div style='background-color:#00FF00;' class='marker-pin'></div><i class='fa fa-home awesome'>", html: "<div style='background-color:#FFFF00;' class='marker-pin'></div><i class='fa fa-home awesome'>",
iconSize: [39, 42], iconSize: [39, 42],
iconAnchor: [15, 42] iconAnchor: [15, 42]
}); });
@ -655,24 +665,42 @@ $(function() {
hub_table += '<td>' + hub['name'] + '</td>'; hub_table += '<td>' + hub['name'] + '</td>';
hub_table += '<td>' + hub['branch'] + '</td>'; hub_table += '<td>' + hub['branch'] + '</td>';
hub_table += '<td>' + hub['cnum'] + '</td>'; hub_table += '<td>' + hub['cnum'] + '</td>';
hub_table += '<td>' + hub['distance'] + '</td>';
hub_table += '<td></td>'; hub_table += '<td></td>';
hub_table += '</tr>'; hub_table += '</tr>';
} }
$('#nearest_hubs').html(hub_table); $('#nearest_hubs').html(hub_table);
}); });
{% endif %} {% endif %}
{% if mode in ['onestep-edit'] %} {% if mode in ['onestep-edit'] %}
// get nearest hubs ajax // get nearest hubs ajax
var hub_table = '';
$.getJSON("{{ url('hub_nearest') }}?lat=" + lat + "&long=" + lng, function(data) { $.getJSON("{{ url('hub_nearest') }}?lat=" + lat + "&long=" + lng, function(data) {
var hubs = data['hubs']; var hubs = data['hubs'];
for (i in hubs) { for (i in hubs) {
var hub = hubs[i]; var hub = hubs[i];
var hub_marker = L.marker([hub['lat'], hub['long']], { icon: icon_hub }); var hub_marker = L.marker([hub['lat'], hub['long']], { icon: icon_hub });
hubLayerGroup.addLayer(hub_marker); hubLayerGroup.addLayer(hub_marker);
if (selected_hub == hub['id']) {
hub_table += '<tr data-id=' + hub['id'] + ' class="m-table__row--primary"' + '>';
}
else {
hub_table += '<tr data-id=' + hub['id'] + '>';
}
hub_table += '<td>' + hub['name'] + '</td>';
hub_table += '<td>' + hub['branch'] + '</td>';
hub_table += '<td>' + hub['cnum'] + '</td>';
hub_table += '<td>' + hub['distance'] + '</td>';
hub_table += '<td></td>';
hub_table += '</tr>';
} }
$('#nearest_hubs').html(hub_table);
}); });
{% endif %} {% endif %}
@ -739,13 +767,6 @@ $(function() {
}); });
$(function() { $(function() {
{% if mode in ['onestep-edit'] %}
selected_hub = '{{ obj.getHub ? obj.getHub.getID: "" }}';
$('#hub-field').val(selected_hub);
{% endif %}
{% if mode in ['onestep'] %}
selected_hub = '';
{% endif %}
$('#hubs-table').on('click', 'tr', function() { $('#hubs-table').on('click', 'tr', function() {
var id = $(this).data('id'); var id = $(this).data('id');
@ -801,13 +822,6 @@ $(function() {
}); });
$(function() { $(function() {
{% if mode in ['onestep-edit'] %}
selected_rider = '{{ obj.getRider ? obj.getRider.getID: "" }}';
$('#rider-field').val(selected_rider);
{% endif %}
{% if mode in ['onestep'] %}
selected_rider = '';
{% endif %}
$('#rider-table').on('click', 'tr', function() { $('#rider-table').on('click', 'tr', function() {
var id = $(this).data('id'); var id = $(this).data('id');
@ -826,9 +840,29 @@ $(function() {
var lat = {{ obj.getCoordinates.getLatitude }}; var lat = {{ obj.getCoordinates.getLatitude }};
var lng = {{ obj.getCoordinates.getLongitude }}; var lng = {{ obj.getCoordinates.getLongitude }};
selected_hub = '{{ obj.getHub ? obj.getHub.getID: "" }}';
$('#hub-field').val(selected_hub);
selected_rider = '{{ obj.getRider ? obj.getRider.getID: "" }}';
$('#rider-field').val(selected_rider);
selectPoint(lat, lng); selectPoint(lat, lng);
// TODO: find a way to highlight the set hub // need to put selected rider on map. selected_hub is already on map because of selectPoint
riderLayerGroup.clearLayers();
$.getJSON("{{ url('hub_riders') }}?id=" + selected_hub, function(data) {
var riders = data['riders'];
for (i in riders) {
var rider = riders[i];
if (selected_rider == rider['id']) {
var rider_lat = rider['location'][0];
var rider_lng = rider['location'][1];
var rider_marker = L.marker([rider_lat, rider_lng], { icon: icon_rider_available });
riderLayerGroup.addLayer(rider_marker);
}
}
});
{% endif %} {% endif %}
{% if mode in ['update-processing', 'update-reassign-hub'] %} {% if mode in ['update-processing', 'update-reassign-hub'] %}

View file

@ -2,7 +2,12 @@
{% set cv = jo.getCustomerVehicle %} {% set cv = jo.getCustomerVehicle %}
<strong>{{ cust.getNameDisplay }}</strong><br> <strong>{{ cust.getNameDisplay }}</strong><br>
{{ cv.getPlateNumber }}<br> {{ cv.getPlateNumber }}<br>
<a href="">Job Order #{{ jo.getID }}</a><br> <a href="{{ url('jo_onestep_edit_form', {'id': jo.getID}) }}">Job Order #{{ jo.getID }}</a><br>
{{ jo.getServiceTypeName }}<br> {{ jo.getServiceTypeName }}<br>
{{ jo.getStatusText }} {{ jo.getStatusText }}
{% if jo.getRider != null %}
<br><br>
{% set rider = jo.getRider %}
{{ rider.getFullName }}<br>
{{ rider.getPlateNumber }}
{% endif %}

View file

@ -0,0 +1,118 @@
{% extends 'base_minimal.html.twig' %}
{% block body %}
<div id="tracker_map" style="height:100%;"></div>
<div class="map-info">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__body">
<div class="row">
<div class="col-12 d-flex flex-row justify-content-start">
<img class="mr-2 rider-image" src="{{ asset(rider.getImageFile ? "uploads/" ~ rider.getImageFile : "assets/images/user.gif") }}" alt="">
<div class="flex-grow-1">
<div><strong>Order #{{ jo.getID }}</strong></div>
<div>{{ rider.getFullName }}</div>
<div class="m-badge m-badge--brand m-badge--wide">{{ service_type }}</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
<script src="{{ asset('assets/js/dashboard_map.js') }}"></script>
<script src="{{ asset('assets/js/map_mqtt.js') }}"></script>
{{ include('map/' ~ map_js_file) }}
<script>
function initMap(r_markers, c_markers, icons) {
var default_lat = {% trans %}default_lat{% endtrans %};
var default_lng = {% trans %}default_long{% endtrans %};
var options = {
'display_overlay': false,
'enable_popup': false,
'access_token': 'pk.eyJ1Ijoia2NvcmRlcm8iLCJhIjoiY2szbzA3ZHdsMDZxdTNsbGl4ZGNnN2VxaSJ9.LRzAe3RlV8sIP1N1x0chdw',
'div_id': 'tracker_map',
'center_lat': default_lat,
'center_lng': default_lng,
'map_type': 'road',
'zoom': 13,
'rider_popup_url': '/riders/[id]/popup',
'cust_popup_url': '/job-order/[id]/popup',
'icons': icons
};
var dashmap = new DashboardMap(options, r_markers, c_markers);
dashmap.initialize();
// dashmap.loadLocations('{{ path('rider_locations') }}');
return dashmap;
}
function initEventHandler(dashmap) {
var options = {
'track_jo': false,
'track_rider': true,
'channels': {
'rider_location': 'rider/{{ rider.getID }}/location',
'jo_location': 'none',
'jo_status': 'none'
},
};
var event_handler = new MapEventHandler(options, dashmap);
event_handler.connect('tracker', '{{ mqtt_host }}', {{ mqtt_port }});
}
// create icons
var icons = {
'rider_active_jo': L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#FF0000;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
}),
'rider_available': L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#00FF00;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
}),
'customer': L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#0055FF;' class='marker-pin'></div><i class='fa fa-user awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
}),
'hub': L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#0055FF;' class='marker-pin'></div><i class='fa fa-home awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
})
};
var r_markers = {};
var c_markers = {};
var h_markers = {};
{% set hub = jo.getHub %}
var dashmap = initMap(r_markers, c_markers, icons);
dashmap.putCustomerMarker({{ jo.getID }}, {{ jo.getCoordinates.getLatitude }}, {{ jo.getCoordinates.getLongitude }});
dashmap.putRiderActiveJOMarker({{ rider.getID }}, {{ rider_pos.getLatitude }}, {{ rider_pos.getLongitude }});
dashmap.putMarker(
{{ hub.getID }},
{{ hub.getCoordinates.getLatitude }},
{{ hub.getCoordinates.getLongitude }},
h_markers,
icons['hub'],
dashmap.map,
'/'
);
initEventHandler(dashmap, icons);
</script>
{% endblock %}

View file

@ -2,142 +2,3 @@
integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew==" integrity="sha512-gZwIG9x3wUXg2hdXF6+rVkLF/0Vi9U8D2Ntg4Ga5I5BZpVkVxlJWbSQtXPSiUTtC0TjtGOmxa1AJPuV0CPthew=="
crossorigin=""> crossorigin="">
</script> </script>
<script>
var rider_markers = {};
var cust_markers = {};
function mapCreate(div_id, center_lat, center_lng, map_type, zoom, rider_popup_url, cust_popup_url) {
var map = L.map(div_id).setView(
[center_lat, center_lng],
zoom
);
// add tile layer
// TODO: put access token in .env
var streets = L.tileLayer('https://api.mapbox.com/styles/v1/{id}/tiles/{z}/{x}/{y}?access_token={accessToken}', {
attribution: 'Map data &copy; <a href="https://www.openstreetmap.org/">OpenStreetMap</a> contributors, <a href="https://creativecommons.org/licenses/by-sa/2.0/">CC-BY-SA</a>, Imagery © <a href="https://www.mapbox.com/">Mapbox</a>',
maxZoom: 18,
id: 'mapbox/streets-v11',
accessToken: 'pk.eyJ1Ijoia2NvcmRlcm8iLCJhIjoiY2szbzA3ZHdsMDZxdTNsbGl4ZGNnN2VxaSJ9.LRzAe3RlV8sIP1N1x0chdw'
}).addTo(map);
// layer groups
// .addTo(map) --> this will display your riders by default
var lg_avail_rider = L.layerGroup().addTo(map);
var lg_jo_rider = L.layerGroup().addTo(map);
var lg_cust = L.layerGroup().addTo(map);
// this little snippet will not display your riders by default.
// Instead, a toggle button will display in the map, with a checkbox with text Riders.
// Check that to display the riders
//var ridersLayerGroup = L.layerGroup();
// create icons
var icon_rider_active_jo = L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#FF0000;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
});
var icon_rider_available = L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#00FF00;' class='marker-pin'></div><i class='fa fa-bolt awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
});
var icon_customer = L.divIcon({
className: 'map-div-icon',
html: "<div style='background-color:#0055FF;' class='marker-pin'></div><i class='fa fa-user awesome'>",
iconSize: [39, 42],
iconAnchor: [15, 42]
});
$.ajax({
url: '{{ path('rider_locations') }}',
}).done(function(response) {
// clear all markers
lg_avail_rider.clearLayers();
lg_jo_rider.clearLayers();
lg_cust.clearLayers();
// get riders and mark
var riders = response.riders;
$.each(riders, function(rider_id, rider_data) {
// rider location
var point = rider_data['loc'];
var lat = point[0];
var long = point[1];
// customer location
var cloc = rider_data['cust_loc'];
var clat = cloc[0];
var clong = cloc[1];
// rider popup content
rider_popup = '<strong>' + rider_data['label'] + '</strong>';
// create rider markers
if (rider_data['has_jo']) {
var jo_data = rider_data['jo'];
// rider_markers[rider_id] = L.marker([lat, long], { icon: icon_rider_active_jo }).bindPopup(rider_popup);
rider_markers[rider_id] = L.marker([lat, long], { icon: icon_rider_active_jo }).bindPopup('Loading...');
// var cust_marker = L.marker([clat, clong], { icon: icon_customer }).bindPopup('Loading...');
cust_markers[jo_data['id']] = L.marker([clat, clong], { icon: icon_customer }).bindPopup('Loading...');
lg_cust.addLayer(cust_markers[jo_data['id']]);
lg_jo_rider.addLayer(rider_markers[rider_id]);
// customer popup ajax
cust_markers[jo_data['id']].on('click', function(e) {
var popup = e.target.getPopup();
var url = cust_popup_url.replace('[id]', jo_data['id']);
console.log(url);
$.get(url).done(function(data) {
popup.setContent(data);
popup.update();
});
});
} else {
// rider_markers[rider_id]= L.marker([lat, long], { icon: icon_rider_available }).bindPopup(rider_popup);
rider_markers[rider_id]= L.marker([lat, long], { icon: icon_rider_available }).bindPopup('Loading...');
lg_avail_rider.addLayer(rider_markers[rider_id]);
}
// ajax loading of rider popup
rider_markers[rider_id].on('click', function(e) {
var popup = e.target.getPopup();
var url = rider_popup_url.replace('[id]', rider_id);
console.log(url);
$.get(url).done(function(data) {
popup.setContent(data);
popup.update();
});
});
});
// console.log(rider_markers);
});
// base layer
var baseMaps = {
'Streets': streets
};
// overlay layer
var overlayMaps = {
'Available Riders' : lg_avail_rider,
'JO Riders' : lg_jo_rider,
'Customers' : lg_cust
}
L.control.layers(baseMaps, overlayMaps).addTo(map);
return map;
}
initMap();
</script>

View file

@ -4,7 +4,7 @@
{% set cust = jo.getCustomer %} {% set cust = jo.getCustomer %}
{% set cv = jo.getCustomerVehicle %} {% set cv = jo.getCustomerVehicle %}
<br> <br>
<a href="">Job Order #{{ jo.getID }}</a><br> <a href="{{ url('jo_onestep_edit_form', {'id': jo.getID}) }}">Job Order #{{ jo.getID }}</a><br>
{{ jo.getServiceTypeName }}<br> {{ jo.getServiceTypeName }}<br>
{{ jo.getStatusText }}<br><br> {{ jo.getStatusText }}<br><br>
{{ cust.getNameDisplay }}<br> {{ cust.getNameDisplay }}<br>

View file

@ -135,13 +135,23 @@
$(function() { $(function() {
$("#row-form").submit(function(e) { $("#row-form").submit(function(e) {
var form = $(this); var form = $(this);
var formdata = form.serialize();
e.preventDefault(); e.preventDefault();
// add battery data
bdata = '';
$.each(battRows, function(index, battery) {
bdata += "&batteries%5B%5D=" + battery.id;
});
// append to form data
formdata += bdata;
$.ajax({ $.ajax({
method: "POST", method: "POST",
url: form.prop('action'), url: form.prop('action'),
data: form.serialize() data: formdata
}).done(function(response) { }).done(function(response) {
// remove all error classes // remove all error classes
removeErrors(); removeErrors();
@ -192,6 +202,7 @@ $(function() {
}); });
var battRows = []; var battRows = [];
var batteryIds = [];
{% for batt in obj.getBatteries %} {% for batt in obj.getBatteries %}
trow = { trow = {
@ -202,8 +213,30 @@ $(function() {
}; };
battRows.push(trow); battRows.push(trow);
batteryIds.push({{ batt.getID }});
{% endfor %} {% endfor %}
// remove battery from table
$(document).on('click', '.btn-delete', function(e) {
var btn = $(this);
var id = $(this).data('id');
$.each(battRows, function(index, battery) {
if (battery.id == id) {
battRows.splice(index, 1);
return false;
}
});
// remove from battery ids
batteryIds.splice(batteryIds.indexOf(id), 1);
// reload table
battTable.row(btn.parents('tr')).remove();
battTable.originalDataSet = battRows;
battTable.reload();
});
// battery data table // battery data table
var battOptions = { var battOptions = {
data: { data: {
@ -235,7 +268,17 @@ $(function() {
{ {
field: 'sell_price', field: 'sell_price',
title: 'Price' title: 'Price'
} },
{
field: 'Actions',
width: 70,
title: 'Actions',
sortable: false,
overflow: 'visible',
template: function (row, index, datatable) {
return '<button data-id="' + row.id + '" type="button" class="m-portlet__nav-link btn m-btn m-btn--hover-danger m-btn--icon m-btn--icon-only m-btn--pill btn-delete" title="Delete"><i class="la la-trash"></i></button>';
},
}
], ],
pagination: false pagination: false
}; };

View file

@ -26,4 +26,4 @@ default_lat: 14.6091
default_long: 121.0223 default_long: 121.0223
#default_lat: 3.084216 #default_lat: 3.084216
#default_long: 101.6129996 #default_long: 101.6129996
default_region: my default_region: ph

View file

@ -0,0 +1,41 @@
import paho.mqtt.client as mqtt
import ssl
import redis
import time
import signal
import sys
import os
import json
class RiderLocationCache(object):
def run(self, client):
print "running loop..."
client.loop_forever()
# TODO: fix this and put these guys back under the class
def init_subscriptions(client):
print "subscribing to rider/+/location"
client.subscribe('rider/+/location')
def on_connect(client, userdata, flags, rc):
init_subscriptions(client)
#print("Connected with result code "+str(rc))
# client.subscribe("$SYS/#")
def on_publish(client, userdata, mid):
pass
def on_message(client, userdata, message):
redis_conn = userdata['redis']
topic_split = message.topic.split('/')
if topic_split[0] != 'rider':
return;
payload_split = message.payload.split(':')
rider_long = str(payload_split[1])
rider_lat = str(payload_split[0])
# set the location
redis_conn.geoadd('loc_rider_active', rider_long, rider_lat, topic_split[1])

Binary file not shown.

View file

@ -0,0 +1,26 @@
import paho.mqtt.client as mqtt
import rider_location_cache as rlc
import ssl
import redis
import logging
redis_client = redis.StrictRedis(host='localhost', port=6379, db=0)
userdata = {'redis': redis_client}
client = mqtt.Client("", True, userdata)
client.on_connect = rlc.on_connect
# client.on_publish = on_publish
client.on_message = rlc.on_message
#client.tls_set(
# "/etc/letsencrypt/live/resqaws.jankstudio.com/fullchain.pem", cert_reqs=ssl.CERT_NONE,
# tls_version=ssl.PROTOCOL_TLSv1)
#client.tls_set(
# "/root/aws_ssl_keys/fullchain.pem", cert_reqs=ssl.CERT_NONE,
# tls_version=ssl.PROTOCOL_TLSv1)
#client.connect("resqaws.jankstudio.com", 8883, 60)
client.connect("localhost", 1883, 60)
rider_location = rlc.RiderLocationCache()
rider_location.run(client)

View file

@ -0,0 +1,12 @@
[Unit]
Description=Rider Location Cache Service
After=mosquitto.service redis.service
[Service]
Type=simple
ExecStart=/usr/bin/python /root/www/resq/utils/rider_location_cache/riderloc.py
StandardInput=tty-force
Restart=always
[Install]
WantedBy=multi-user.target

View file

@ -1,7 +1,6 @@
import paho.mqtt.client as mqtt import paho.mqtt.client as mqtt
import yaml
import ssl import ssl
from threading import Thread
from daemonize import Daemonize
import redis import redis
import time import time
import signal import signal
@ -10,29 +9,23 @@ import os
import logging import logging
# TODO: yaml configuration file for redis and mqtt settings
def sigint_handler(signal, frame):
#logging.warning('Interrupted')
sys.exit(0)
os._exit(0)
def on_connect(client, userdata, flags, rc): def on_connect(client, userdata, flags, rc):
#logging.info("Connected with result code "+str(rc)) logging.info("Connected with result code "+str(rc))
client.subscribe("$SYS/#") #client.subscribe("$SYS/#")
def on_publish(client, userdata, mid): def on_publish(client, userdata, mid):
pass pass
def getRedis(i, client, logger): def redis_listen(client, logger):
logger.info("Listening in redis events") logger.info("Listening in redis events")
r = redis.StrictRedis(host='localhost', port=6379, db=0) r = redis.StrictRedis(host='localhost', port=6379, db=0)
while 1: while 1:
time.sleep(0) time.sleep(0)
data = r.brpop("events", 10) data = r.brpop("mqtt_events", 10)
if data: if data:
info = data[1].split('|') info = data[1].split('|')
logger.info("Channel: " + info[0] + " message: " + info[1]) logger.info("Channel: " + info[0] + " message: " + info[1])
@ -40,9 +33,6 @@ def getRedis(i, client, logger):
def sigint_handler(signal, frame):
sys.exit(0)
def get_logger(): def get_logger():
logger = logging.getLogger("mqtt_logger") logger = logging.getLogger("mqtt_logger")
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
@ -64,25 +54,14 @@ def main():
client.on_connect = on_connect client.on_connect = on_connect
client.on_publish = on_publish client.on_publish = on_publish
client.tls_set( # configure mqtt broker to accept localhost
"/etc/letsencrypt/live/resqaws.jankstudio.com/fullchain.pem", cert_reqs=ssl.CERT_NONE, client.connect("localhost", 1883, 60)
tls_version=ssl.PROTOCOL_TLSv1)
client.connect("resqaws.jankstudio.com", 8883, 60) client.loop_start()
redis_listen(client, logger)
client.loop_end()
logger.info("Starting redis thread") #client.loop_forever()
t = Thread(target=getRedis, args=(1, client, logger))
t.start()
signal.signal(signal.SIGINT, sigint_handler)
client.loop_forever()
#logging.basicConfig(filename='/tmp/mqtt_sender.log', level=logging.INFO)
#logging.info('Started mqtt_sender')
#pid = "/tmp/mqtt_sender.pid"
#daemon = Daemonize(app="mqtt_sender", pid=pid, action=main)
#daemon.start()
main() main()

View file

@ -12,9 +12,9 @@ client.on_message = rlc.on_message
#client.tls_set( #client.tls_set(
# "/etc/letsencrypt/live/resqaws.jankstudio.com/fullchain.pem", cert_reqs=ssl.CERT_NONE, # "/etc/letsencrypt/live/resqaws.jankstudio.com/fullchain.pem", cert_reqs=ssl.CERT_NONE,
# tls_version=ssl.PROTOCOL_TLSv1) # tls_version=ssl.PROTOCOL_TLSv1)
client.tls_set( #client.tls_set(
"/root/aws_ssl_keys/fullchain.pem", cert_reqs=ssl.CERT_NONE, # "/root/aws_ssl_keys/fullchain.pem", cert_reqs=ssl.CERT_NONE,
tls_version=ssl.PROTOCOL_TLSv1) # tls_version=ssl.PROTOCOL_TLSv1)
#client.connect("resqaws.jankstudio.com", 8883, 60) #client.connect("resqaws.jankstudio.com", 8883, 60)
client.connect("localhost", 8883, 60) client.connect("localhost", 8883, 60)