WIP: "CMB - notifications when JO is cancelled or rejected" #1311
10 changed files with 501 additions and 0 deletions
|
|
@ -429,3 +429,11 @@ access_keys:
|
|||
- id: analytics.forecast
|
||||
label: Forecasting
|
||||
|
||||
- id: notifications
|
||||
label: Notifications
|
||||
acls:
|
||||
- id: notification.menu
|
||||
label: Menu
|
||||
- id: notification.list
|
||||
label: List
|
||||
|
||||
|
|
|
|||
|
|
@ -177,3 +177,12 @@ main_menu:
|
|||
acl: review.list
|
||||
label: Reviews
|
||||
parent: partner
|
||||
|
||||
- id: notification
|
||||
acl: notification.menu
|
||||
label: Notifications
|
||||
icon: flaticon-bell
|
||||
- id: notification_list
|
||||
acl: notification.list
|
||||
label: Notifications
|
||||
parent: notification
|
||||
|
|
|
|||
14
config/routes/notification.yaml
Normal file
14
config/routes/notification.yaml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
notification_list:
|
||||
path: /notifications
|
||||
controller: App\Controller\NotificationController::index
|
||||
|
||||
notification_rows:
|
||||
path: /notifications/rows
|
||||
controller: App\Controller\NotificationController::rows
|
||||
methods: [POST]
|
||||
|
||||
# ajax call
|
||||
notification_ajax_read:
|
||||
path: /notifications/read
|
||||
controller: App\Controller\NotificationController::markNotificationRead
|
||||
methods: [POST]
|
||||
129
src/Controller/NotificationController.php
Normal file
129
src/Controller/NotificationController.php
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
namespace App\Controller;
|
||||
|
||||
use App\Entity\Notification;
|
||||
|
||||
use Doctrine\ORM\Query;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
|
||||
|
||||
use Catalyst\MenuBundle\Annotation\Menu;
|
||||
|
||||
class NotificationController extends Controller
|
||||
{
|
||||
/**
|
||||
* @Menu(selected="notification_list")
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$this->denyAccessUnlessGranted('notification.list', null, 'No access.');
|
||||
|
||||
return $this->render('notification/list.html.twig');
|
||||
}
|
||||
|
||||
public function rows(Request $req)
|
||||
{
|
||||
$this->denyAccessUnlessGranted('notification.list', null, 'No access.');
|
||||
|
||||
// get current user
|
||||
$user = $this->getUser();
|
||||
|
||||
// build query
|
||||
$qb = $this->getDoctrine()
|
||||
->getRepository(Notification::class)
|
||||
->createQueryBuilder('q');
|
||||
|
||||
// get datatable params
|
||||
$datatable = $req->request->get('datatable');
|
||||
|
||||
// count total records
|
||||
$tquery = $qb->select('COUNT(q)');
|
||||
|
||||
// find by user
|
||||
$tquery->andWhere('q.user = :user')
|
||||
->setParameter('user', $user);
|
||||
|
||||
$total = $tquery->getQuery()
|
||||
->getSingleScalarResult();
|
||||
|
||||
// get current page number
|
||||
$page = $datatable['pagination']['page'] ?? 1;
|
||||
|
||||
$perpage = $datatable['pagination']['perpage'];
|
||||
$offset = ($page - 1) * $perpage;
|
||||
|
||||
// add metadata
|
||||
$meta = [
|
||||
'page' => $page,
|
||||
'perpage' => $perpage,
|
||||
'pages' => ceil($total / $perpage),
|
||||
'total' => $total,
|
||||
'sort' => 'asc',
|
||||
'field' => 'id'
|
||||
];
|
||||
|
||||
// build query
|
||||
$query = $qb->select('q');
|
||||
|
||||
// find by user
|
||||
$query->andWhere('q.user = :user')
|
||||
->setParameter('user', $user);
|
||||
|
||||
// order by date_create desc
|
||||
$query->orderBy('q.date_create', 'desc');
|
||||
|
||||
// get rows for this page
|
||||
$obj_rows = $query->setFirstResult($offset)
|
||||
->setMaxResults($perpage)
|
||||
->getQuery()
|
||||
->getResult();
|
||||
|
||||
// process rows
|
||||
$rows = [];
|
||||
foreach ($obj_rows as $orow) {
|
||||
// add row data
|
||||
$row['id'] = $orow->getID();
|
||||
$row['message'] = $orow->getMessage();
|
||||
$row['notif_type'] = $orow->getNotificationTypeName();
|
||||
$row['jo_id'] = $orow->getJobOrder()->getID();
|
||||
$row['flag_read'] = $orow->isNotificationRead();
|
||||
|
||||
// add row metadata
|
||||
$row['meta']['view_jo_url'] = $this->generateUrl('jo_walkin_edit_form', ['id' => $row['jo_id']]);
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
// response
|
||||
return $this->json([
|
||||
'meta' => $meta,
|
||||
'data' => $rows
|
||||
]);
|
||||
}
|
||||
|
||||
public function markNotificationRead(EntityManagerInterface $em, Request $req)
|
||||
{
|
||||
$id = $req->request->get('id');
|
||||
|
||||
$notif = $em->getRepository(Notification::class)->find($id);
|
||||
// make sure this notification exists
|
||||
if (empty($notif)) {
|
||||
return $this->json([
|
||||
'success' => false,
|
||||
'errors' => 'No notification found.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$notif->setReadNotification();
|
||||
|
||||
$em->flush();
|
||||
|
||||
return $this->json([
|
||||
'success' => 'Changes have been saved!'
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -346,6 +346,12 @@ class JobOrder
|
|||
*/
|
||||
protected $date_status_change;
|
||||
|
||||
// notifications for this job order
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="Notification", mappedBy="job_order")
|
||||
*/
|
||||
protected $jo_notifications;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->date_create = new DateTime();
|
||||
|
|
@ -368,6 +374,8 @@ class JobOrder
|
|||
$this->meta = [];
|
||||
|
||||
$this->phone_mobile = '';
|
||||
|
||||
$this->jo_notifications = new ArrayCollection();
|
||||
}
|
||||
|
||||
public function getID()
|
||||
|
|
@ -996,4 +1004,9 @@ class JobOrder
|
|||
return $this->jo_extra;
|
||||
}
|
||||
|
||||
public function getNotifications()
|
||||
{
|
||||
return $this->jo_notifications;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
145
src/Entity/Notification.php
Normal file
145
src/Entity/Notification.php
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
<?php
|
||||
|
||||
namespace App\Entity;
|
||||
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
|
||||
use App\Ramcar\NotificationType;
|
||||
|
||||
use DateTime;
|
||||
|
||||
/**
|
||||
* @ORM\Entity
|
||||
* @ORM\Table(name="notification")
|
||||
*/
|
||||
class Notification
|
||||
{
|
||||
// unique id
|
||||
/**
|
||||
* @ORM\Id
|
||||
* @ORM\Column(type="integer")
|
||||
* @ORM\GeneratedValue(strategy="AUTO")
|
||||
*/
|
||||
protected $id;
|
||||
|
||||
// message
|
||||
/**
|
||||
* @ORM\Column(type="string", length=80, nullable=true)
|
||||
*/
|
||||
protected $message;
|
||||
|
||||
// date notification was created
|
||||
/**
|
||||
* @ORM\Column(type="datetime")
|
||||
*/
|
||||
protected $date_create;
|
||||
|
||||
// has it been read, if yes, true.
|
||||
/**
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
protected $flag_read;
|
||||
|
||||
// user that created the job order
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="User", inversedBy="user_notifications")
|
||||
* @ORM\JoinColumn(name="user_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $user;
|
||||
|
||||
// job order in notification
|
||||
/**
|
||||
* @ORM\ManyToOne(targetEntity="JobOrder", inversedBy="jo_notifications")
|
||||
* @ORM\JoinColumn(name="jo_id", referencedColumnName="id")
|
||||
*/
|
||||
protected $job_order;
|
||||
|
||||
// notification_type
|
||||
/**
|
||||
* @ORM\Column(type="string", length=25, nullable=true)
|
||||
*/
|
||||
protected $notif_type;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->flag_read = false;
|
||||
$this->date_create = new DateTime();
|
||||
}
|
||||
|
||||
public function getID()
|
||||
{
|
||||
return $this->id;
|
||||
}
|
||||
|
||||
public function setDateCreate(DateTime $date_create)
|
||||
{
|
||||
$this->date_create = $date_create;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDateCreate()
|
||||
{
|
||||
return $this->date_create;
|
||||
}
|
||||
|
||||
public function setMessage($message)
|
||||
{
|
||||
$this->message = $message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMessage()
|
||||
{
|
||||
return $this->message;
|
||||
}
|
||||
|
||||
public function isNotificationRead()
|
||||
{
|
||||
return $this->flag_read;
|
||||
}
|
||||
|
||||
public function setReadNotification($bool = true)
|
||||
{
|
||||
$this->flag_read = $bool;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setUser(User $user)
|
||||
{
|
||||
$this->user = $user;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getUser()
|
||||
{
|
||||
return $this->user;
|
||||
}
|
||||
|
||||
public function setJobOrder(JobOrder $jo)
|
||||
{
|
||||
$this->job_order = $jo;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getJobOrder()
|
||||
{
|
||||
return $this->job_order;
|
||||
}
|
||||
|
||||
public function setNotificationType($notif_type)
|
||||
{
|
||||
$this->notif_type = $notif_type;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNotificationType()
|
||||
{
|
||||
return $this->notif_type;
|
||||
}
|
||||
|
||||
public function getNotificationTypeName()
|
||||
{
|
||||
return NotificationType::getName($this->notif_type);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -110,6 +110,18 @@ class User extends BaseUser implements Serializable
|
|||
*/
|
||||
protected $partners_created;
|
||||
|
||||
// notifications for this user
|
||||
/**
|
||||
* @ORM\OneToMany(targetEntity="Notification", mappedBy="user", indexBy="id")
|
||||
*/
|
||||
protected $user_notifications;
|
||||
|
||||
// flag to check if user has read notifications
|
||||
/**
|
||||
* @ORM\Column(type="boolean")
|
||||
*/
|
||||
protected $flag_read_notifications;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
|
|
@ -119,6 +131,9 @@ class User extends BaseUser implements Serializable
|
|||
$this->job_orders_assigned = new ArrayCollection();
|
||||
$this->tickets = new ArrayCollection();
|
||||
$this->partners_created = new ArrayCollection();
|
||||
$this->user_notifications = new ArrayCollection();
|
||||
|
||||
$this->flag_read_notifications = false;
|
||||
}
|
||||
|
||||
public function getID()
|
||||
|
|
@ -309,4 +324,21 @@ class User extends BaseUser implements Serializable
|
|||
{
|
||||
return $this->partners_created;
|
||||
}
|
||||
|
||||
public function getNotifications()
|
||||
{
|
||||
return $this->user_notifications;
|
||||
}
|
||||
|
||||
public function isNotificationsRead()
|
||||
{
|
||||
return $this->flag_read_notifications;
|
||||
}
|
||||
|
||||
public function setReadNotifications($bool = true)
|
||||
{
|
||||
$this->flag_read_notifications = $bool;
|
||||
return $this;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
14
src/Ramcar/NotificationType.php
Normal file
14
src/Ramcar/NotificationType.php
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
namespace App\Ramcar;
|
||||
|
||||
class NotificationType extends NameValue
|
||||
{
|
||||
const CANCELLATION = 'cancellation';
|
||||
const REJECTION = 'rejection';
|
||||
|
||||
const COLLECTION = [
|
||||
'cancellation' => 'Cancellation',
|
||||
'rejection' => 'Rejection',
|
||||
];
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Service\RiderAPIHandler;
|
||||
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
|
||||
|
||||
|
|
@ -14,6 +15,7 @@ use App\Ramcar\InvoiceStatus;
|
|||
use App\Ramcar\CMBModeOfPayment;
|
||||
use App\Ramcar\InvoiceCriteria;
|
||||
use App\Ramcar\CMBCancelReason;
|
||||
use App\Ramcar\NotificationType;
|
||||
|
||||
use App\Service\RiderAPIHandlerInterface;
|
||||
use App\Service\RedisClientProvider;
|
||||
|
|
@ -33,6 +35,7 @@ use App\Entity\BatteryModel;
|
|||
use App\Entity\BatterySize;
|
||||
use App\Entity\JobOrder;
|
||||
use App\Entity\JOExtra;
|
||||
use App\Entity\Notification;
|
||||
|
||||
use DateTime;
|
||||
use DateInterval;
|
||||
|
|
@ -865,6 +868,14 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
->setRider($rider);
|
||||
$this->em->persist($event);
|
||||
|
||||
// add to notifications
|
||||
$notif = new Notification();
|
||||
$notif->setJobOrder($jo)
|
||||
->setMessage($rider->getFullName() . ' cancelled Job Order# ' . $jo->getID())
|
||||
->setNotificationType(NotificationType::CANCELLATION)
|
||||
->setUser($jo->getCreatedBy());
|
||||
$this->em->persist($notif);
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
return $data;
|
||||
|
|
@ -924,6 +935,14 @@ class CMBRiderAPIHandler implements RiderAPIHandlerInterface
|
|||
->setRider($rider);
|
||||
$this->em->persist($event);
|
||||
|
||||
// add to notifications
|
||||
$notif = new Notification();
|
||||
$notif->setJobOrder($jo)
|
||||
->setMessage($rider->getFullName() . ' rejected Job Order# ' . $jo->getID())
|
||||
->setNotificationType(NotificationType::REJECTION)
|
||||
->setUser($jo->getCreatedBy());
|
||||
$this->em->persist($notif);
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
// send mqtt event
|
||||
|
|
|
|||
118
templates/notification/list.html.twig
Normal file
118
templates/notification/list.html.twig
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block body %}
|
||||
<!-- BEGIN: Subheader -->
|
||||
<div class="m-subheader">
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="mr-auto">
|
||||
<h3 class="m-subheader__title">
|
||||
Notifications
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- END: Subheader -->
|
||||
<div class="m-content">
|
||||
<!--Begin::Section-->
|
||||
<div class="row">
|
||||
<div class="col-xl-12">
|
||||
<div class="m-portlet m-portlet--mobile">
|
||||
<div class="m-portlet__body">
|
||||
<div class="m-form m-form--label-align-right m--margin-top-20 m--margin-bottom-30">
|
||||
<div class="row align-items-center">
|
||||
<div class="m-separator m-separator--dashed d-xl-none"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!--begin: Datatable -->
|
||||
<div id="data-rows"></div>
|
||||
<!--end: Datatable -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
$(function() {
|
||||
var options = {
|
||||
data: {
|
||||
type: 'remote',
|
||||
source: {
|
||||
read: {
|
||||
url: '{{ url("notification_rows") }}',
|
||||
method: 'POST',
|
||||
}
|
||||
},
|
||||
saveState: {
|
||||
cookie: false,
|
||||
webstorage: false
|
||||
},
|
||||
pageSize: 10,
|
||||
serverPaging: true,
|
||||
serverFiltering: true,
|
||||
serverSorting: true
|
||||
},
|
||||
rows: {
|
||||
beforeTemplate: function(row, data, index) {
|
||||
console.log(data.flag_read);
|
||||
if (data.flag_read) {
|
||||
$(row).addClass('m-table__row--primary');
|
||||
}
|
||||
else {
|
||||
$(row).addClass('m-table__row--danger');
|
||||
}
|
||||
}
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
field: 'id',
|
||||
title: 'ID',
|
||||
width: 30
|
||||
},
|
||||
{
|
||||
field: 'message',
|
||||
title: 'Message'
|
||||
},
|
||||
{
|
||||
field: 'notif_type',
|
||||
title: 'Notification Type'
|
||||
},
|
||||
{
|
||||
field: 'Actions',
|
||||
width: 110,
|
||||
title: 'Actions',
|
||||
sortable: false,
|
||||
overflow: 'visible',
|
||||
template: function (row, index, datatable) {
|
||||
var actions = '';
|
||||
|
||||
if (row.meta.view_jo_url != '') {
|
||||
actions += '<a href="' + row.meta.view_jo_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-edit" data-id="' + row.id + '" title="View JO"><i class="la la-edit"></i></a>';
|
||||
}
|
||||
return actions;
|
||||
},
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var table = $("#data-rows").mDatatable(options);
|
||||
|
||||
$(document).on('click', '.btn-edit', function(e) {
|
||||
var url = $(this).prop('href');
|
||||
var id = $(this).data('id');
|
||||
console.log(id);
|
||||
$.ajax({
|
||||
method: 'POST',
|
||||
url: "{{ url('notification_ajax_read') }}",
|
||||
data: { id: id }
|
||||
}).done(function(response) {
|
||||
console.log(response);
|
||||
}).error(function(err) {
|
||||
console.log(err);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
Reference in a new issue