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(); $flag_riders_check = $criteria->hasRidersCheck(); $items = $criteria->getItems(); $date_time = $criteria->getDateTime(); $payment_method = $criteria->getPaymentMethod(); $flag_emergency = $criteria->isEmergency(); $flag_round_robin = $criteria->isRoundRobin(); $jo_id = $criteria->getJobOrderId(); $customer_id = $criteria->getCustomerId(); $results = []; // error_log('payment methods ' . $payment_method); // error_log('distance limit ' . $limit_distance); // error_log('emergency flag ' . $flag_emergency); // get all the hubs within distance $filtered_hubs = $this->getClosestHubs($point, $limit_distance, $jo_id, $customer_id); // 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, $jo_id, $customer_id); $filtered_hubs = $hubs_date_time; // error_log('date_time hubs ' . json_encode($filtered_hubs)); if (!$flag_emergency) { // filter jo types $hubs_jo_type = $this->filterHubsByJoType($filtered_hubs, $jo_type, $jo_id, $customer_id); $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, $jo_id, $customer_id); $filtered_hubs = $hubs_payment_method; //error_log('payment hubs ' . json_encode($filtered_hubs)); //error_log('inventory hubs ' . json_encode($filtered_hubs)); // error_log('round robin hubs ' . json_encode($filtered_hubs)); // available riders filter $hubs_riders = $this->filterHubsByRiderAvailability($filtered_hubs, $flag_riders_check, $jo_type, $jo_id, $customer_id); $filtered_hubs = $hubs_riders; // inventory filter $hubs_inventory = $this->filterHubsByInventory($filtered_hubs, $flag_inventory_check, $jo_type, $items, $jo_id, $customer_id); $filtered_hubs = $hubs_inventory; // round robin filter $hubs_round_robin = $this->filterHubsByRoundRobin($filtered_hubs, $flag_round_robin); $filtered_hubs = $hubs_round_robin; // max results filter $hubs_max_result = $this->filterHubsByMaxResults($filtered_hubs, $limit_results, $jo_id, $customer_id); $filtered_hubs = $hubs_max_result; } $results = $filtered_hubs; // error_log(json_encode($results)); return $results; } protected function filterHubsByRoundRobin($hubs, $flag_round_robin) { if (empty($hubs)) return $hubs; if (!$flag_round_robin) 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, $jo_id, $customer_id) { 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', $jo_id, $customer_id); } return $results; } protected function filterHubsByJoType($hubs, $jo_type, $jo_id, $customer_id) { 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'], 'jo_count' => 0, 'inventory' => $hub_data['inventory'], ]; else $this->hub_filter_logger->logFilteredHub($hub, 'job_order_type', $jo_id, $customer_id); } return $results; } protected function filterHubsByPaymentMethod($hubs, $payment_method, $jo_id, $customer_id) { 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'], 'jo_count' => 0, 'inventory' => $hub_data['inventory'], ]; } $flag_found_pmethod = true; } if (!$flag_found_pmethod) $this->hub_filter_logger->logFilteredHub($hub, 'no_payment_method', $jo_id, $customer_id); } else $this->hub_filter_logger->logFilteredHub($hub, 'no_payment_method', $jo_id, $customer_id); } return $results; } protected function filterHubsByDateAndTime($hubs, $date_time, $jo_id, $customer_id) { 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'], 'jo_count' => 0, 'inventory' => $hub_data['inventory'], ]; } else $this->hub_filter_logger->logFilteredHub($hub, 'date_and_time', $jo_id, $customer_id); } return $results; } protected function filterHubsByInventory($hubs, $flag_inventory_check, $jo_type, $items, $jo_id, $customer_id) { // check if this is enabled if (!$flag_inventory_check) { error_log("INVENTORY CHECK " . $jo_id . ": DISABLED"); return $hubs; } // check hub list is not empty if (empty($hubs)) { error_log("INVENTORY CHECK " . $jo_id . ": NO HUBS"); return $hubs; } // check item list is not empty if (empty($items)) { error_log("INVENTORY CHECK " . $jo_id . ": NO ITEMS"); return $hubs; } // check this is a battery item related JO if ($jo_type != ServiceType::BATTERY_REPLACEMENT_NEW && $jo_type != ServiceType::BATTERY_REPLACEMENT_WARRANTY ) { error_log("INVENTORY CHECK " . $jo_id . ": INVALID SERVICE TYPE: " . $jo_type); return $hubs; } // get a list of all hubs with branch codes $branch_codes = []; foreach ($hubs as $hub_data) { $branch_code = $hub_data['hub']->getBranchCode(); if (!empty($branch_code)) { $branch_codes[] = $branch_code; } }; $hubs_to_filter = []; $results = []; $qtys = []; // call inventory manager for all hubs for selected SKUs $skus = array_keys($items); error_log("CHECKING INVENTORY FOR " . count($skus) . " ITEM(S) ON HUBS " . count($branch_codes) . "..."); $branches = $this->im->getBranchesInventory($branch_codes, $skus); error_log("REQUEST COMPLETE, RESULT COUNT: " . count($branches)); // check each result to see if sufficient quantity exists to meet request foreach ($branches as $branch) { if (isset($branch['BranchCode'])) { // filter out branch if it does not have sufficient inventory if ($branch['Quantity'] < $items[$branch['SapCode']] && !isset($hubs_to_filter[$branch['BranchCode']]) ) { error_log("FILTERING BRANCH WITH NO INVENTORY: " . $branch['BranchCode']); $hubs_to_filter[$branch['BranchCode']] = true; } else { // save inventory count so we don't have to recheck later $qtys[$branch['BranchCode']] = $branch['Quantity']; } } } // remove filtered hubs from list foreach ($hubs as $hub_data) { $hub_obj = $hub_data['hub']; $branch_code = $hub_data['hub']->getBranchCode(); // check if we are filtering this hub if (isset($hubs_to_filter[$branch_code])) { // send SMS to hub $this->sendSMSMessage($hub_obj, $items); // log this filter $this->hub_filter_logger->logFilteredHub($hub_obj, 'no_inventory', $jo_id, $customer_id); } else if (!empty($branch_code) && isset($qtys[$branch_code])) { // include inventory in hub data $hub_data['inventory'] = $qtys[$branch_code]; // we only include branches with branch codes and quantities $results[] = $hub_data; } } // return filtered hubs return $results; // NOTE: leaving the old code here for posterity, for now /* 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'], 'jo_count' => 0, ]; 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', $jo_id, $customer_id); } } 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'], 'jo_count' => 0, ]; 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', $jo_id, $customer_id); } } } } return $results; */ } protected function filterHubsByRiderAvailability($hubs, $flag_riders_check, $jo_type, $jo_id, $customer_id) { // check if this is enabled if (!$flag_riders_check) { return $hubs; } // check hub list is not empty if (empty($hubs)) { return $hubs; } $results = []; foreach ($hubs as $hub_data) { $hub_obj = $hub_data['hub']; // check we have available riders if ($hub_obj->getAvailableRiders() === 0) { // send SMS to hub $this->rt->sendSMS( $hub_obj->getNotifNumber(), $this->trans->trans('message.battery_brand_allcaps'), $this->trans->trans('message.no_riders_message') ); // log this filter $this->hub_filter_logger->logFilteredHub($hub_obj, 'no_available_rider', $jo_id, $customer_id); error_log("FILTERED HUB " . $hub_obj->getID() . " (no_available_rider)"); } else { $results[] = $hub_data; } } return $results; } protected function getClosestHubs(Point $point, $limit_distance, $jo_id, $customer_id) { // 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, 'jo_count' => 0, 'inventory' => 0, ]; } else { $this->hub_filter_logger->logFilteredHub($row[0], 'not_within_distance', $jo_id, $customer_id); } } return $hubs_data; } protected function checkInventory($items, $branch_codes) { // check if hub has all items $skus = []; $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, $this->trans->trans('message.battery_brand_allcaps'), $message); } } }