450 lines
14 KiB
PHP
450 lines
14 KiB
PHP
<?php
|
|
|
|
namespace App\Service;
|
|
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
|
|
use Symfony\Contracts\Translation\TranslatorInterface;
|
|
|
|
use CrEOF\Spatial\PHP\Types\Geometry\Point;
|
|
|
|
use App\Entity\Hub;
|
|
|
|
use App\Service\HubDistributor;
|
|
use App\Service\InventoryManager;
|
|
use App\Service\HubFilterLogger;
|
|
use App\Service\RisingTideGateway;
|
|
|
|
use App\Ramcar\HubCriteria;
|
|
use App\Ramcar\ServiceType;
|
|
|
|
class HubSelector
|
|
{
|
|
protected $em;
|
|
protected $im;
|
|
protected $hub_distributor;
|
|
protected $hub_filter_logger;
|
|
protected $trans;
|
|
protected $rt;
|
|
|
|
public function __construct(EntityManagerInterface $em, InventoryManager $im,
|
|
HubDistributor $hub_distributor, HubFilterLogger $hub_filter_logger,
|
|
TranslatorInterface $trans, RisingTideGateway $rt)
|
|
{
|
|
$this->em = $em;
|
|
$this->im = $im;
|
|
$this->hub_distributor = $hub_distributor;
|
|
$this->hub_filter_logger = $hub_filter_logger;
|
|
$this->trans = $trans;
|
|
$this->rt = $rt;
|
|
}
|
|
|
|
public function find(HubCriteria $criteria)
|
|
{
|
|
$point = $criteria->getPoint();
|
|
$limit_results = $criteria->getLimitResults();
|
|
$limit_distance = $criteria->getLimitDistance();
|
|
$jo_type = $criteria->getJoType();
|
|
$flag_inventory_check = $criteria->hasInventoryCheck();
|
|
$items = $criteria->getItems();
|
|
$date_time = $criteria->getDateTime();
|
|
$payment_method = $criteria->getPaymentMethod();
|
|
$flag_emergency = $criteria->isEmergency();
|
|
|
|
$results = [];
|
|
|
|
// get all the hubs within distance
|
|
$filtered_hubs = $this->getClosestHubs($point, $limit_distance);
|
|
|
|
error_log('closest hubs ' . json_encode($filtered_hubs));
|
|
|
|
if (!$flag_emergency)
|
|
{
|
|
// filter the first hub results for date and opening times
|
|
$hubs_date_time = $this->filterHubsByDateAndTime($filtered_hubs, $date_time);
|
|
$filtered_hubs = $hubs_date_time;
|
|
|
|
//error_log('date_time hubs ' . json_encode($filtered_hubs));
|
|
|
|
// filter jo types
|
|
$hubs_jo_type = $this->filterHubsByJoType($filtered_hubs, $jo_type);
|
|
$filtered_hubs = $hubs_jo_type;
|
|
|
|
//error_log('jo_type hubs ' . json_encode($filtered_hubs));
|
|
|
|
// filter hubs by payment methods
|
|
$hubs_payment_method = $this->filterHubsByPaymentMethod($filtered_hubs, $payment_method);
|
|
$filtered_hubs = $hubs_payment_method;
|
|
|
|
//error_log('payment hubs ' . json_encode($filtered_hubs));
|
|
|
|
// inventory filter
|
|
$hubs_inventory = $this->filterHubsByInventory($filtered_hubs, $flag_inventory_check,
|
|
$jo_type, $items);
|
|
$filtered_hubs = $hubs_inventory;
|
|
|
|
//error_log('inventory hubs ' . json_encode($filtered_hubs));
|
|
|
|
// round robin filter
|
|
$hubs_round_robin = $this->filterHubsByRoundRobin($filtered_hubs);
|
|
$filtered_hubs = $hubs_round_robin;
|
|
|
|
//error_log('round robin hubs ' . json_encode($filtered_hubs));
|
|
|
|
// max results filter
|
|
$hubs_max_result = $this->filterHubsByMaxResults($filtered_hubs, $limit_results);
|
|
$filtered_hubs = $hubs_max_result;
|
|
}
|
|
|
|
$results = $filtered_hubs;
|
|
|
|
error_log(json_encode($results));
|
|
|
|
return $results;
|
|
}
|
|
|
|
protected function filterHubsByRoundRobin($hubs)
|
|
{
|
|
if (empty($hubs))
|
|
return $hubs;
|
|
|
|
$results = [];
|
|
// call hub distributor service
|
|
$arranged_hubs = $this->hub_distributor->arrangeHubs($hubs);
|
|
$results = $arranged_hubs;
|
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
protected function filterHubsByMaxResults($hubs, $limit_result)
|
|
{
|
|
if (empty($hubs))
|
|
return $hubs;
|
|
if (empty($limit_result))
|
|
return $hubs;
|
|
|
|
$results = [];
|
|
for ($i = 0; $i < count($hubs); $i++)
|
|
{
|
|
if ($i < $limit_result)
|
|
$results[] = $hubs[$i];
|
|
else
|
|
$this->hub_filter_logger->logFilteredHub($hubs[$i]['hub'], 'max_results');
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
protected function filterHubsByJoType($hubs, $jo_type)
|
|
{
|
|
if (empty($hubs))
|
|
return $hubs;
|
|
if (empty($jo_type))
|
|
return $hubs;
|
|
|
|
$results = [];
|
|
foreach ($hubs as $hub_data)
|
|
{
|
|
$hub = $hub_data['hub'];
|
|
|
|
// TODO: for now, have this return true
|
|
$has_jo_type = true;
|
|
// check if hub offers the jo_type
|
|
// TODO: add service to hub
|
|
if ($has_jo_type)
|
|
$results[] = [
|
|
'hub' => $hub,
|
|
'db_distance' => $hub_data['db_distance'],
|
|
'distance' => $hub_data['distance'],
|
|
'duration' => $hub_data['duration'],
|
|
];
|
|
else
|
|
$this->hub_filter_logger->logFilteredHub($hub, 'job_order_type');
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
protected function filterHubsByPaymentMethod($hubs, $payment_method)
|
|
{
|
|
if (empty($hubs))
|
|
return $hubs;
|
|
if (empty($payment_method))
|
|
return $hubs;
|
|
|
|
$results = [];
|
|
foreach ($hubs as $hub_data)
|
|
{
|
|
$hub = $hub_data['hub'];
|
|
|
|
// name of payment method is what is saved
|
|
$payment_methods = $hub->getPaymentMethods();
|
|
if ($payment_methods != null)
|
|
{
|
|
$flag_found_pmethod = false;
|
|
foreach ($payment_methods as $pmethod)
|
|
{
|
|
if ($pmethod == $payment_method)
|
|
{
|
|
$results[] = [
|
|
'hub' => $hub,
|
|
'db_distance' => $hub_data['db_distance'],
|
|
'distance' => $hub_data['distance'],
|
|
'duration' => $hub_data['duration'],
|
|
];
|
|
}
|
|
$flag_found_pmethod = true;
|
|
}
|
|
|
|
if (!$flag_found_pmethod)
|
|
$this->hub_filter_logger->logFilteredHub($hub, 'no_payment_method');
|
|
}
|
|
else
|
|
$this->hub_filter_logger->logFilteredHub($hub, 'no_payment_method');
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
protected function filterHubsByDateAndTime($hubs, $date_time)
|
|
{
|
|
if (empty($hubs))
|
|
return $hubs;
|
|
|
|
if ($date_time == null)
|
|
return $hubs;
|
|
|
|
$results = [];
|
|
|
|
foreach ($hubs as $hub_data)
|
|
{
|
|
// go through each hub's opening times to check if hub is open
|
|
// for the specified time
|
|
// get hub opening and closing times
|
|
// TODO: maybe in the future, might also have to check if hub
|
|
// is open/available on date/day
|
|
$hub = $hub_data['hub'];
|
|
|
|
$time_open = $hub->getTimeOpen()->format("H:i:s");
|
|
$time_close = $hub->getTimeClose()->format("H:i:s");
|
|
|
|
$filter_time = $date_time->format("H:i:s");
|
|
|
|
if (($filter_time >= $time_open) &&
|
|
($filter_time <= $time_close))
|
|
{
|
|
$results[] = [
|
|
'hub' => $hub,
|
|
'db_distance' => $hub_data['db_distance'],
|
|
'distance' => $hub_data['distance'],
|
|
'duration' => $hub_data['duration'],
|
|
];
|
|
}
|
|
else
|
|
$this->hub_filter_logger->logFilteredHub($hub, 'date_and_time');
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
protected function filterHubsByInventory($hubs, $flag_inventory_check, $jo_type, $items)
|
|
{
|
|
if (empty($hubs))
|
|
return $hubs;
|
|
|
|
if (!$flag_inventory_check)
|
|
return $hubs;
|
|
|
|
$results = [];
|
|
if ($flag_inventory_check)
|
|
{
|
|
foreach ($hubs as $hub_data)
|
|
{
|
|
$hub = $hub_data['hub'];
|
|
|
|
if ($jo_type == ServiceType::BATTERY_REPLACEMENT_NEW)
|
|
{
|
|
// call inventory
|
|
$has_items = $this->checkInventory($items, $hub);
|
|
if ($has_items)
|
|
$results[] = [
|
|
'hub' => $hub,
|
|
'db_distance' => $hub_data['db_distance'],
|
|
'distance' => $hub_data['distance'],
|
|
'duration' => $hub_data['duration'],
|
|
];
|
|
else
|
|
{
|
|
// get the skus for the message
|
|
$sku_text = '';
|
|
foreach ($items as $key => $value)
|
|
{
|
|
$sku_text .= ' ' . $key;
|
|
}
|
|
// send SMS to hub
|
|
$message = str_replace('item_display', trim($sku_text), $this->trans->trans('no_inventory_message'));
|
|
error_log($message);
|
|
$this->sendSMSMessage($hub, $items);
|
|
|
|
$this->hub_filter_logger->logFilteredHub($hub, 'no_inventory');
|
|
}
|
|
}
|
|
if ($jo_type == ServiceType::BATTERY_REPLACEMENT_WARRANTY)
|
|
{
|
|
// call inventory
|
|
$has_items = $this->checkInventory($items, $hub);
|
|
if ($has_items)
|
|
$results[] = [
|
|
'hub' => $hub,
|
|
'db_distance' => $hub_data['db_distance'],
|
|
'distance' => $hub_data['distance'],
|
|
'duration' => $hub_data['duration'],
|
|
];
|
|
else
|
|
{
|
|
// get the skus for the message
|
|
$sku_text = '';
|
|
foreach ($items as $key => $value)
|
|
{
|
|
$sku_text .= ' ' . $key;
|
|
}
|
|
// send SMS to hub
|
|
$message = str_replace('item_display', trim($sku_text), $this->trans->trans('no_inventory_message'));
|
|
error_log($message);
|
|
$this->sendSMSMessage($hub, $items);
|
|
|
|
$this->hub_filter_logger->logFilteredHub($hub, 'no_inventory');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return $results;
|
|
}
|
|
|
|
protected function getClosestHubs(Point $point, $limit_distance)
|
|
{
|
|
// get closest hubs based on st_distance function from db
|
|
$query_string = 'SELECT h, st_distance(h.coordinates, point(:lng, :lat)) as dist FROM App\Entity\Hub h WHERE h.status_open = true ORDER BY dist';
|
|
|
|
$query = $this->em->createQuery($query_string)
|
|
->setParameter('lat', $point->getLatitude())
|
|
->setParameter('lng', $point->getLongitude());
|
|
|
|
// error_log($query->getSql());
|
|
$result = $query->getResult();
|
|
|
|
$hubs = [];
|
|
$hubs_data = [];
|
|
foreach ($result as $row)
|
|
{
|
|
$hubs[] = $row[0];
|
|
|
|
// get coordinates of hub
|
|
$hub_coordinates = $row[0]->getCoordinates();
|
|
|
|
$cust_lat = $point->getLatitude();
|
|
$cust_lng = $point->getLongitude();
|
|
|
|
$hub_lat = $hub_coordinates->getLatitude();
|
|
$hub_lng = $hub_coordinates->getLongitude();
|
|
|
|
// get distance in kilometers from customer point to hub point
|
|
$dist = $this->distance($cust_lat, $cust_lng, $hub_lat, $hub_lng);
|
|
|
|
if ($dist < $limit_distance)
|
|
{
|
|
$hubs_data[] = [
|
|
'hub' => $row[0],
|
|
'db_distance' => $row['dist'],
|
|
'distance' => $dist,
|
|
'duration' => 0,
|
|
];
|
|
}
|
|
else
|
|
{
|
|
$this->hub_filter_logger->logFilteredHub($row[0], 'distance');
|
|
}
|
|
}
|
|
|
|
return $hubs_data;
|
|
}
|
|
|
|
protected function checkInventory($items, $hub)
|
|
{
|
|
// check if hub has all items
|
|
$skus = [];
|
|
$branch_codes[] = $hub->getBranchCode();
|
|
$result = false;
|
|
|
|
foreach ($items as $key=> $value)
|
|
{
|
|
// add sap code of item/battery into array since
|
|
// getBranchesInventory takes in an array of hubs/branches
|
|
// and an array of skus
|
|
// $items as format: $items[sku] = quantity
|
|
$skus[] = $key;
|
|
}
|
|
|
|
// call InventoryManager's getBranchesInventory to check if hub has all items
|
|
$branches_with_items = $this->im->getBranchesInventory($branch_codes, $skus);
|
|
|
|
if (!empty($branches_with_items))
|
|
{
|
|
// check if branch has enough quantity for item
|
|
foreach ($branches_with_items as $branch)
|
|
{
|
|
// get quantity from call
|
|
$qty_available = $branch['Quantity'];
|
|
|
|
// get the quantity request
|
|
$sku_requested = $branch['SapCode'];
|
|
$qty_requested = $items[$sku_requested];
|
|
if ($qty_available >= $qty_requested)
|
|
$result = true;
|
|
}
|
|
}
|
|
|
|
// return true or false
|
|
return $result;
|
|
}
|
|
|
|
// convert db distance to kilometers
|
|
protected function distance($lat1, $lon1, $lat2, $lon2)
|
|
{
|
|
if (($lat1 == $lat2) && ($lon1 == $lon2))
|
|
return 0;
|
|
|
|
$theta = $lon1 - $lon2;
|
|
$dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
|
|
$dist = acos($dist);
|
|
$dist = rad2deg($dist);
|
|
$miles = $dist * 60 * 1.1515;
|
|
|
|
return round(($miles * 1.609344), 1);
|
|
}
|
|
|
|
protected function sendSMSMessage($hub, $items)
|
|
{
|
|
// compose message
|
|
// get the skus for the message
|
|
$sku_text = '';
|
|
foreach ($items as $key => $value)
|
|
{
|
|
$sku_text .= ' ' . $key;
|
|
}
|
|
$message = str_replace('item_display', trim($sku_text), $this->trans->trans('no_inventory_message'));
|
|
|
|
// get hub notification number
|
|
$mobile_number = $hub->getNotifNumber();
|
|
|
|
if (!empty($mobile_number))
|
|
{
|
|
// send SMS message
|
|
error_log('sending sms to - ' . $mobile_number);
|
|
$this->rt->sendSMS($mobile_number, 'MOTOLITE', $message);
|
|
}
|
|
}
|
|
}
|
|
|