WIP: "CMB - notifications when JO is cancelled or rejected" #1311

Draft
korina.cordero wants to merge 5 commits from 433-cmb-notifications-when-jo-is-cancelled-or-rejected into 424-cmb-release
10 changed files with 501 additions and 0 deletions

View file

@ -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

View file

@ -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

View 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]

View 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!'
]);
}
}

View file

@ -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
View 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);
}
}

View file

@ -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;
}
}

View 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',
];
}

View file

@ -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

View 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 %}