diff --git a/.env.dist b/.env.dist index f6d35eec..33902df3 100644 --- a/.env.dist +++ b/.env.dist @@ -57,6 +57,7 @@ COUNTRY_CODE=+insert_country_code_here # redis hash LATEST_ACTIVE_JO=latest_active_jo +HUB_JO_KEY=hub_jo_count # dashboard DASHBOARD_ENABLE=set_to_true_or_false diff --git a/config/services.yaml b/config/services.yaml index 75c83d1d..29231ef0 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -266,3 +266,21 @@ services: event: 'postPersist' entity: 'App\Entity\CustomerVehicle' + # hub service + App\Service\HubSelector: + arguments: + $em: "@doctrine.orm.entity_manager" + $im: "@App\\Service\\InventoryManager" + $hub_distributor: "@App\\Service\\HubDistributor" + $hub_filter_logger: "@App\\Service\\HubFilterLogger" + + # hub distributor + App\Service\HubDistributor: + arguments: + $redis: "@App\\Service\\RedisClientProvider" + $hub_jo_key: "%env(HUB_JO_KEY)%" + + # hub filter logger + App\Service\HubFilterLogger: + arguments: + $em: "@doctrine.orm.entity_manager" diff --git a/src/Command/ResetHubJoCountCommand.php b/src/Command/ResetHubJoCountCommand.php new file mode 100644 index 00000000..ea2314f3 --- /dev/null +++ b/src/Command/ResetHubJoCountCommand.php @@ -0,0 +1,39 @@ +redis = $redis->getRedisClient(); + + parent::__construct(); + } + + protected function configure() + { + $this->setName('hub:jo:reset') + ->setDescription('Reset hub\'s job order count') + ->setHelp('Reset hub\'s job order count'); + } + + + protected function execute(InputInterface $input, OutputInterface $output) + { + $key = 'hub_jo_count'; + + $this->redis->del($key); + + return 0; + } +} diff --git a/src/Controller/APIController.php b/src/Controller/APIController.php index b03f057e..c510db98 100644 --- a/src/Controller/APIController.php +++ b/src/Controller/APIController.php @@ -28,6 +28,7 @@ use App\Ramcar\TradeInType; use App\Ramcar\JOEventType; use App\Ramcar\AdvanceOrderSlot; use App\Ramcar\AutoAssignStatus; +use App\Ramcar\HubCriteria; use App\Service\InvoiceGeneratorInterface; use App\Service\RisingTideGateway; @@ -37,6 +38,9 @@ use App\Service\RiderTracker; use App\Service\MapTools; use App\Service\InventoryManager; use App\Service\RiderAssignmentHandlerInterface; +use App\Service\HubSelector; +use App\Service\HubDistributor; +use App\Service\HubFilterLogger; use App\Entity\MobileSession; use App\Entity\Customer; @@ -844,7 +848,8 @@ class APIController extends Controller implements LoggedController public function requestJobOrder(Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo, MapTools $map_tools, InventoryManager $im, MQTTClient $mclient, - RiderAssignmentHandlerInterface $rah) + RiderAssignmentHandlerInterface $rah, HubSelector $hub_select, + HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger) { // check required parameters and api key $required_params = [ @@ -1009,49 +1014,69 @@ class APIController extends Controller implements LoggedController $invoice = $ic->generateInvoice($icrit); $jo->setInvoice($invoice); - // assign hub and rider - if (($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) || - ($jo->getServicetype() == ServiceType::BATTERY_REPLACEMENT_WARRANTY)) - { - // get nearest hub - // $nearest_hub = $this->findNearestHubWithInventory($jo, $batt, $em, $map_tools, $im); - $nearest_hub = $this->findNearestHub($jo, $em, $map_tools); - } - else - { - $nearest_hub = $this->findNearestHub($jo, $em, $map_tools); - } - if (!empty($nearest_hub)) + // TODO: set this properly, since the other flags + // are on default values + $hub_criteria = new HubCriteria(); + $hub_criteria->setPoint($jo->getCoordinates()) + ->setJoType($jo->getServiceType()); + + // add battery to items + $sku = $batt->getSAPCode(); + if (!empty($sku)) + $hub_criteria->addItem($batt->getSAPCode(), 1); + + // find nearest hubs + $nearest_hubs = $hub_select->find($hub_criteria); + + if (!empty($nearest_hubs)) { + // go through the hub list, find the nearest hub + // with an available rider //error_log('found nearest hub ' . $nearest_hub->getID()); - // assign rider - $available_riders = $nearest_hub->getAvailableRiders(); - if (count($available_riders) > 0) + foreach ($nearest_hubs as $nearest_hub) { - $assigned_rider = null; - if (count($available_riders) > 1) + $available_riders = $nearest_hub['hub']->getAvailableRiders(); + if (count($available_riders) >= 1) { - // TODO: the setting of riders into an array - // will no longer be necessary when the contents - // of randomizeRider changes - $riders = []; - foreach ($available_riders as $rider) + $assigned_rider = null; + if (count($available_riders) == 1) { - $riders[] = $rider; + $assigned_rider = $available_riders[0]; + } + else + { + // TODO: the setting of riders into an array + // will no longer be necessary when the contents + // of randomizeRider changes + $riders = []; + foreach ($available_riders as $rider) + { + $riders[] = $rider; + } + + $assigned_rider = $this->randomizeRider($riders); } - $assigned_rider = $this->randomizeRider($riders); + $jo->setHub($nearest_hub['hub']); + $jo->setRider($assigned_rider); + $jo->setStatus(JOStatus::ASSIGNED); + $jo->setStatusAutoAssign(AutoAssignStatus::HUB_AND_RIDER_ASSIGNED); + + $assigned_rider->setAvailable(false); + + // update redis hub_jo_count for hub + $hub_dist->incrementJoCountForHub($nearest_hub['hub']); + + // break out of loop + break; } else - $assigned_rider = $available_riders[0]; - - //error_log('found rider ' . $assigned_rider->getID()); - $jo->setHub($nearest_hub); - $jo->setRider($assigned_rider); - $jo->setStatus(JOStatus::ASSIGNED); - - $assigned_rider->setAvailable(false); + { + // log hub into hub_filter_log + $hub_filter_logger->logFilteredHub($nearest_hub['hub'], 'no_available_rider'); + // continue to go through list to find hub with an available rider + } } } @@ -2330,7 +2355,8 @@ class APIController extends Controller implements LoggedController public function newRequestJobOrder(Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo, MapTools $map_tools, InventoryManager $im, MQTTClient $mclient, - RiderAssignmentHandlerInterface $rah) + RiderAssignmentHandlerInterface $rah, HubSelector $hub_select, + HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger) { // check required parameters and api key $required_params = [ @@ -2515,50 +2541,68 @@ class APIController extends Controller implements LoggedController // check if hub is null if ($hub == null) { - // find nearest hub - if (($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) || - ($jo->getServicetype() == ServiceType::BATTERY_REPLACEMENT_WARRANTY)) - { - // get nearest hub - // $nearest_hub = $this->findNearestHubWithInventory($jo, $batt, $em, $map_tools, $im); - $nearest_hub = $this->findNearestHub($jo, $em, $map_tools); - } - else - { - $nearest_hub = $this->findNearestHub($jo, $em, $map_tools); - } + // TODO: set this properly, since the other flags + // are on default values + $hub_criteria = new HubCriteria(); + $hub_criteria->setPoint($jo->getCoordinates()) + ->setJoType($jo->getServiceType()); - if (!empty($nearest_hub)) + // add battery to items + $sku = $batt->getSAPCode(); + if (!empty($sku)) + $hub_criteria->addItem($batt->getSAPCode(), 1); + + // find nearest hubs + $nearest_hubs = $hub_select->find($hub_criteria); + + if (!empty($nearest_hubs)) { + // go through the hub list, find the nearest hub + // with an available rider //error_log('found nearest hub ' . $nearest_hub->getID()); - // assign rider - $available_riders = $nearest_hub->getAvailableRiders(); - if (count($available_riders) > 0) + foreach ($nearest_hubs as $nearest_hub) { - $assigned_rider = null; - if (count($available_riders) > 1) + $available_riders = $nearest_hub['hub']->getAvailableRiders(); + if (count($available_riders) >= 1) { - // TODO: the setting of riders into an array - // will no longer be necessary when the contents - // of randomizeRider changes - $riders = []; - foreach ($available_riders as $rider) + $assigned_rider = null; + if (count($available_riders) == 1) { - $riders[] = $rider; + $assigned_rider = $available_riders[0]; + } + else + { + // TODO: the setting of riders into an array + // will no longer be necessary when the contents + // of randomizeRider changes + $riders = []; + foreach ($available_riders as $rider) + { + $riders[] = $rider; + } + + $assigned_rider = $this->randomizeRider($riders); } - $assigned_rider = $this->randomizeRider($riders); + $jo->setHub($nearest_hub['hub']); + $jo->setRider($assigned_rider); + $jo->setStatus(JOStatus::ASSIGNED); + $jo->setStatusAutoAssign(AutoAssignStatus::HUB_AND_RIDER_ASSIGNED); + + $assigned_rider->setAvailable(false); + + // update redis hub_jo_count for hub + $hub_dist->incrementJoCountForHub($nearest_hub['hub']); + + // break out of loop + break; } else - $assigned_rider = $available_riders[0]; - - //error_log('found rider ' . $assigned_rider->getID()); - $jo->setHub($nearest_hub); - $jo->setRider($assigned_rider); - $jo->setStatus(JOStatus::ASSIGNED); - $jo->setStatusAutoAssign(AutoAssignStatus::HUB_AND_RIDER_ASSIGNED); - - $assigned_rider->setAvailable(false); + { + // log hub into hub_filter_log + $hub_filter_logger->logFilteredHub($nearest_hub['hub'], 'no_available_rider'); + // continue to go through list to find hub with an available rider + } } } } @@ -2581,7 +2625,10 @@ class APIController extends Controller implements LoggedController $jo->setStatusAutoAssign(AutoAssignStatus::HUB_ASSIGNED); if ($date_schedule != null) - $jo->setDateSchedule($date_schedule); + $jo->setDateSchedule($date_schedule); + + // update redis hub_jo_count for hub + $hub_dist->incrementJoCountForHub($hub); } $em->persist($jo); diff --git a/src/Controller/HubController.php b/src/Controller/HubController.php index d2e52d6a..864a6261 100644 --- a/src/Controller/HubController.php +++ b/src/Controller/HubController.php @@ -17,9 +17,11 @@ use DateTime; use Catalyst\MenuBundle\Annotation\Menu; -use App\Service\MapTools; use App\Service\RiderTracker; use App\Service\RisingTideGateway; +use App\Service\HubSelector; + +use App\Ramcar\HubCriteria; class HubController extends Controller { @@ -311,7 +313,7 @@ class HubController extends Controller $response->send(); } - public function nearest(MapTools $map_tools, Request $req) + public function nearest(HubSelector $hub_selector, Request $req) { // get lat / long $lat = $req->query->get('lat'); @@ -319,7 +321,12 @@ class HubController extends Controller // get nearest hubs according to position $point = new Point($long, $lat); - $result = $map_tools->getClosestHubs($point, 10, date("H:i:s")); + + // set up hub criteria + $hub_criteria = new HubCriteria(); + $hub_criteria->setPoint($point); + $hub_selector->find($hub_criteria); + //$result = $map_tools->getClosestHubs($point, 10, date("H:i:s")); $hubs = []; foreach ($result as $hub_res) diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index 11c7edf0..15f524d6 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -23,6 +23,7 @@ use App\Service\MapTools; use App\Service\MQTTClient; use App\Service\APNSClient; use App\Service\InventoryManager; +use App\Service\HubSelector; use App\Service\RiderTracker; use App\Service\MotivConnector; @@ -315,13 +316,13 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_proc") */ - public function processingForm(MapTools $map_tools, $id, JobOrderHandlerInterface $jo_handler, GISManagerInterface $gis, MotivConnector $motiv) + public function processingForm(HubSelector $hub_selector, $id, JobOrderHandlerInterface $jo_handler, GISManagerInterface $gis, MotivConnector $motiv) { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); try { - $params = $jo_handler->initializeProcessingForm($id, $map_tools, $motiv); + $params = $jo_handler->initializeProcessingForm($id, $hub_selector, $motiv); } catch (AccessDeniedHttpException $e) { @@ -504,14 +505,14 @@ class JobOrderController extends Controller /** * @Menu(selected="jo_open") */ - public function openHubForm(MapTools $map_tools, $id, JobOrderHandlerInterface $jo_handler, + public function openHubForm(HubSelector $hub_selector, $id, JobOrderHandlerInterface $jo_handler, GISManagerInterface $gis) { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); try { - $params = $jo_handler->initializeHubForm($id, $map_tools); + $params = $jo_handler->initializeHubForm($id, $hub_selector); } catch (NotFoundHttpException $e) { @@ -1204,6 +1205,7 @@ class JobOrderController extends Controller ]); } + // this is uncalled or does not display in admin panel /** * @Menu(selected="jo_autoassign") */ @@ -1223,6 +1225,7 @@ class JobOrderController extends Controller return $this->render($template, $params); } + // this is uncalled or does not display in admin panel public function autoAssignSubmit(Request $req, JobOrderHandlerInterface $jo_handler, EntityManagerInterface $em, MapTools $map_tools, InventoryManager $im) diff --git a/src/Entity/HubFilterLog.php b/src/Entity/HubFilterLog.php new file mode 100644 index 00000000..00f8394d --- /dev/null +++ b/src/Entity/HubFilterLog.php @@ -0,0 +1,83 @@ +date_create = new DateTime(); + } + + public function getID() + { + return $this->id; + } + + public function getDateCreate() + { + return $this->date_create; + } + + public function setHub(Hub $hub) + { + $this->hub = $hub; + return $this; + } + + public function getHub() + { + return $this->hub; + } + + public function setFilterTypeId($filter_type_id) + { + $this->filter_type_id = $filter_type_id; + return $this; + } + + public function getFilterTypeId() + { + return $this->filter_type_id; + } +} diff --git a/src/Ramcar/HubCriteria.php b/src/Ramcar/HubCriteria.php new file mode 100644 index 00000000..2c91654f --- /dev/null +++ b/src/Ramcar/HubCriteria.php @@ -0,0 +1,108 @@ +limit_results = 10; + $this->limit_distance = 500; + $this->jo_type = ''; + $this->date_time = null; + $this->has_inventory = false; + $this->items = []; + } + + public function setPoint(Point $point) + { + $this->point = $point; + return $this; + } + + public function getPoint() + { + return $this->point; + } + + public function setLimitResults($limit_results) + { + $this->limit_results = $limit_results; + return $this; + } + + public function getLimitResults() + { + return $this->limit_results; + } + + public function setLimitDistance($limit_distance) + { + $this->limit_distance = $limit_distance; + return $this; + } + + public function getLimitDistance() + { + return $this->limit_distance; + } + + public function setInventoryCheck($flag_inventory_check = true) + { + $this->flag_inventory_check = $flag_inventory_check; + return $this; + } + + public function hasInventoryCheck() + { + return $this->flag_inventory_check; + } + + public function setJoType($jo_type) + { + // TODO: validate the jo type + $this->jo_type = $jo_type; + return $this; + } + + public function getJoType() + { + return $this->jo_type; + } + + public function setDateTime(DateTime $date_time) + { + $this->date_time = $date_time; + return $this; + } + + public function getDateTime() + { + return $this->date_time; + } + + public function addItem($sku, $quantity) + { + // at this point, sku is assumed to be a valid item + $this->items[$sku] = $quantity; + return $this; + } + + public function getItems() + { + return $this->items; + } +} diff --git a/src/Service/HubDistributor.php b/src/Service/HubDistributor.php new file mode 100644 index 00000000..e401a4a1 --- /dev/null +++ b/src/Service/HubDistributor.php @@ -0,0 +1,109 @@ +redis = $redis->getRedisClient(); + $this->hub_jo_key = $hub_jo_key; + } + + public function incrementJoCountForHub(Hub $hub) + { + $key = $hub->getID(); + + // get current count + $jo_count = $this->redis->hget($this->hub_jo_key, $key); + if ($jo_count == false) + { + // hub not in hash + // add hub to hash + // set to 1 since this is first jo for hub + $this->redis->hset($this->hub_jo_key, $key, 1); + } + else + { + // hub exist in hash + // add to count + $this->redis->hset($this->hub_jo_key, $key, $jo_count + 1); + } + } + + public function decrementJoCountForHub(Hub $hub) + { + $key = $hub->getID(); + + // get current count + $jo_count = $this->redis->hget($this->hub_jo_key, $key); + if ($jo_count) + { + // hub exist in hash + // decrement count + $this->redis->hset($this->hub_jo_key, $key, $jo_count - 1); + } + } + + public function arrangeHubs($hubs) + { + if (count($hubs) == 1) + return $hubs; + + $arranged_hubs = []; + + foreach ($hubs as $hub_data) + { + $hub = $hub_data['hub']; + + // need the id of hub + $key = $hub->getID(); + + // get jo count of hub + $hub_jo_count = $this->redis->hget($this->hub_jo_key, $key); + + // check if hub is in hash. if not, hub has no jobs + // but should still be added to results + if ($hub_jo_count != null) + { + $arranged_hubs[] = [ + 'hub' => $hub, + 'db_distance' => $hub_data['db_distance'], + 'distance' => $hub_data['distance'], + 'duration' => $hub_data['duration'], + 'jo_count' => $hub_jo_count, + ]; + } + else + { + $arranged_hubs[] = [ + 'hub' => $hub, + 'db_distance' => $hub_data['db_distance'], + 'distance' => $hub_data['distance'], + 'duration' => $hub_data['duration'], + 'jo_count' => 0, + ]; + } + } + + usort($arranged_hubs, function($a, $b) { + if ($a['jo_count'] == $b['jo_count']) + return 0; + if ($a['jo_count'] < $b['jo_count']) + return -1; + else + return 1; + }); + + //error_log('arranged hubs ' . json_encode($arranged_hubs)); + + return $arranged_hubs; + } +} diff --git a/src/Service/HubFilterLogger.php b/src/Service/HubFilterLogger.php new file mode 100644 index 00000000..71cc1929 --- /dev/null +++ b/src/Service/HubFilterLogger.php @@ -0,0 +1,29 @@ +em = $em; + } + + public function logFilteredHub(Hub $hub, $filter_type) + { + $hub_filter_log = new HubFilterLog(); + + $hub_filter_log->setHub($hub) + ->setFilterTypeId($filter_type); + + $this->em->persist($hub_filter_log); + $this->em->flush(); + } +} diff --git a/src/Service/HubSelector.php b/src/Service/HubSelector.php new file mode 100644 index 00000000..ea55b160 --- /dev/null +++ b/src/Service/HubSelector.php @@ -0,0 +1,335 @@ +em = $em; + $this->im = $im; + $this->hub_distributor = $hub_distributor; + $this->hub_filter_logger = $hub_filter_logger; + } + + 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(); + + $results = []; + + // get all the hubs within distance + $filtered_hubs = $this->getClosestHubs($point, $limit_distance); + + //error_log('closest hubs ' . json_encode($filtered_hubs)); + + // filter the first hub results for date and opening times + $hubs_date_time = $this->filterHubsByDateAndTime($filtered_hubs, $date_time); + $filtered_hubs = $hubs_date_time; + + // filter jo types + $hubs_jo_type = $this->filterHubsByJoType($filtered_hubs, $jo_type); + $filtered_hubs = $hubs_jo_type; + + // inventory filter + $hubs_inventory = $this->filterHubsByInventory($filtered_hubs, $flag_inventory_check, + $jo_type, $items); + $filtered_hubs = $hubs_inventory; + + // round robin filter + $hubs_round_robin = $this->filterHubsByRoundRobin($filtered_hubs); + $filtered_hubs = $hubs_round_robin; + + //error_log(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 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 + $this->hub_filter_logger->logFilteredHub($hub, '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 + $this->hub_filter_logger->logFilteredHub($hub, '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); + } +} diff --git a/src/Service/InventoryManager.php b/src/Service/InventoryManager.php index 4dc5a8b1..f0669559 100644 --- a/src/Service/InventoryManager.php +++ b/src/Service/InventoryManager.php @@ -71,6 +71,7 @@ class InventoryManager // check if the response is empty $results = []; + //error_log($response); if (!empty($response)) $results = json_decode($response, true); diff --git a/src/Service/JobOrderHandler/ResqJobOrderHandler.php b/src/Service/JobOrderHandler/ResqJobOrderHandler.php index 10d5b2f8..49317478 100644 --- a/src/Service/JobOrderHandler/ResqJobOrderHandler.php +++ b/src/Service/JobOrderHandler/ResqJobOrderHandler.php @@ -40,6 +40,7 @@ use App\Ramcar\JORejectionReason; use App\Ramcar\CustomerNotWaitReason; use App\Ramcar\NoTradeInReason; use App\Ramcar\WillingToWaitContent; +use App\Ramcar\HubCriteria; use App\Service\InvoiceGeneratorInterface; use App\Service\JobOrderHandlerInterface; @@ -49,6 +50,8 @@ use App\Service\MQTTClient; use App\Service\APNSClient; use App\Service\MapTools; use App\Service\RisingTideGateway; +use App\Service\HubSelector; +use App\Service\HubDistributor; use CrEOF\Spatial\PHP\Types\Geometry\Point; @@ -70,13 +73,15 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface protected $country_code; protected $wh; protected $rt; + protected $hub_dist; protected $template_hash; public function __construct(Security $security, EntityManagerInterface $em, InvoiceGeneratorInterface $ic, ValidatorInterface $validator, TranslatorInterface $translator, RiderAssignmentHandlerInterface $rah, - string $country_code, WarrantyHandler $wh, RisingTideGateway $rt) + string $country_code, WarrantyHandler $wh, RisingTideGateway $rt, + HubDistributor $hub_dist) { $this->em = $em; $this->ic = $ic; @@ -87,6 +92,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface $this->country_code = $country_code; $this->wh = $wh; $this->rt = $rt; + $this->hub_dist = $hub_dist; $this->loadTemplates(); } @@ -755,6 +761,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface 'event' => 'outlet_assign' ]; $mclient->sendEvent($obj, $payload); + + // update redis hub jo count + $this->hub_dist->incrementJoCountForHub($hub); } return $error_array; @@ -1124,6 +1133,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface $more_reason = $req->request->get('not_wait_notes'); } + // get previously assigned hub, if any + $old_hub = $obj->getHub(); + if (empty($error_array)) { // rider mqtt event @@ -1197,6 +1209,13 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface 'event' => 'outlet_assign' ]; $mclient->sendEvent($obj, $payload); + + // update redis hub_jo_count for hub + // decrement old hub's count and increment new hub's count + if ($old_hub != null) + $this->hub_dist->decrementJoCountForHub($old_hub); + if ($hub != null) + $this->hub_dist->incrementJoCountForHub($hub); } return $error_array; @@ -1632,6 +1651,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface return $params; } + // CMB code public function initializeOneStepEditForm($id, $map_tools) { $em = $this->em; @@ -1812,7 +1832,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } // initialize dispatch/processing job order form - public function initializeProcessingForm($id, $map_tools, $motiv) + public function initializeProcessingForm($id, HubSelector $hub_selector, $motiv) { $em = $this->em; @@ -1879,7 +1899,12 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface $params['rejection_reasons'] = JORejectionReason::getCollection(); // get closest hubs - $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + // set hub criteria + $hub_criteria = new HubCriteria(); + $hub_criteria->setPoint($obj->getCoordinates()) + ->setLimitResults(50); + $hubs = $hub_selector->find($hub_criteria); + //$hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); $params['hubs'] = []; @@ -2103,7 +2128,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } // initialize hub form - public function initializeHubForm($id, $map_tools) + public function initializeHubForm($id, HubSelector $hub_selector) { $em = $this->em; @@ -2128,7 +2153,11 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface $params['rejection_reasons'] = JORejectionReason::getCollection(); // get closest hubs - $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); + $hub_criteria = new HubCriteria(); + $hub_criteria->setPoint($obj->getCoordinates()) + ->setLimitResults(50); + $hubs = $hub_selector->find($hub_criteria); + //$hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); $params['status_cancelled'] = JOStatus::CANCELLED; $params['hubs'] = []; diff --git a/src/Service/JobOrderHandlerInterface.php b/src/Service/JobOrderHandlerInterface.php index 59aea943..cff06242 100644 --- a/src/Service/JobOrderHandlerInterface.php +++ b/src/Service/JobOrderHandlerInterface.php @@ -7,6 +7,7 @@ use Symfony\Component\HttpFoundation\Request; use App\Service\MQTTClient; use App\Service\APNSClient; use App\Service\MapTools; +use App\Service\HubSelector; use App\Entity\JobOrder; @@ -67,7 +68,7 @@ interface JobOrderHandlerInterface public function initializeAllForm(int $id); // initialize dispatch/processing job order form - public function initializeProcessingForm(int $id, MapTools $map_tools, $motiv); + public function initializeProcessingForm(int $id, HubSelector $hub_selector, $motiv); // initialize assign job order form public function initializeAssignForm(int $id); @@ -76,7 +77,7 @@ interface JobOrderHandlerInterface public function initializeFulfillmentForm(int $id); // initialize hub form - public function initializeHubForm(int $id, MapTools $map_tools); + public function initializeHubForm(int $id, HubSelector $hub_selector); // initialize rider form public function initializeRiderForm(int $id);