Resolve "CMB - Data migration for Carfix data" #1355

Closed
korina.cordero wants to merge 57 commits from 460-cmb-data-migration-for-carfix-data into 424-cmb-release
24 changed files with 2333 additions and 270 deletions

View file

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

View file

@ -61,3 +61,9 @@ rider_ajax_avialable:
path: /riders/{id}/available
controller: App\Controller\RiderController::ajaxAvailable
methods: [GET]
rider_ajax_rider_name:
path: /riders/{id}/name
controller: App\Controller\RiderController::ajaxRiderName
methods: [GET]

View file

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

View file

@ -4,6 +4,7 @@ class DashboardMap {
this.rider_markers = rider_markers;
this.cust_markers = cust_markers;
this.rider_availability = {};
this.rider_names = {};
// layer groups
this.layer_groups = {
@ -230,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,12 +314,39 @@ class DashboardMap {
var name = '';
if (data.has_jo)
my.putRiderActiveJOMarker(id, lat, lng, name);
my.putRiderActiveJOMarker(id, lat, lng);
else
my.putRiderAvailableMarker(id, lat, lng, name);
my.putRiderAvailableMarker(id, lat, lng);
});
// console.log(rider_markers);
});
}
getRiderName(id, callback) {
var name = '';
var rider_url = this.options.rider_name_url.replace('[id]', id);
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);
});
}
}
}

View file

