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