diff --git a/config/routes/notification.yaml b/config/routes/notification.yaml
new file mode 100644
index 00000000..f3a15104
--- /dev/null
+++ b/config/routes/notification.yaml
@@ -0,0 +1,9 @@
+notification_ajax_list:
+ path: /ajax/notifications
+ controller: App\Controller\NotificationController::ajaxList
+ methods: [GET]
+
+notification_ajax_update:
+ path: /ajax/notifications
+ controller: App\Controller\NotificationController::ajaxUpdate
+ methods: [POST]
diff --git a/config/services.yaml b/config/services.yaml
index 3d9fd7a3..7d585e08 100644
--- a/config/services.yaml
+++ b/config/services.yaml
@@ -221,6 +221,12 @@ services:
event: 'postPersist'
entity: 'App\Entity\JobOrder'
+ App\Service\NotificationManager:
+ arguments:
+ $redis_prov: "@App\\Service\\RedisClientProvider"
+ $redis_mqtt_key: "mqtt_events"
+ $em: "@doctrine.orm.entity_manager"
+
App\Service\JobOrderCache:
arguments:
$redis_prov: "@App\\Service\\RedisClientProvider"
diff --git a/public/assets/js/dashboard_map.js b/public/assets/js/dashboard_map.js
index 5d753cab..2df2d69f 100644
--- a/public/assets/js/dashboard_map.js
+++ b/public/assets/js/dashboard_map.js
@@ -231,30 +231,38 @@ class DashboardMap {
);
}
- putRiderAvailableMarker(id, lat, lng, name) {
- this.putMarkerWithLabel(
- id,
- lat,
- lng,
- this.rider_markers,
- this.options.icons.rider_available,
- this.layer_groups.rider_available,
- this.options.rider_popup_url,
- name
- );
+ putRiderAvailableMarker(id, lat, lng) {
+ var my = this;
+
+ my.getRiderName(id, function(name) {
+ my.putMarkerWithLabel(
+ id,
+ lat,
+ lng,
+ my.rider_markers,
+ my.options.icons.rider_available,
+ my.layer_groups.rider_available,
+ my.options.rider_popup_url,
+ name
+ );
+ });
}
- putRiderActiveJOMarker(id, lat, lng, name) {
- this.putMarkerWithLabel(
- id,
- lat,
- lng,
- this.rider_markers,
- this.options.icons.rider_active_jo,
- this.layer_groups.rider_active_jo,
- this.options.rider_popup_url,
- name
- );
+ putRiderActiveJOMarker(id, lat, lng) {
+ var my = this;
+
+ my.getRiderName(id, function(name) {
+ my.putMarkerWithLabel(
+ id,
+ lat,
+ lng,
+ my.rider_markers,
+ my.options.icons.rider_active_jo,
+ my.layer_groups.rider_active_jo,
+ my.options.rider_popup_url,
+ name
+ );
+ });
}
removeRiderMarker(id) {
@@ -305,41 +313,40 @@ class DashboardMap {
var lng = data.longitude;
var name = '';
- if (my.rider_names.hasOwnProperty(id)) {
- name = my.rider_names[id];
-
- if (data.has_jo)
- my.putRiderActiveJOMarker(id, lat, lng, name);
- else
- my.putRiderAvailableMarker(id, lat, lng, name)
-
- } else {
- getRiderName(id, my.options.rider_name_url, function(name) {
- my.rider_names[id] = name;
-
- if (data.has_jo)
- my.putRiderActiveJOMarker(id, lat, lng, name);
- else
- my.putRiderAvailableMarker(id, lat, lng, name)
- });
- }
+ if (data.has_jo)
+ my.putRiderActiveJOMarker(id, lat, lng);
+ else
+ my.putRiderAvailableMarker(id, lat, lng);
});
// console.log(rider_markers);
});
}
-}
-function getRiderName(id, url, callback) {
- var name = '';
- var rider_url = url.replace('[id]', id);
+ getRiderName(id, callback) {
+ var name = '';
+ var rider_url = this.options.rider_name_url.replace('[id]', id);
- $.ajax({
- method: "GET",
- url: rider_url
- }).done(function(response) {
- name = response.rider_name;
- callback(name);
- });
+ var my = this;
+
+ console.log('getting rider name for rider ' + id);
+
+ // check if we have it in cache
+ if (this.rider_names.hasOwnProperty(id)) {
+ name = this.rider_names[id];
+ callback(name);
+ } else {
+ // ajax call to get it
+ $.ajax({
+ method: "GET",
+ url: rider_url
+ }).done(function(response) {
+ name = response.rider_name;
+
+ // set name in cache
+ my.rider_names[id] = name;
+ callback(name);
+ });
+ }
+ }
}
-
diff --git a/public/assets/js/map_mqtt.js b/public/assets/js/map_mqtt.js
index 63f3e3b3..e3fc9900 100644
--- a/public/assets/js/map_mqtt.js
+++ b/public/assets/js/map_mqtt.js
@@ -142,6 +142,7 @@ class MapEventHandler {
}
} else {
console.log('rider not in availability check');
+ display_marker = false;
}
// TODO: cache rider availability (available vs active jo) status and check before displaying icon
diff --git a/public/assets/js/notification.js b/public/assets/js/notification.js
new file mode 100644
index 00000000..9a4b169c
--- /dev/null
+++ b/public/assets/js/notification.js
@@ -0,0 +1,111 @@
+class NotificationHandler {
+ constructor(options) {
+ this.options = options;
+ }
+
+ clearAll() {
+ // clear notification count
+ document.getElementById('notif-count').innerHTML = '';
+
+ // remove notifications
+ document.getElementById('notif-body').innerHTML = '';
+ }
+
+ loadAll() {
+ console.log('loading notifications');
+ // ajax load
+ var self = this;
+ var notif_update_url = this.options['notif_ajax_update_url'];
+ var xhr = new XMLHttpRequest();
+ xhr.open('GET', this.options['notif_ajax_url']);
+ xhr.onload = function() {
+ if (xhr.status === 200) {
+ var data = JSON.parse(xhr.responseText);
+ var notifs = data.notifications;
+
+ // update notification unread count
+ var count_html = data.unread_count;
+ document.getElementById('notif-count').innerHTML = count_html;
+
+ // do we have any notifications?
+ if (notifs.length <= 0)
+ return;
+
+ // add actual notifications
+ var notif_body = document.getElementById('notif-body');
+ var notif_index = 0;
+ notifs.forEach(function(notif) {
+ var notif_date = moment(notif.date).fromNow();
+
+ var notif_html = '
';
+ notif_html += '
';
+ notif_html += '
';
+ notif_html += '' + notif.text + ''
+ notif_html += '';
+ notif_html += '
';
+ notif_html += notif_date;
+ notif_html += '';
+ notif_html += '
';
+
+ notif_body.insertAdjacentHTML('beforeend', notif_html);
+
+ document.getElementsByClassName('m-list-timeline__item')[notif_index].addEventListener('click', function(e) {
+ e.preventDefault();
+ $.ajax({
+ method: "POST",
+ url: notif_update_url,
+ data: {id: notif.id}
+ }).done(function(response) {
+ window.location.href = notif.link;
+ });
+ });
+
+ notif_index++;
+ });
+ }
+
+ };
+ xhr.send();
+ }
+
+ listen(user_id, host, port, use_ssl = false) {
+ var d = new Date();
+ var client_id = "dash-" + user_id + "-" + d.getMonth() + "-" + d.getDate() + "-" + d.getHours() + "-" + d.getMinutes() + "-" + d.getSeconds() + "-" + d.getMilliseconds();
+
+ this.mqtt = new Paho.MQTT.Client(host, port, client_id);
+ var options = {
+ useSSL: use_ssl,
+ timeout: 3,
+ invocationContext: this,
+ onSuccess: this.onConnect.bind(this)
+ }
+
+ this.mqtt.onMessageArrived = this.onMessage.bind(this);
+
+ this.mqtt.connect(options);
+ }
+
+ onConnect(icontext) {
+ console.log('notification mqtt connected');
+ var my = icontext.invocationContext;
+
+ // subscribe to general notifications
+ my.mqtt.subscribe('user/0/notification');
+ }
+
+ onMessage(msg) {
+ console.log('notification event received');
+ // we don't care about messasge, we update
+ this.clearAll();
+ this.loadAll();
+ }
+
+ getIcon(type_id) {
+ if (type_id in this.options['icons']) {
+ return this.options['icons'][type_id];
+ }
+
+ return this.options['default_icon'];
+ }
+
+}
diff --git a/src/Controller/NotificationController.php b/src/Controller/NotificationController.php
new file mode 100644
index 00000000..64150c51
--- /dev/null
+++ b/src/Controller/NotificationController.php
@@ -0,0 +1,111 @@
+sub(new DateInterval('PT10M'));
+ $notifs = [
+ [
+ 'id' => 1,
+ 'type' => 'jo_new',
+ 'date' => $date->format('Y-m-d\TH:i:s.000P'),
+ 'text' => 'Sample incoming job order',
+ 'link' => '#',
+ ], [
+ 'id' => 2,
+ 'type' => 'rider_accept',
+ 'date' => $date->format('Y-m-d\TH:i:s.000P'),
+ 'text' => 'Sample rider has accepted job order',
+ 'link' => '#',
+ ], [
+ 'id' => 3,
+ 'type' => 'jo_cancel',
+ 'date' => $date->format('Y-m-d\TH:i:s.000P'),
+ 'text' => 'Customer has cancelled job order.',
+ 'link' => '#',
+ ], [
+ 'id' => 3,
+ 'type' => 'rider_reject',
+ 'date' => $date->format('Y-m-d\TH:i:s.000P'),
+ 'text' => 'Rider has rejected job order. Job order needs to be reassigned.',
+ 'link' => '#',
+ ],
+ ];
+ $sample_data = [
+ 'count' => 4,
+ 'notifications' => $notifs,
+ ];
+ */
+
+ $notifs = $em->getRepository(Notification::class)->findBy(['user_id' => 0]);
+ $notif_data = [];
+
+ $unread_count = 0;
+ foreach ($notifs as $notif)
+ {
+ if (!($notif->isRead()))
+ $unread_count++;
+
+ $notif_data[] = [
+ 'id' => $notif->getID(),
+ 'type' => 'jo_new',
+ 'is_read' => $notif->isRead(),
+ 'is_fresh' => $notif->isFresh(),
+ 'date' => $notif->getDateCreate()->format('Y-m-d\TH:i:s.000P'),
+ 'text' => $notif->getMessage(),
+ 'link' => $notif->getURL(),
+ ];
+ }
+
+
+ $sample_data = [
+ 'count' => count($notif_data),
+ 'unread_count' => $unread_count,
+ 'notifications' => $notif_data,
+ ];
+
+ $res = new JsonResponse($sample_data);
+
+ return $res;
+ }
+
+ // TODO: security
+ public function ajaxUpdate(EntityManagerInterface $em, Request $req)
+ {
+ $notif_id = $req->request->get('id');
+ error_log($notif_id);
+
+ $notif = $em->getRepository(Notification::class)->find($notif_id);
+
+ if ($notif != null)
+ {
+ // TODO: fresh is if unread and still within x hours
+ // but for now fresh and unread are both the same
+ $notif->setIsRead(true);
+ $notif->setIsFresh(false);
+
+ $em->persist($notif);
+ $em->flush();
+ }
+
+ $res = new JsonResponse();
+
+ return $res;
+ }
+}
diff --git a/src/Entity/Notification.php b/src/Entity/Notification.php
new file mode 100644
index 00000000..012bcb15
--- /dev/null
+++ b/src/Entity/Notification.php
@@ -0,0 +1,148 @@
+date_create = new DateTime();
+ $this->flag_read = false;
+ $this->flag_fresh = true;
+ }
+
+ public function getID()
+ {
+ return $this->id;
+ }
+
+ public function getDateCreate()
+ {
+ return $this->date_create;
+ }
+
+ public function setUserID($user_id)
+ {
+ $this->user_id = $user_id;
+ return $this;
+ }
+
+ public function getUserID()
+ {
+ return $this->user_id;
+ }
+
+ public function setIcon($icon)
+ {
+ $this->icon = $icon;
+ return $this;
+ }
+
+ public function getIcon()
+ {
+ return $this->icon;
+ }
+
+ public function setMessage($message)
+ {
+ $this->message = $message;
+ return $this;
+ }
+
+ public function getMessage()
+ {
+ return $this->message;
+ }
+
+ public function setURL($url)
+ {
+ $this->url = $url;
+ return $this;
+ }
+
+ public function getURL()
+ {
+ return $this->url;
+ }
+
+ public function setIsRead($bool = true)
+ {
+ $this->flag_read = $bool;
+ return $this;
+ }
+
+ public function isRead()
+ {
+ return $this->flag_read;
+ }
+
+ public function setIsFresh($bool = true)
+ {
+ $this->flag_fresh = $bool;
+ return $this;
+ }
+
+ public function isFresh()
+ {
+ return $this->flag_fresh;
+ }
+}
diff --git a/src/Service/JobOrderHandler/CMBJobOrderHandler.php b/src/Service/JobOrderHandler/CMBJobOrderHandler.php
index 9ffb102a..b55318e1 100644
--- a/src/Service/JobOrderHandler/CMBJobOrderHandler.php
+++ b/src/Service/JobOrderHandler/CMBJobOrderHandler.php
@@ -449,6 +449,7 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$jo = $em->getRepository(JobOrder::class)->find($id);
$old_jo_status = null;
+ $old_rider = null;
if (empty($jo))
{
// new job order
@@ -456,7 +457,8 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
}
else
{
- //$old_rider = $jo->getRider();
+ // need to get old values of rider and status to see if we need to change JO status or not
+ $old_rider = $jo->getRider();
$old_jo_status = $jo->getStatus();
}
@@ -639,10 +641,25 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
// and JO is already in_transit or in_progress?
// retain old jo status if it's an update JO
- if ($old_jo_status != null)
- $jo->setStatus($old_jo_status);
- else
+ // check old rider if it is also a reassignment
+ // old_rider should be null if JO has been rejected
+ if (($old_rider == null) && ($old_jo_status == null))
$jo->setStatus(JOStatus::ASSIGNED);
+ else
+ {
+ error_log('not a new JO');
+ $new_rider = $jo->getRider();
+ if ($new_rider != $old_rider)
+ {
+ // reassignment
+ $jo->setStatus(JOStatus::ASSIGNED);
+ }
+ else
+ {
+ if ($old_jo_status != null)
+ $jo->setStatus($old_jo_status);
+ }
+ }
// check if user is null, meaning call to create came from API
if ($user != null)
@@ -702,8 +719,8 @@ class CMBJobOrderHandler implements JobOrderHandlerInterface
$em->flush();
// check if JO has been reassigned
- //if ($old_rider != $rider)
- if ($old_jo_status != $jo->getStatus())
+ if ($old_rider != $jo->getRider())
+ //if ($old_jo_status != $jo->getStatus())
{
error_log('JO has been reassigned');
// TODO: refactor later
diff --git a/src/Service/NotificationManager.php b/src/Service/NotificationManager.php
new file mode 100644
index 00000000..86de0348
--- /dev/null
+++ b/src/Service/NotificationManager.php
@@ -0,0 +1,48 @@
+redis = $redis_prov->getRedisClient();
+ $this->redis_mqtt_key = $redis_mqtt_key;
+ $this->em = $em;
+ }
+
+ // set user_id to 0 for all
+ public function sendNotification($user_id, $msg, $url)
+ {
+ // send mqtt
+ $chan = $this->getChannel($user_id);
+ $data = $chan . '|' . $msg;
+ $this->redis->lpush($this->redis_mqtt_key, $data);
+
+ // create notif
+ $notif = new Notification();
+ $notif->setUserID($user_id)
+ ->setIcon('')
+ ->setMessage($msg)
+ ->setURL($url);
+
+ // save to db
+ $this->em->persist($notif);
+ $this->em->flush();
+ }
+
+ protected function getChannel($user_id)
+ {
+ return str_replace('{user_id}', $user_id, self::NOTIF_KEY );
+ }
+}
diff --git a/src/Service/RiderAPIHandler/CMBRiderAPIHandler.php b/src/Service/RiderAPIHandler/CMBRiderAPIHandler.php
index 282f6bb6..80a8301b 100644
--- a/src/Service/RiderAPIHandler/CMBRiderAPIHandler.php
+++ b/src/Service/RiderAPIHandler/CMBRiderAPIHandler.php
@@ -5,6 +5,7 @@ namespace App\Service\RiderAPIHandler;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
+use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
use App\Ramcar\CMBServiceType;
use App\Ramcar\TradeInType;
@@ -23,6 +24,7 @@ use App\Service\WarrantyHandler;
use App\Service\JobOrderHandlerInterface;
use App\Service\InvoiceGeneratorInterface;
use App\Service\RiderTracker;
+use App\Service\NotificationManager;
use App\Entity\RiderSession;
use App\Entity\Rider;
@@ -53,13 +55,16 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
protected $ic;
protected $session;
protected $upload_dir;
+ protected $nm;
+ protected $router;
public function __construct(EntityManagerInterface $em, RedisClientProvider $redis,
EncoderFactoryInterface $ef, RiderCache $rcache,
string $country_code, MQTTClient $mclient,
WarrantyHandler $wh, JobOrderHandlerInterface $jo_handler,
InvoiceGeneratorInterface $ic, string $upload_dir,
- RiderTracker $rider_tracker)
+ RiderTracker $rider_tracker, NotificationManager $nm,
+ UrlGeneratorInterface $router)
{
$this->em = $em;
$this->redis = $redis;
@@ -72,6 +77,8 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$this->ic = $ic;
$this->upload_dir = $upload_dir;
$this->rider_tracker = $rider_tracker;
+ $this->nm = $nm;
+ $this->router = $router;
// one device = one session, since we have control over the devices
// when a rider logs in, we just change the rider assigned to the device
@@ -208,7 +215,7 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$rider_id = $rider->getID();
// cache rider location (default to hub)
// TODO: figure out longitude / latitude default
- $this->rcache->addActiveRider($rider_id, 0, 0);
+ // $this->rcache->addActiveRider($rider_id, 0, 0);
// TODO: log rider logging in
@@ -278,7 +285,7 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$rider->setActive(false);
// remove from cache
- $this->rcache->removeActiveRider($rider->getID());
+ // $this->rcache->removeActiveRider($rider->getID());
// remove rider from session
$this->session->setRider(null);
@@ -311,6 +318,11 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$this->em->flush();
+ // cache rider location (default to hub)
+ // TODO: figure out longitude / latitude default
+ $rider_id = $rider->getID();
+ $this->rcache->addActiveRider($rider_id, 0, 0);
+
// send mqtt event to put rider on map
// get rider coordinates from redis
$coord = $this->rider_tracker->getRiderLocation($rider->getID());
@@ -360,6 +372,9 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$this->em->flush();
+ // remove from cache
+ $this->rcache->removeActiveRider($rider->getID());
+
// send mqtt event to remove rider from map
$channel = 'rider/' . $rider->getID() . '/availability';
$payload = [
@@ -1069,6 +1084,10 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$this->em->flush();
+ // notification
+ $notif_url = $this->router->generate('jo_onestep_edit_form', ['id' => $jo->getID()]);
+ $this->nm->sendNotification(0, 'Job order has been cancelled by rider.', $notif_url);
+
return $data;
}
@@ -1154,6 +1173,9 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
$this->mclient->publish($channel, $rider_status);
+ $notif_url = $this->router->generate('jo_onestep_edit_form', ['id' => $jo->getID()]);
+ $this->nm->sendNotification(0, 'Job order has been rejected by rider.', $notif_url);
+
return $data;
}
diff --git a/src/Service/RiderCache.php b/src/Service/RiderCache.php
index cdd18864..af5ca716 100644
--- a/src/Service/RiderCache.php
+++ b/src/Service/RiderCache.php
@@ -2,6 +2,8 @@
namespace App\Service;
+use Doctrine\ORM\EntityManagerInterface;
+
use App\Service\RedisClientProvider;
use App\Entity\Rider;
@@ -10,12 +12,14 @@ class RiderCache
protected $redis;
protected $loc_key;
protected $status_key;
+ protected $em;
- public function __construct(RedisClientProvider $redis_prov, $loc_key, $status_key)
+ public function __construct(EntityManagerInterface $em, RedisClientProvider $redis_prov, $loc_key, $status_key)
{
$this->redis = $redis_prov->getRedisClient();
$this->loc_key = $loc_key;
$this->status_key = $status_key;
+ $this->em = $em;
}
public function addActiveRider($id, $lat, $lng)
@@ -46,10 +50,15 @@ class RiderCache
$lng = $data[1][0];
$lat = $data[1][1];
- $locs[$id] = [
- 'longitude' => $lng,
- 'latitude' => $lat,
- ];
+ // get rider details so we can check for availability
+ $rider = $this->getRiderDetails($id);
+ if ($rider != null)
+ {
+ $locs[$id] = [
+ 'longitude' => $lng,
+ 'latitude' => $lat,
+ ];
+ }
}
// error_log(print_r($all_riders, true));
@@ -73,4 +82,17 @@ class RiderCache
{
$this->redis->hincrby($this->status_key, $id, -1);
}
+
+ protected function getRiderDetails($id)
+ {
+ $rider = $this->em->getRepository(Rider::class)->find($id);
+ if ($rider == null)
+ return null;
+
+ // return only if available
+ if ($rider->isAvailable())
+ return $rider;
+
+ return null;
+ }
}
diff --git a/templates/base.html.twig b/templates/base.html.twig
index 5e5abfc2..37fcfc4d 100644
--- a/templates/base.html.twig
+++ b/templates/base.html.twig
@@ -448,217 +448,50 @@
+
+
@@ -770,6 +605,26 @@
+
+
+
+
{% block scripts %}{% endblock %}