@ -78,8 +78,9 @@ class MapEventHandler {
}
handleRider(chan_split, payload) {
// console.log("rider message");
//console.log("rider message");
var rider_id = chan_split[1];
//console.log('url ' + this.dashmap.options.rider_availability_url);
switch (chan_split[2]) {
case "availability":
console.log("got availability for rider " + chan_split[1] + " - " + payload);
@ -100,7 +101,10 @@ class MapEventHandler {
// cheeck if rider is available / unavailable
// TODO: make url not hardcoded
var dashmap = this.dashmap;
$.get('https://cmbdev.wildcard.cc/riders/' + chan_split[1] + '/available').done(function(data) {
var url = dashmap.options.rider_availability_url;
var rider_availability_url = url.replace('[id]', chan_split[1]);
//console.log(rider_availability_url);
$.get(rider_availability_url).done(function(data) {
console.log('rider availability - ' + data);
switch (data) {
case 'available':
@ -122,7 +126,7 @@ class MapEventHandler {
// 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;
@ -138,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

View file

@ -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 = '<div class="m-list-timeline__item">';
notif_html += '<span class="m-list-timeline__badge -m-list-timeline__badge--state-success"></span>';
notif_html += '<span class="m-list-timeline__text">';
notif_html += '<a href="">' + notif.text + '</a>'
notif_html += '</span>';
notif_html += '<span class="m-list-timeline__time">';
notif_html += notif_date;
notif_html += '</span>';
notif_html += '</div>';
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'];
}
}

View file

@ -148,19 +148,19 @@ class ImportCMBBatteryDataCommand extends Command
// save battery manufacturer if not yet in system
if (!isset($this->bmanu_hash[$normalized_manu]))
{
$this->addBatteryManufacturer($battery_manufacturer);
$this->addBatteryManufacturer(strtoupper($battery_manufacturer));
}
// save battery model if not yet in system
if (!isset($this->bmodel_hash[$normalized_model]))
{
$this->addBatteryModel($battery_model);
$this->addBatteryModel(strtoupper($battery_model));
}
// save battery size if not yet in system
if (!isset($this->bsize_hash[$normalized_size]))
{
$this->addBatterySize($clean_size);
$this->addBatterySize(strtoupper($clean_size));
}
// save battery if not yet in system

View file

@ -0,0 +1,489 @@
<?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\ORM\EntityManagerInterface;
use App\Entity\JobOrder;
use App\Entity\VehicleManufacturer;
use App\Entity\Vehicle;
use App\Entity\BatteryManufacturer;
use App\Entity\BatteryModel;
use App\Entity\BatterySize;
use App\Entity\Battery;
use App\Entity\Customer;
use App\Entity\CustomerVehicle;
use App\Ramcar\CMBServiceType;
use App\Ramcar\JOStatus;
use App\Ramcar\FuelType;
use App\Ramcar\VehicleStatusCondition;
use DateTime;
class ImportCMBCarFixDataCommand extends Command
{
// field index in csv file
const F_INDEX = 0;
const F_CREATED_DATE = 1;
const F_CASE_NO = 2;
const F_INSURER = 3;
const F_VEHICLE_NO = 4;
const F_CAR_MODEL = 5;
const F_NATURE_OF_CALL = 6;
const F_SERVICE_NEEDED = 7;
const F_LOCATION = 8;
const F_STATE = 9;
const F_DRIVER = 10;
const F_TRUCK = 11;
const F_WORKSHOP_ARRIVAL_TIME = 12;
const F_STATUS = 13;
const F_CUSTOMER_NAME = 14;
const F_CUSTOMER_PHONE_NO = 15;
const F_OUR_REFERENCE = 16;
const F_ODOMETER = 17;
const F_BATT_MODEL = 18;
const F_BATT_SIZE = 19;
const F_BATT_TRADE_IN = 20;
const F_REPLACED_BY = 21;
const F_REMARK = 22;
const F_SATISFACTION = 23;
protected $em;
protected $bmanu_hash;
protected $bmodel_hash;
protected $bsize_hash;
protected $batt_hash;
protected $vmanu_hash;
protected $vmake_hash;
protected $cv_hash;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
// load existing battery data
$this->loadBatteryManufacturers();
$this->loadBatteryModels();
$this->loadBatterySizes();
$this->loadBatteries();
// load existing vehicle data
$this->loadVehicleManufacturers();
$this->loadVehicleMakes();
parent::__construct();
}
public function configure()
{
$this->setName('cmbcarfixdata:import')
->setDescription('Retrieve from a CSV file CarFix data.')
->setHelp('Creates job orders based on data from imported CSV.')
->addArgument('file', InputArgument::REQUIRED, 'Path to the CSV file.');
}
public function execute(InputInterface $input, OutputInterface $output)
{
$csv_file = $input->getArgument('file');
// attempt to open file
try
{
$fh = fopen($csv_file, "r");
}
catch (Exception $e)
{
throw new Exception('The file "' . $csv_file . '" could be read.');
}
// get entity manager
$em = $this->em;
$row_num = 0;
$invalid_entries = [];
error_log('Processing CarFix data csv file...');
while (($fields = fgetcsv($fh)) !== false)
{
if ($row_num < 1)
{
$row_num++;
continue;
}
// get the information
$entry_num = trim($fields[self::F_INDEX]);
$date_create = trim($fields[self::F_CREATED_DATE]);
$case_number = trim($fields[self::F_CASE_NO]);
$insurer = trim($fields[self::F_INSURER]);
$vehicle_number = trim($fields[self::F_VEHICLE_NO]);
$car_model = trim($fields[self::F_CAR_MODEL]);
$nature_of_call = trim($fields[self::F_NATURE_OF_CALL]);
$service_needed = trim($fields[self::F_SERVICE_NEEDED]);
$location = trim($fields[self::F_LOCATION]);
$state = trim($fields[self::F_STATE]);
$driver = trim($fields[self::F_DRIVER]);
$truck = trim($fields[self::F_TRUCK]);
$workshop_arrival_time = trim($fields[self::F_WORKSHOP_ARRIVAL_TIME]);
$status = trim($fields[self::F_STATUS]);
$customer_name = trim($fields[self::F_CUSTOMER_NAME]);
$customer_mobile = trim($fields[self::F_CUSTOMER_PHONE_NO]);
$reference = trim($fields[self::F_OUR_REFERENCE]);
$odometer = trim($fields[self::F_ODOMETER]);
$batt_model = trim(strtolower($fields[self::F_BATT_MODEL]));
$batt_size = trim(strtolower($fields[self::F_BATT_SIZE]));
$trade_in = trim($fields[self::F_BATT_TRADE_IN]);
$replaced_by = trim($fields[self::F_REPLACED_BY]);
$remark = trim($fields[self::F_REMARK]);
$satisfaction = trim($fields[self::F_SATISFACTION]);
//error_log($date_create . ' ' . $case_number . ' ' . $driver . ' ' . $customer_name . ' ' . $remark . ' ' . $satisfaction);
// get customer vehicle
$v_status = $this->processVehicleInfo($car_model);
if ($v_status != null)
{
error_log($v_status . ' ' . $car_model);
$invalid_entries[] = $this->addInvalidEntry($entry_num, $date_create, $case_number, $insurer, $vehicle_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$trade_in, $replaced_by, $remark, $satisfaction, $v_status);
// move to next entry
continue;
}
// check batteries
$batt_status = $this->processBatteryInfo($batt_model, $batt_size);
if ($batt_status != null)
{
error_log($batt_status);
$invalid_entries[] = $this->addInvalidEntry($entry_num, $date_create, $case_number, $insurer, $vehicle_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$trade_in, $replaced_by, $remark, $satisfaction, $batt_status);
// move to next entry
continue;
}
$new_jo = new JobOrder();
// add to metadata
// case number, nature of call, workshop arrival time, reference, odometer, replaced by, satisfaction
$new_jo->addMeta('case_number', $case_number);
$new_jo->addMeta('nature_of_call', $nature_of_call);
$new_jo->addMeta('workshop_arrival_time', $workshop_arrival_time);
$new_jo->addMeta('reference', $reference);
$new_jo->addMeta('odometer', $odometer);
$new_jo->addMeta('replaced_by', $replaced_by);
$new_jo->addMeta('satisfaction', $satisfaction);
// date_create
$created_date = DateTime::createFromFormat('d-m-Y H:i', $date_create);
$new_jo->setDateCreate($created_date);
// insurer == responsible_party
$new_jo->setResponsibleParty($insurer);
// delivery address = location + state
$delivery_address = $location . ', ' . $state;
$new_jo->setDeliveryAddress($delivery_address);
// remarks == tier 1 notes
$new_jo->setTier1Notes($remark);
// service_needed = service type
// check service needed:
// Battery == Battery Sales
// Battery Warranty Claim == Warranty Claim
// Battery Warranty Replacement == Warranty Replacement
$service = $this->normalizeName($service_needed);
switch ($service)
{
case 'battery':
$new_jo->setServiceType(CMBServiceType::BATTERY_REPLACEMENT_NEW);
break;
case 'battery warranty claim':
$new_jo->setServiceType(CMBServiceType::WARRANTY_CLAIM);
break;
case 'battery warranty replacement':
$new_jo->setServiceType(CMBServiceType::BATTERY_REPLACEMENT_WARRANTY);
break;
}
// status set everything to fulfilled
// store old status to metadata
$new_jo->setStatus(JOStatus::FULFILLED);
$new_jo->addMeta('status', $status);
// plate number == vehicle_number. Use as key to cv_hash
$clean_plate = $this->cleanPlateNumber($vehicle_number);
//error_log($clean_plate . ' ' . $new_jo->getServiceType());
// check if plate number has been added
if (!isset($this->cv_hash[$clean_plate]))
{
$cust = $this->addCustomer($customer_name, $customer_mobile);
$cv = $this->addCustomerVehicle($clean_plate, $car_model, $cust);
}
else
{
// get customer vehicle from hash
$cv = $this->cv_hash[$clean_plate];
$cust = $cv->getCustomer();
}
$new_jo->setCustomer($cust)
->setCustomerVehicle($cv);
$row_num++;
}
$this->em->flush();
$this->em->clear();
// check for invalid entries. if any, write to csv
return 0;
}
protected function processVehicleInfo($car_model)
{
// vehicle manufacturer is the first entry
// vehicle model is the 2nd entry + whatever follows
$v_array = explode(' ', $car_model);
// manufacturer
$v_manufacturer = trim($v_array[0]);
// get model
$model_info = '';
$v_model = '';
for ($i = 1; $i < count($v_array); $i++)
{
$model_info = $model_info . ' ' . trim($v_array[$i]);
}
$v_model = trim($model_info);
//error_log($v_manufacturer . ' ' . $v_model);
// check if manufacturer is in hash
if (!isset($this->vmanu_hash[$v_manufacturer]))
{
//error_log($v_manufacturer . ' invalid.');
return 'Vehicle manufacturer not in system.';
}
// check if manufacturer and make is in hash
if (!isset($this->vmake_hash[$v_manufacturer][$v_model]))
{
//error_log($v_model . ' invalid.');
return 'Vehicle model not in system.';
}
// car model info valid
return null;
}
protected function processBatteryInfo($batt_model, $batt_size)
{
// check if battery model is in hash
if (!isset($this->bmodel_hash[$batt_model]))
return 'Battery model not in system.';
// check if battery size is in hash
if (!isset($this->bsize_hash[$batt_size]))
return 'Battery size not in system.';
// battery info valid
return null;
}
protected function addCustomer($name, $mobile)
{
$new_cust = new Customer();
$new_cust->setFirstName($name)
->setLastName('')
->setPhoneMobile($mobile);
$this->em->persist($new_cust);
return $new_cust;
}
protected function addCustomerVehicle($plate_num, $car_model, $customer)
{
$new_cv = new CustomerVehicle();
// get vehicle from hash
$v_array = explode(' ', $car_model);
// manufacturer
$v_manufacturer = trim($v_array[0]);
// get model
$model_info = '';
$v_model = '';
for ($i = 1; $i < count($v_array); $i++)
{
$model_info = $model_info . ' ' . trim($v_array[$i]);
}
$v_model = trim($model_info);
$vehicle = $this->vmake_hash[$v_manufacturer][$v_model];
$new_cv->setCustomer($customer)
->setPlateNumber($plate_num)
->setStatusCondition(VehicleStatusCondition::BRAND_NEW)
->setModelYear('')
->setColor('')
->setFuelType(FuelType::GAS)
->setHasMotoliteBattery(true)
->setVehicle($vehicle);
$this->em->persist($new_cv);
// add customer vehicle to cv_hash
$this->cv_hash[$plate_num] = $new_cv;
return $new_cv;
}
protected function addInvalidEntry($entry_num, $date_create, $case_number, $insurer, $vehicle_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$trade_in, $replaced_by, $remark, $satisfaction, $v_status)
{
$inv_entry = [
'number' => $entry_num,
'created_date' => $date_create,
'case_number' => $case_number,
'insurer' => $insurer,
'vehicle_number' => $vehicle_number,
'car_model' => $car_model,
'nature_of_call' => $nature_of_call,
'service_needed' => $service_needed,
'location' => $location,
'state' => $state,
'driver' => $driver,
'truck' => $truck,
'workshop_arrival_time' => $workshop_arrival_time,
'status' => $status,
'customer_name' => $customer_name,
'customer_phone_number' => $customer_mobile,
'reference' => $reference,
'odometer' => $odometer,
'batt_model' => $batt_model,
'batt_size' => $batt_size,
'batt_trade_in' => $trade_in,
'replaced_by' => $replaced_by,
'remark' => $remark,
'satisfaction' => $satisfaction,
'reason' => $v_status,
];
return $inv_entry;
}
protected function loadBatteryManufacturers()
{
$this->bmanu_hash = [];
$batt_manufacturers = $this->em->getRepository(BatteryManufacturer::class)->findAll();
foreach ($batt_manufacturers as $batt_manu)
{
$name = $this->normalizeName($batt_manu->getName());
$this->bmanu_hash[$name] = $batt_manu;
}
}
protected function loadBatteryModels()
{
$this->bmodel_hash = [];
$batt_models = $this->em->getRepository(BatteryModel::class)->findAll();
foreach ($batt_models as $batt_model)
{
$name = $this->normalizeName($batt_model->getName());
$this->bmodel_hash[$name] = $batt_model;
}
}
protected function loadBatterySizes()
{
$this->bsize_hash = [];
$batt_sizes = $this->em->getRepository(BatterySize::class)->findAll();
foreach ($batt_sizes as $batt_size)
{
$name = $this->normalizeName($batt_size->getName());
$this->bsize_hash[$name] = $batt_size;
}
}
protected function loadBatteries()
{
$this->batt_hash = [];
$batts = $this->em->getRepository(Battery::class)->findAll();
foreach ($batts as $batt)
{
$brand = $this->normalizeName($batt->getManufacturer()->getName());
$model = $this->normalizeName($batt->getModel()->getName());
$size = $this->normalizeName($batt->getSize()->getName());
$this->batt_hash[$brand][$model][$size] = $batt;
}
}
protected function loadVehicleManufacturers()
{
$this->vmanu_hash = [];
$vmanus = $this->em->getRepository(VehicleManufacturer::class)->findAll();
foreach ($vmanus as $vmanu)
{
$name = $vmanu->getName();
$this->vmanu_hash[$name] = $vmanu;
}
}
protected function loadVehicleMakes()
{
$this->vmake_hash = [];
$vmakes = $this->em->getRepository(Vehicle::class)->findAll();
foreach ($vmakes as $vmake)
{
$manufacturer = $vmake->getManufacturer()->getName();
$make = $vmake->getMake();
$this->vmake_hash[$manufacturer][$make] = $vmake;
}
}
protected function normalizeName($name)
{
$normalized_key = trim(strtolower($name));
return $normalized_key;
}
protected function cleanPlateNumber($plate_number)
{
// remove spaces and make upper case
$clean_plate_number = strtoupper(str_replace(' ' , '', $plate_number));
return $clean_plate_number;
}
}

View file

@ -0,0 +1,164 @@
<?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\ORM\EntityManagerInterface;
use App\Entity\CMBLegacyJobOrderRow;
class ImportCMBLegacyJobOrderCommand extends Command
{
// field index in csv file
const F_INDEX = 0;
const F_CREATED_DATE = 1;
const F_CASE_NO = 2;
const F_INSURER = 3;
const F_VEHICLE_NO = 4;
const F_CAR_MODEL = 5;
const F_NATURE_OF_CALL = 6;
const F_SERVICE_NEEDED = 7;
const F_LOCATION = 8;
const F_STATE = 9;
const F_DRIVER = 10;
const F_TRUCK = 11;
const F_WORKSHOP_ARRIVAL_TIME = 12;
const F_STATUS = 13;
const F_CUSTOMER_NAME = 14;
const F_CUSTOMER_PHONE_NO = 15;
const F_OUR_REFERENCE = 16;
const F_ODOMETER = 17;
const F_BATT_MODEL = 18;
const F_BATT_SIZE = 19;
const F_BATT_TRADE_IN = 20;
const F_REPLACED_BY = 21;
const F_REMARK = 22;
const F_SATISFACTION = 23;
protected $em;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
parent::__construct();
}
public function configure()
{
$this->setName('cmblegacyjoborderdata:import')
->setDescription('Retrieve from a CSV file CarFix data.')
->setHelp('Creates legacy job order entries based on data from imported CSV.')
->addArgument('file', InputArgument::REQUIRED, 'Path to the CSV file.');
}
public function execute(InputInterface $input, OutputInterface $output)
{
$csv_file = $input->getArgument('file');
// attempt to open file
try
{
$fh = fopen($csv_file, "r");
}
catch (Exception $e)
{
throw new Exception('The file "' . $csv_file . '" could be read.');
}
// get entity manager
$em = $this->em;
$row_num = 0;
error_log('Processing CarFix data csv file...');
while (($fields = fgetcsv($fh)) !== false)
{
if ($row_num < 1)
{
$row_num++;
continue;
}
// get the information
$entry_num = trim($fields[self::F_INDEX]);
$date_create = trim($fields[self::F_CREATED_DATE]);
$case_number = trim($fields[self::F_CASE_NO]);
$insurer = trim($fields[self::F_INSURER]);
$vehicle_number = trim($fields[self::F_VEHICLE_NO]);
$car_model = trim($fields[self::F_CAR_MODEL]);
$nature_of_call = trim($fields[self::F_NATURE_OF_CALL]);
$service_needed = trim($fields[self::F_SERVICE_NEEDED]);
$location = trim($fields[self::F_LOCATION]);
$state = trim($fields[self::F_STATE]);
$driver = trim($fields[self::F_DRIVER]);
$truck = trim($fields[self::F_TRUCK]);
$workshop_arrival_time = trim($fields[self::F_WORKSHOP_ARRIVAL_TIME]);
$status = trim($fields[self::F_STATUS]);
$customer_name = trim($fields[self::F_CUSTOMER_NAME]);
$customer_mobile = trim($fields[self::F_CUSTOMER_PHONE_NO]);
$reference = trim($fields[self::F_OUR_REFERENCE]);
$odometer = trim($fields[self::F_ODOMETER]);
$batt_model = trim($fields[self::F_BATT_MODEL]);
$batt_size = trim($fields[self::F_BATT_SIZE]);
$trade_in = trim($fields[self::F_BATT_TRADE_IN]);
$replaced_by = trim($fields[self::F_REPLACED_BY]);
$remark = trim($fields[self::F_REMARK]);
$satisfaction = trim($fields[self::F_SATISFACTION]);
$data_entry = $this->processEntry($entry_num, $date_create, $case_number, $insurer, $vehicle_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$trade_in, $replaced_by, $remark, $satisfaction);
$legacy_data = new CMBLegacyJobOrderRow();
$legacy_data->setData($data_entry);
$this->em->persist($legacy_data);
}
$this->em->flush();
$this->em->clear();
return 0;
}
protected function processEntry($entry_num, $date_create, $case_number, $insurer, $vehicle_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$trade_in, $replaced_by, $remark, $satisfaction)
{
$data_entry = [
'entry_num' => $entry_num,
'created_date' => $date_create,
'case_number' => $case_number,
'insurer' => $insurer,
'vehicle_number' => $vehicle_number,
'car_model' => $car_model,
'nature_of_call' => $nature_of_call,
'service_needed' => $service_needed,
'location' => $location,
'state' => $state,
'driver' => $driver,
'truck' => $truck,
'workshop_arrival_time' => $workshop_arrival_time,
'status' => $status,
'customer_name' => $customer_name,
'customer_phone_number' => $customer_mobile,
'reference' => $reference,
'odometer' => $odometer,
'batt_model' => $batt_model,
'batt_size' => $batt_size,
'batt_trade_in' => $trade_in,
'replaced_by' => $replaced_by,
'remark' => $remark,
'satisfaction' => $satisfaction,
];
return $data_entry;
}
}

View file

@ -222,8 +222,8 @@ class ImportCMBVehicleCompatibilityCommand extends Command
//}
// vehicle info
$manufacturer = trim($fields[self::F_VEHICLE_MANUFACTURER]);
$make = trim($fields[self::F_VEHICLE_MAKE]);
$manufacturer = trim(strtolower($fields[self::F_VEHICLE_MANUFACTURER]));
$make = trim(strtolower($fields[self::F_VEHICLE_MAKE]));
$year = trim($fields[self::F_VEHICLE_YEAR]);
// vehicle data
@ -298,7 +298,7 @@ class ImportCMBVehicleCompatibilityCommand extends Command
// save to db
$vehicle_manufacturer = new VehicleManufacturer();
$vehicle_manufacturer->setName($name);
$vehicle_manufacturer->setName(strtoupper($name));
$this->em->persist($vehicle_manufacturer);
$this->em->flush();
@ -332,7 +332,7 @@ class ImportCMBVehicleCompatibilityCommand extends Command
}
$vehicle->setManufacturer($vmanu)
->setMake($make)
->setMake(strtoupper($make))
->setModelYearFrom($year_from)
->setModelYearTo($year_to);
@ -412,7 +412,7 @@ class ImportCMBVehicleCompatibilityCommand extends Command
$vmanus = $this->em->getRepository(VehicleManufacturer::class)->findAll();
foreach ($vmanus as $vmanu)
{
$name = $vmanu->getName();
$name = $this->normalizeName($vmanu->getName());
$this->vmanu_hash[$name] = $vmanu;
}
}
@ -425,7 +425,7 @@ class ImportCMBVehicleCompatibilityCommand extends Command
foreach ($vmakes as $vmake)
{
$manufacturer = $vmake->getManufacturer()->getName();
$make = $vmake->getMake();
$make = $this->normalizeName($vmake->getMake());
$this->vmake_hash[$manufacturer][$make] = $vmake;
}

View file

@ -0,0 +1,545 @@
<?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\ORM\EntityManagerInterface;
use App\Entity\CMBLegacyJobOrderRow;
use App\Entity\CMBLegacyJobOrder;
use App\Entity\VehicleManufacturer;
use App\Entity\Vehicle;
use App\Entity\BatteryManufacturer;
use App\Entity\BatteryModel;
use App\Entity\BatterySize;
use App\Entity\Battery;
use App\Entity\Customer;
use App\Entity\CustomerVehicle;
use App\Ramcar\CMBServiceType;
use App\Ramcar\JOStatus;
use App\Ramcar\FuelType;
use App\Ramcar\VehicleStatusCondition;
use DateTime;
class MigrateCMBLegacyJobOrderCommand extends Command
{
/*
id = 'entry_num'
trans_date = 'created_date'
case_number = 'case_number'
insurer = 'insurer'
plate_number = 'vehicle_number'
car_brand and car_make = split the 'car_model'
nature_of_call = nature_of_call
trans_type = 'service_needed'
address = 'location'
address = 'state'
driver = 'driver'
truck = 'truck'
workshop_arrival_time => 'workshop_arrival_time'
status = 'status'
cust_name = 'customer_name'
cust_mobile = 'customer_phone_number'
reference = 'reference'
odometer = 'odometer'
batt_model = 'batt_model'
batt_size = 'batt_size'
is_trade_in = 'batt_trade_in'
replaced_by = 'replaced_by'
remarks = 'remark'
satisfaction => 'satisfaction'
*/
protected $em;
protected $bmanu_hash;
protected $bmodel_hash;
protected $bsize_hash;
protected $batt_hash;
protected $vmanu_hash;
protected $vmake_hash;
protected $cv_hash;
public function __construct(EntityManagerInterface $em)
{
$this->em = $em;
// load existing battery data
$this->loadBatteryManufacturers();
$this->loadBatteryModels();
$this->loadBatterySizes();
$this->loadBatteries();
// load existing vehicle data
$this->loadVehicleManufacturers();
$this->loadVehicleMakes();
// load existing customer vehicle data
$this->loadCustomerVehicles();
parent::__construct();
}
public function configure()
{
$this->setName('cmblegacyjoborderdata:migrate')
->setDescription('Retrieve database cmb legacy job orders and insert into job order.')
->setHelp('Creates job orders based on data from imported CSV.')
->addArgument('output_file', InputArgument::REQUIRED, 'Path to the output CSV file of the entries not added.');
}
public function execute(InputInterface $input, OutputInterface $output)
{
$csv_file = $input->getArgument('output_file');
// attempt to open file
try
{
$fh = fopen($csv_file, "w");
}
catch (Exception $e)
{
throw new Exception('The file "' . $csv_file . '" could be opened.');
}
// get all legacy job orders
$query = $this->em->createQuery('SELECT legacy_jo_row FROM App\Entity\CMBLegacyJobOrderRow legacy_jo_row');
$result = $query->iterate();
$invalid_entries = [];
$added_entries = 0;
$total_entries = 0;
$total_inv_entries = 0;
foreach ($result as $row)
{
$entry = $row[0];
$jo_entry = $entry->getData();
$total_entries++;
// get the entry information
$entry_num = $jo_entry['entry_num'];
$date_create = $jo_entry['created_date'];
$case_number = $jo_entry['case_number'];
$insurer = $jo_entry['insurer'];
$plate_number = $this->cleanPlateNumber($jo_entry['vehicle_number']);
$car_model = $this->normalizeName($jo_entry['car_model']);
$nature_of_call = $jo_entry['nature_of_call'];
$service_needed = $jo_entry['service_needed'];
$location = $jo_entry['location'];
$state = $jo_entry['state'];
$driver = $jo_entry['driver'];
$truck = $jo_entry['truck'];
$workshop_arrival_time = $jo_entry['workshop_arrival_time'];
$status = $jo_entry['status'];
$customer_name = $jo_entry['customer_name'];
$customer_mobile = $jo_entry['customer_phone_number'];
$reference = $jo_entry['reference'];
$odometer = $jo_entry['odometer'];
$batt_model = $this->normalizeName($jo_entry['batt_model']);
$batt_size = $this->normalizeName($jo_entry['batt_size']);
$batt_trade_in = $jo_entry['batt_trade_in'];
$replaced_by = $jo_entry['replaced_by'];
$remark = $jo_entry['remark'];
$satisfaction = $jo_entry['satisfaction'];
// check vehicle info if valid
$v_status = $this->processVehicleInfo($car_model);
if ($v_status != null)
{
//error_log($v_status . ' ' . $car_model);
$invalid_entries[] = $this->addInvalidEntry($entry_num, $date_create, $case_number, $insurer, $plate_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$batt_trade_in, $replaced_by, $remark, $satisfaction, $v_status);
$total_inv_entries++;
// move to next entry
continue;
}
// check battery info if valid
$batt_status = $this->processBatteryInfo($batt_model, $batt_size);
if ($batt_status != null)
{
//error_log($batt_status . ' ' . $batt_model . ' ' . $batt_size);
$invalid_entries[] = $this->addInvalidEntry($entry_num, $date_create, $case_number, $insurer, $plate_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$batt_trade_in, $replaced_by, $remark, $satisfaction, $batt_status);
$total_inv_entries++;
// move to next entry
continue;
}
// create job order
$cmb_legacy_jo = new CMBLegacyJobOrder();
// date_create
$created_date = DateTime::createFromFormat("d-m-Y H:i", $date_create);
$cmb_legacy_jo->setTransDate($created_date);
// parse vehicle info
// get vehicle from hash
$v_array = explode(' ', $car_model);
// manufacturer
$v_manufacturer = trim($v_array[0]);
// get model
$model_info = '';
$v_model = '';
for ($i = 1; $i < count($v_array); $i++)
{
$model_info = $model_info . ' ' . trim($v_array[$i]);
}
$v_model = trim($model_info);
// delivery address = location + state
$delivery_address = $location . ', ' . $state;
// check if trade in
$trade_in = false;
if (strtolower($batt_trade_in) == 'yes')
$trade_in = true;
$cmb_legacy_jo->setCaseNumber($case_number)
->setInsurer($insurer)
->setNatureOfCall($nature_of_call)
->setTransType($service_needed)
->setCarBrand(strtoupper($v_manufacturer))
->setCarMake(strtoupper($v_model))
->setCustName($customer_name)
->setCustMobile($customer_mobile)
->setAddress($delivery_address)
->setDriver($driver)
->setTruck($truck)
->setWorkshopArrivalTime($workshop_arrival_time)
->setPlateNumber($plate_number)
->setStatus($status)
->setReference($reference)
->setOdometer($odometer)
->setBatteryModel(strtoupper($batt_model))
->setBatterySize(strtoupper($batt_size))
->setIsTradeIn($trade_in)
->setReplacedBy($replaced_by)
->setSatisfaction($satisfaction);
// plate number == vehicle_number. Use as key to cv_hash
// check if plate number has been added
if (!isset($this->cv_hash[$plate_number]))
{
$cust = $this->addCustomer($customer_name, $customer_mobile);
$cv = $this->addCustomerVehicle($plate_number, $car_model, $cust);
}
$this->em->persist($cmb_legacy_jo);
$this->em->detach($row[0]);
$added_entries++;
}
$this->em->flush();
$this->em->clear();
// output the entries that were not added
if (count($invalid_entries) > 0)
{
fputcsv($fh, [
'Entry Num',
'Created Date',
'Case Number',
'Insurer',
'Vehicle Number',
'Car Model',
'Nature of Call',
'Service Needed',
'Location',
'State',
'Driver',
'Truck',
'Workshop Arrival Time',
'Status',
'Customer Name',
'Customer Phone Number',
'Reference',
'Odometer',
'Batt Model',
'Batt Size',
'Batt Trade In',
'Replaced By',
'Remark',
'Satisfaction',
'Reason',
]);
foreach($invalid_entries as $row)
{
fputcsv($fh, $row);
}
}
fclose($fh);
error_log('Processed ' . $total_entries . ' entries.');
error_log('Added ' . $added_entries . ' entries.');
error_log('Did not add ' . $total_inv_entries . ' entries.');
return 0;
}
protected function processVehicleInfo($car_model)
{
// vehicle manufacturer is the first entry
// vehicle model is the 2nd entry + whatever follows
$v_array = explode(' ', $car_model);
// manufacturer
$v_manufacturer = trim($v_array[0]);
// get model
$model_info = '';
$v_model = '';
for ($i = 1; $i < count($v_array); $i++)
{
$model_info = $model_info . ' ' . trim($v_array[$i]);
}
$v_model = trim($model_info);
//error_log($v_manufacturer . ' ' . $v_model);
// check if manufacturer is in hash
if (!isset($this->vmanu_hash[$v_manufacturer]))
{
//error_log($v_manufacturer . ' invalid.');
return 'Vehicle manufacturer not in system.';
}
// check if manufacturer and make is in hash
if (!isset($this->vmake_hash[$v_manufacturer][$v_model]))
{
//error_log($v_model . ' invalid.');
return 'Vehicle model not in system.';
}
// car model info valid
return null;
}
protected function processBatteryInfo($batt_model, $batt_size)
{
// check if battery model is in hash
if (!isset($this->bmodel_hash[$batt_model]))
return 'Battery model not in system.';
// check if battery size is in hash
if (!isset($this->bsize_hash[$batt_size]))
return 'Battery size not in system.';
// battery info valid
return null;
}
protected function addInvalidEntry($entry_num, $date_create, $case_number, $insurer, $plate_number, $car_model,
$nature_of_call, $service_needed, $location, $state, $driver, $truck, $workshop_arrival_time,
$status, $customer_name, $customer_mobile, $reference, $odometer, $batt_model, $batt_size,
$batt_trade_in, $replaced_by, $remark, $satisfaction, $v_status)
{
$inv_entry = [
'number' => $entry_num,
'created_date' => $date_create,
'case_number' => $case_number,
'insurer' => $insurer,
'vehicle_number' => $plate_number,
'car_model' => $car_model,
'nature_of_call' => $nature_of_call,
'service_needed' => $service_needed,
'location' => $location,
'state' => $state,
'driver' => $driver,
'truck' => $truck,
'workshop_arrival_time' => $workshop_arrival_time,
'status' => $status,
'customer_name' => $customer_name,
'customer_phone_number' => $customer_mobile,
'reference' => $reference,
'odometer' => $odometer,
'batt_model' => $batt_model,
'batt_size' => $batt_size,
'batt_trade_in' => $batt_trade_in,
'replaced_by' => $replaced_by,
'remark' => $remark,
'satisfaction' => $satisfaction,
'reason' => $v_status,
];
return $inv_entry;
}
protected function addCustomer($name, $mobile)
{
$new_cust = new Customer();
$new_cust->setFirstName($name)
->setLastName('')
->setPhoneMobile($mobile);
$this->em->persist($new_cust);
return $new_cust;
}
protected function addCustomerVehicle($plate_num, $car_model, $customer)
{
$new_cv = new CustomerVehicle();
// get vehicle from hash
$v_array = explode(' ', $car_model);
// manufacturer
$v_manufacturer = trim($v_array[0]);
// get model
$model_info = '';
$v_model = '';
for ($i = 1; $i < count($v_array); $i++)
{
$model_info = $model_info . ' ' . trim($v_array[$i]);
}
$v_model = trim($model_info);
$vehicle = $this->vmake_hash[$v_manufacturer][$v_model];
$new_cv->setCustomer($customer)
->setPlateNumber($plate_num)
->setStatusCondition(VehicleStatusCondition::BRAND_NEW)
->setModelYear('')
->setColor('')
->setFuelType(FuelType::GAS)
->setHasMotoliteBattery(true)
->setVehicle($vehicle);
$this->em->persist($new_cv);
// add customer vehicle to cv_hash
$this->cv_hash[$plate_num] = $new_cv;
return $new_cv;
}
protected function loadBatteryManufacturers()
{
$this->bmanu_hash = [];
$batt_manufacturers = $this->em->getRepository(BatteryManufacturer::class)->findAll();
foreach ($batt_manufacturers as $batt_manu)
{
$name = $this->normalizeName($batt_manu->getName());
$this->bmanu_hash[$name] = $batt_manu;
}
}
protected function loadBatteryModels()
{
$this->bmodel_hash = [];
$batt_models = $this->em->getRepository(BatteryModel::class)->findAll();
foreach ($batt_models as $batt_model)
{
$name = $this->normalizeName($batt_model->getName());
$this->bmodel_hash[$name] = $batt_model;
}
}
protected function loadBatterySizes()
{
$this->bsize_hash = [];
$batt_sizes = $this->em->getRepository(BatterySize::class)->findAll();
foreach ($batt_sizes as $batt_size)
{
$name = $this->normalizeName($batt_size->getName());
$this->bsize_hash[$name] = $batt_size;
}
}
protected function loadBatteries()
{
$this->batt_hash = [];
$batts = $this->em->getRepository(Battery::class)->findAll();
foreach ($batts as $batt)
{
$brand = $this->normalizeName($batt->getManufacturer()->getName());
$model = $this->normalizeName($batt->getModel()->getName());
$size = $this->normalizeName($batt->getSize()->getName());
$this->batt_hash[$brand][$model][$size] = $batt;
}
}
protected function loadVehicleManufacturers()
{
$this->vmanu_hash = [];
$vmanus = $this->em->getRepository(VehicleManufacturer::class)->findAll();
foreach ($vmanus as $vmanu)
{
$name = $this->normalizeName($vmanu->getName());
$this->vmanu_hash[$name] = $vmanu;
}
}
protected function loadVehicleMakes()
{
$this->vmake_hash = [];
$vmakes = $this->em->getRepository(Vehicle::class)->findAll();
foreach ($vmakes as $vmake)
{
$manufacturer = $this->normalizeName($vmake->getManufacturer()->getName());
$make = $this->normalizeName($vmake->getMake());
$this->vmake_hash[$manufacturer][$make] = $vmake;
}
}
protected function loadCustomerVehicles()
{
$this->cv_hash = [];
$cvs = $this->em->getRepository(CustomerVehicle::class)->findAll();
foreach ($cvs as $cv)
{
$plate_number = $this->cleanPlateNumber($cv->getPlateNumber());
$this->cv_hash[$plate_number] = $cv;
}
}
protected function normalizeName($name)
{
$normalized_key = trim(strtolower($name));
return $normalized_key;
}
protected function cleanPlateNumber($plate_number)
{
// remove spaces and make upper case
$clean_plate_number = strtoupper(str_replace(' ' , '', $plate_number));
return $clean_plate_number;
}
}

View file

@ -0,0 +1,111 @@
<?php
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Doctrine\ORM\EntityManagerInterface;
use Catalyst\MenuBundle\Annotation\Menu;
use DateTime;
use DateInterval;
use App\Entity\Notification;
class NotificationController extends AbstractController
{
// TODO: security
public function ajaxList(EntityManagerInterface $em)
{
/*
$date = new DateTime();
$date->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;
}
}

View file

@ -616,11 +616,11 @@ class RiderController extends Controller
return $response;
}
public function ajaxRiderName(EntityManagerInterface $em, Request $req)
/**
* @ParamConverter("rider", class="App\Entity\Rider")
*/
public function ajaxRiderName(EntityManagerInterface $em, Rider $rider)
{
$rider_id = $req->query->get('id');
$rider = $em->getRepository(Rider::class)->find($rider_id);
$rider_name = '';
if ($rider != null)
$rider_name = $rider->getFullName();
@ -629,4 +629,5 @@ class RiderController extends Controller
'rider_name' => $rider_name,
]);
}
}

View file

@ -0,0 +1,395 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use DateTime;
/**
* @ORM\Entity
* @ORM\Table(name="cmb_legacy_job_order")
*/
class CMBLegacyJobOrder
{
// legacy internal id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* @ORM\Column(type="datetime")
*/
protected $trans_date;
/**
* @ORM\Column(type="string", length=30, nullable=true)
*/
protected $case_number;
/**
* @ORM\Column(type="string", length=30, nullable=true)
*/
protected $insurer;
/**
* @ORM\Column(type="string", length=20, nullable=true)
*/
protected $plate_number;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $trans_type;
/**
* @ORM\Column(type="string", length=20, nullable=true)
*/
protected $car_brand;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $car_make;
/**
* @ORM\Column(type="string", length=30, nullable=true)
*/
protected $nature_of_call;
/**
* @ORM\Column(type="string", length=400, nullable=true)
*/
protected $address;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $driver;
/**
* @ORM\Column(type="string", length=20, nullable=true)
*/
protected $truck;
/**
* @ORM\Column(type="integer", nullable=true)
*/
protected $workshop_arrival_time;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $status;
/**
* @ORM\Column(type="string", length=100, nullable=true)
*/
protected $cust_name;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $cust_mobile;
/**
* @ORM\Column(type="string", length=20, nullable=true)
*/
protected $reference;
/**
* @ORM\Column(type="integer", nullable=true)
*/
protected $odometer;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $batt_model;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $batt_size;
/**
* @ORM\Column(type="boolean", options={"default":false})
*/
protected $flag_trade_in;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $replaced_by;
/**
* @ORM\Column(type="string", length=4000, nullable=true)
*/
protected $remarks;
/**
* @ORM\Column(type="string", length=50, nullable=true)
*/
protected $satisfaction;
public function __construct()
{
$this->trans_date = new DateTime();
}
public function setID($id)
{
$this->id = $id;
return $this;
}
public function getID()
{
return $this->id;
}
public function setTransDate(DateTime $trans_date)
{
$this->trans_date = $trans_date;
return $this;
}
public function getTransDate()
{
return $this->trans_date;
}
public function setTransType($trans_type)
{
$this->trans_type = $trans_type;
return $this;
}
public function getTransType()
{
return $this->trans_type;
}
public function setCaseNumber($case_number)
{
$this->case_number = $case_number;
return $this;
}
public function getCaseNumber()
{
return $this->case_number;
}
public function setInsurer($insurer)
{
$this->insurer = $insurer;
return $this;
}
public function getInsurer()
{
return $this->insurer;
}
public function setNatureOfCall($nature_of_call)
{
$this->nature_of_call = $nature_of_call;
return $this;
}
public function getNatureOfCall()
{
return $this->nature_of_call;
}
public function setPlateNumber($plate_number)
{
$this->plate_number = $plate_number;
return $this;
}
public function getPlateNumber()
{
return $this->plate_number;
}
public function setCarBrand($car_brand)
{
$this->car_brand = $car_brand;
return $this;
}
public function getCarBrand()
{
return $this->car_brand;
}
public function setCarMake($car_make)
{
$this->car_make = $car_make;
return $this;
}
public function getCarMake()
{
return $this->car_make;
}
public function setAddress($address)
{
$this->address = $address;
return $this;
}
public function getAddress()
{
return $this->address;
}
public function setDriver($driver)
{
$this->driver = $driver;
return $this;
}
public function getDriver()
{
return $this->driver;
}
public function setTruck($truck)
{
$this->truck = $truck;
return $this;
}
public function getTruck()
{
return $this->truck;
}
public function setWorkshopArrivalTime($workshop_arrival_time)
{
$this->workshop_arrival_time = $workshop_arrival_time;
return $this;
}
public function getWorkshopArrivalTime()
{
return $this->workshop_arrival_time;
}
public function setStatus($status)
{
$this->status = $status;
return $this;
}
public function getStatus()
{
return $this->status;
}
public function setCustName($cust_name)
{
$this->cust_name = $cust_name;
return $this;
}
public function getCustName()
{
return $this->cust_name;
}
public function setCustMobile($cust_mobile)
{
$this->cust_mobile = $cust_mobile;
return $this;
}
public function getCustMobile()
{
return $this->cust_mobile;
}
public function setReference($reference)
{
$this->reference = $reference;
return $this;
}
public function getReference()
{
return $this->reference;
}
public function setOdometer($odometer)
{
$this->odometer = $odometer;
return $this;
}
public function getOdometer()
{
return $this->odometer;
}
public function setBatteryModel($batt_model)
{
$this->batt_model = $batt_model;
return $this;
}
public function getBatteryModel()
{
return $this->batt_model;
}
public function setBatterySize($batt_size)
{
$this->batt_size = $batt_size;
return $this;
}
public function getBatterySize()
{
return $this->batt_size;
}
public function setIsTradeIn($flag_trade_in = true)
{
$this->flag_trade_in = $flag_trade_in;
return $this;
}
public function isTradeIn()
{
return $this->flag_trade_in;
}
public function setReplacedBy($replaced_by)
{
$this->replaced_by = $replaced_by;
return $this;
}
public function getReplacedBy()
{
return $this->replaced_by;
}
public function setSatisfaction($satisfaction)
{
$this->satisfaction = $satisfaction;
return $this;
}
public function getSatisfaction()
{
return $this->satisfaction;
}
}

View file

@ -0,0 +1,57 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* @ORM\Entity
* @ORM\Table(name="cmb_legacy_job_order_row")
*/
class CMBLegacyJobOrderRow
{
// unique id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// data from csv file
/**
* @ORM\Column(type="json")
*/
protected $data;
public function __construct()
{
$this->data = [];
}
public function addData($id, $value)
{
$this->data[$id] = $value;
return $this;
}
public function setData(array $data_array)
{
$this->data = $data_array;
return $this;
}
public function getDataById($id)
{
// return null if we don't have it
if (!isset($this->data[$id]))
return null;
return $this->data[$id];
}
public function getData()
{
return $this->data;
}
}

View file

@ -34,7 +34,7 @@ class Customer
// first name
/**
* @ORM\Column(type="string", length=80)
* @ORM\Column(type="string", length=100)
* @Assert\NotBlank()
*/
protected $first_name;
@ -42,7 +42,6 @@ class Customer
// last name
/**
* @ORM\Column(type="string", length=80)
* @Assert\NotBlank()
*/
protected $last_name;
@ -66,7 +65,7 @@ class Customer
// mobile phone
/**
* @ORM\Column(type="string", length=30)
* @ORM\Column(type="string", length=50)
*/
protected $phone_mobile;

148
src/Entity/Notification.php Normal file
View file

@ -0,0 +1,148 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use DateTime;
/**
* @ORM\Entity
* @ORM\Table(name="notification", indexes={@ORM\Index(columns={"user_id"})})
*/
class Notification
{
// unique id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// date / time created
/**
* @ORM\Column(type="datetime")
*/
protected $date_create;
// index by user for fast fetching
// user id (don't link, don't need to)
/**
* @ORM\Column(type="integer")
*/
protected $user_id;
// icon
/**
* @ORM\Column(type="string", length=25)
*/
protected $icon;
// message text
/**
* @ORM\Column(type="string", length=300)
*/
protected $message;
/**
* @ORM\Column(type="string", length=180)
*/
protected $url;
// has user read the notification
/**
* @ORM\Column(type="boolean")
*/
protected $flag_read;
// is this still a fresh notification for the user
/**
* @ORM\Column(type="boolean")
*/
protected $flag_fresh;
public function __construct()
{
$this->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;
}
}

View file

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

View file

@ -0,0 +1,48 @@
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Service\RedisClientProvider;
use App\Entity\Notification;
class NotificationManager
{
protected $redis;
protected $redis_mqtt_key;
protected $em;
const NOTIF_KEY = 'user/{user_id}/notification';
public function __construct(RedisClientProvider $redis_prov, EntityManagerInterface $em, $redis_mqtt_key)
{
$this->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 );
}
}

View file

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

View file

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

View file

@ -448,217 +448,50 @@
<div id="m_header_topbar" class="m-topbar m-stack m-stack--ver m-stack--general">
<div class="m-stack__item m-topbar__nav-wrapper">
<ul class="m-topbar__nav m-nav m-nav--inline">
<!--
<li class="m-nav__item m-topbar__notifications m-topbar__notifications--img m-dropdown m-dropdown--large m-dropdown--header-bg-fill m-dropdown--arrow m-dropdown--align-center m-dropdown--mobile-full-width" data-dropdown-toggle="click" data-dropdown-persistent="true">
<a href="#" class="m-nav__link m-dropdown__toggle" id="m_topbar_notification_icon">
<span class="m-nav__link-badge m-badge m-badge--dot m-badge--dot-small m-badge--danger"></span>
<span class="m-nav__link-icon">
<i class="flaticon-music-2"></i>
</span>
</a>
<div class="m-dropdown__wrapper">
<span class="m-dropdown__arrow m-dropdown__arrow--center"></span>
<div class="m-dropdown__inner">
<div class="m-dropdown__header m--align-center" style="background: url(assets/app/media/img/misc/notification_bg.jpg); background-size: cover;">
<span class="m-dropdown__header-title">
9 New
</span>
<span class="m-dropdown__header-subtitle">
User Notifications
</span>
</div>
<div class="m-dropdown__body">
<div class="m-dropdown__content">
<ul class="nav nav-tabs m-tabs m-tabs-line m-tabs-line--brand" role="tablist">
<li class="nav-item m-tabs__item">
<a class="nav-link m-tabs__link active" data-toggle="tab" href="#topbar_notifications_notifications" role="tab">
Alerts
</a>
</li>
<li class="nav-item m-tabs__item">
<a class="nav-link m-tabs__link" data-toggle="tab" href="#topbar_notifications_events" role="tab">
Events
</a>
</li>
<li class="nav-item m-tabs__item">
<a class="nav-link m-tabs__link" data-toggle="tab" href="#topbar_notifications_logs" role="tab">
Logs
</a>
</li>
</ul>
<div class="tab-content">
<div class="tab-pane active" id="topbar_notifications_notifications" role="tabpanel">
<div class="m-scrollable" data-scrollable="true" data-max-height="250" data-mobile-max-height="200">
<div class="m-list-timeline m-list-timeline--skin-light">
<div class="m-list-timeline__items">
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge -m-list-timeline__badge--state-success"></span>
<span class="m-list-timeline__text">
12 new users registered
</span>
<span class="m-list-timeline__time">
Just now
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
System shutdown
<span class="m-badge m-badge--success m-badge--wide">
pending
</span>
</span>
<span class="m-list-timeline__time">
14 mins
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
New invoice received
</span>
<span class="m-list-timeline__time">
20 mins
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
DB overloaded 80%
<span class="m-badge m-badge--info m-badge--wide">
settled
</span>
</span>
<span class="m-list-timeline__time">
1 hr
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
System error -
<a href="#" class="m-link">
Check
</a>
</span>
<span class="m-list-timeline__time">
2 hrs
</span>
</div>
<div class="m-list-timeline__item m-list-timeline__item--read">
<span class="m-list-timeline__badge"></span>
<span href="" class="m-list-timeline__text">
New order received
<span class="m-badge m-badge--danger m-badge--wide">
urgent
</span>
</span>
<span class="m-list-timeline__time">
7 hrs
</span>
</div>
<div class="m-list-timeline__item m-list-timeline__item--read">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
Production server down
</span>
<span class="m-list-timeline__time">
3 hrs
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge"></span>
<span class="m-list-timeline__text">
Production server up
</span>
<span class="m-list-timeline__time">
5 hrs
</span>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="topbar_notifications_events" role="tabpanel">
<div class="m-scrollable" m-scrollabledata-scrollable="true" data-max-height="250" data-mobile-max-height="200">
<div class="m-list-timeline m-list-timeline--skin-light">
<div class="m-list-timeline__items">
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-success"></span>
<a href="" class="m-list-timeline__text">
New order received
</a>
<span class="m-list-timeline__time">
Just now
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-danger"></span>
<a href="" class="m-list-timeline__text">
New invoice received
</a>
<span class="m-list-timeline__time">
20 mins
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-success"></span>
<a href="" class="m-list-timeline__text">
Production server up
</a>
<span class="m-list-timeline__time">
5 hrs
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-info"></span>
<a href="" class="m-list-timeline__text">
New order received
</a>
<span class="m-list-timeline__time">
7 hrs
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-info"></span>
<a href="" class="m-list-timeline__text">
System shutdown
</a>
<span class="m-list-timeline__time">
11 mins
</span>
</div>
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge m-list-timeline__badge--state1-info"></span>
<a href="" class="m-list-timeline__text">
Production server down
</a>
<span class="m-list-timeline__time">
3 hrs
</span>
</div>
</div>
</div>
</div>
</div>
<div class="tab-pane" id="topbar_notifications_logs" role="tabpanel">
<div class="m-stack m-stack--ver m-stack--general" style="min-height: 180px;">
<div class="m-stack__item m-stack__item--center m-stack__item--middle">
<span class="">
All caught up!
<br>
No new logs.
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
-->
<li class="m-nav__item m-topbar__notifications m-topbar__notifications--img m-dropdown m-dropdown--large m-dropdown--header-bg-fill m-dropdown--arrow m-dropdown--align-right m-dropdown--mobile-full-width" data-dropdown-toggle="click" data-dropdown-persistent="true">
<a href="#" class="m-nav__link m-dropdown__toggle" id="m_topbar_notification_icon">
<span class="m-nav__link-badge m-badge m-badge--dot m-badge--dot-small m-badge--danger"></span>
<span class="m-nav__link-icon">
<i class="flaticon-music-2"></i>
</span>
</a>
<div class="m-dropdown__wrapper">
<span class="m-dropdown__arrow m-dropdown__arrow--right m-dropdown__arrow--adjust"></span>
<div class="m-dropdown__inner">
<div class="m-dropdown__header m--align-center" style="background: url(/assets/app/media/img/misc/notification_bg.jpg); background-size: cover;">
<span class="m-dropdown__header-title">
<span id="notif-count">-</span> New
</span>
<span class="m-dropdown__header-subtitle">
User Notifications
</span>
</div>
<div class="m-dropdown__body">
<div class="m-dropdown__content">
<div class="m-scrollable" data-scrollable="true" data-max-height="250" data-mobile-max-height="200">
<div class="m-list-timeline m-list-timeline--skin-light">
<div id="notif-body" class="m-list-timeline__items">
<div class="m-list-timeline__item">
<span class="m-list-timeline__badge -m-list-timeline__badge--state-success"></span>
<span class="m-list-timeline__text">
<a href="#">This is a notification</a>
</span>
<span class="m-list-timeline__time">
Just now
</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</li>
<li class="m-nav__item m-topbar__user-profile m-topbar__user-profile--img m-dropdown m-dropdown--medium m-dropdown--arrow m-dropdown--header-bg-fill m-dropdown--align-right m-dropdown--mobile-full-width m-dropdown--skin-light" data-dropdown-toggle="click">
<a href="#" class="m-nav__link m-dropdown__toggle">
<span class="m-topbar__userpic">
@ -718,6 +551,8 @@
</div>
</div>
</li>
</ul>
</div>
</div>
@ -770,6 +605,26 @@
<!--end::Page Snippets -->
<!--begin::Extra Scripts -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.min.js" type="text/javascript"></script>
<script src="/assets/js/moment.min.js" type="text/javascript"></script>
<script src="/assets/js/notification.js" type="text/javascript"></script>
<script>
// notifications
var notif = new NotificationHandler({
'notif_ajax_url': '{{ url('notification_ajax_list') }}',
'notif_ajax_update_url': '{{ url('notification_ajax_update') }}',
'icons': {
'jo_new': 'flaticon-placeholder-3 kt-font-brand',
'jo_cancel': 'fa fa-times-circle kt-font-danger',
'rider_accept': 'fa fa-motorcycle kt-font-success',
'rider_reject': 'fa fa-exclamation-triangle kt-font-danger'
},
'default_icon': 'fa fa-asterisk kt-font-brand',
});
notif.clearAll();
notif.loadAll();
notif.listen('{{ app.user.id }}', '{{ mqtt_host }}', {{ mqtt_port }}, {{ ssl_enable }});
</script>
{% block scripts %}{% endblock %}
<!--end::Extra Scripts -->
</body>

View file

@ -35,7 +35,9 @@ function initMap(r_markers, c_markers, icons) {
'zoom': 13,
'rider_popup_url': '/riders/[id]/popup',
'cust_popup_url': '/job-order/[id]/popup',
'icons': icons
'icons': icons,
'rider_name_url': '/riders/[id]/name',
'rider_availability_url': '{{ absolute_url('/riders/[id]/available')|raw }}'
};
var dashmap = new DashboardMap(options, r_markers, c_markers);
@ -55,7 +57,7 @@ function initEventHandler(dashmap, icons, ssl) {
'jo_location': 'jo/+/location',
'jo_status': 'jo/+/status',
'jo_origin': 'jo/+/origin',
'rider_availability': 'rider/+/availability'
'rider_availability': 'rider/+/availability',
},
};

15
utils/clear_jo_data.sql Normal file
View file

@ -0,0 +1,15 @@
delete from jo_event;
delete from invoice_item;
delete from invoice;
delete from ticket;
set foreign_key_checks = 0;
delete from job_order;
set foreign_key_checks = 1;
delete from mobile_session;
delete from customer_vehicle;
delete from customer;
delete from warranty;
update rider set active_jo_id = null;