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 %} diff --git a/templates/home.html.twig b/templates/home.html.twig index 8e5b1760..4da6bd72 100644 --- a/templates/home.html.twig +++ b/templates/home.html.twig @@ -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', }, }; diff --git a/utils/clear_jo_data.sql b/utils/clear_jo_data.sql new file mode 100644 index 00000000..30b20c61 --- /dev/null +++ b/utils/clear_jo_data.sql @@ -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;