validateRequest($req, [ 'longitude', 'latitude', ]); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } $long = $req->query->get('longitude'); $lat = $req->query->get('latitude'); // NOTE: had to add this for promo tag $cust = $this->session->getCustomer(); if ($cust == null) { return new ApiResponse(false, 'No customer information found.'); } $is_covered = false; // check if customer still has promo if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) || ($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')) ) { // if has customer tag, customer has not availed of promo $is_covered = true; } else { // geofence $is_covered = $geo->isCovered($long, $lat); } // geofence // $is_covered = $geo->isCovered($long, $lat); $data = [ 'longitude' => $long, 'latitude' => $lat, 'supported' => $is_covered, ]; // check if is_covered is false. If so, we need to set the error part in the response if (!$is_covered) { return new ApiResponse(false, $this->getGeoErrorMessage(), $data); } // response return new ApiResponse(true, '', $data); } public function getNearestHubAndSlots( Request $req, MapTools $map_tools ) { // validate params $validity = $this->validateRequest($req, [ 'longitude', 'latitude', ]); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } $coordinates = new Point($req->query->get('longitude'), $req->query->get('latitude')); // add checking if customer has a pre-registered hub $cust = $this->session->getCustomer(); if ($cust == null) { return new ApiResponse(false, 'No customer information found.'); } // check if customer has customer tag promo if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) || ($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')) ) { // if has customer tag, customer has not availed of promo, get the hub where customer is pre-registered $car_club_cust_hub = $cust->getCarClubCustomerHub(); if ($car_club_cust_hub != null) { // need to get the rider slots for the pre-registered hub $hub = $car_club_cust_hub->getHub(); $nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $map_tools, $hub); } else { $nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $map_tools); if (empty($nearest_hub_slots['hub'])) { return new ApiResponse(false, 'Thank you for reaching out to us. Please expect a call from us and we will assist you with your request. Thank you and stay safe!'); } } } else { $nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $map_tools); if (empty($nearest_hub_slots['hub'])) { return new ApiResponse(false, 'Thank you for reaching out to us. Please expect a call from us and we will assist you with your request. Thank you and stay safe!'); } } // response return new ApiResponse(true, '', [ 'hub_id' => $nearest_hub_slots['hub']->getID(), 'hub_slots' => $nearest_hub_slots['slots'], ]); } public function addLocation(Request $req) { // validate params $validity = $this->validateRequest($req, [ 'name', 'address', 'longitude', 'latitude', 'landmark', ]); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // get customer $cust = $this->session->getCustomer(); if ($cust == null) { return new ApiResponse(false, 'No customer information found.'); } // get the information $name = $req->request->get('name'); $address = $req->request->get('address'); $lng = $req->request->get('longitude'); $lat = $req->request->get('latitude'); $landmark = $req->request->get('landmark'); $loc_info = [ 'address' => $address, 'longitude' => $lng, 'latitude' => $lat, 'landmark' => $landmark, ]; // check if customer already has existing metadata $c_meta = $this->em->getRepository(CustomerMetadata::class)->findOneBy(['customer' => $cust]); if ($c_meta == null) { // create new customer meta $cust_meta = new CustomerMetadata(); $cust_meta->setCustomer($cust); $cust_meta->addMetaInfo($name, $loc_info); $this->em->persist($cust_meta); } else { // limit locations to 6. If more than 6, pop the first one out // add location to existing customer meta $meta_count = count($c_meta->getAllMetaInfo()); if ($meta_count >= 6) $c_meta->popMetaInfo(); $c_meta->addMetaInfo($name, $loc_info); } $this->em->flush(); // response return new ApiResponse(); } public function removeLocation($id, Request $req) { // validate params $validity = $this->validateRequest($req); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // get customer $cust = $this->session->getCustomer(); if ($cust == null) { return new ApiResponse(false, 'No customer information found.'); } // find customer metadata and delete entry if present $cv = $this->em->getRepository(CustomerMetadata::class)->findOneBy(['customer' => $cust]); if ($cv != null) { $cv->deleteMetadataInfo(base64_decode($id)); } $this->em->flush(); // response return new ApiResponse(); } public function getLocations(Request $req) { // validate params $validity = $this->validateRequest($req); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // get customer $cust = $this->session->getCustomer(); if ($cust == null) { return new ApiResponse(false, 'No customer information found.'); } // get the customer meta for customer $locations = []; $cust_meta = $this->em->getRepository(CustomerMetadata::class)->findOneBy(['customer' => $cust]); if ($cust_meta != null) { $locations = $cust_meta->getAllMetaInfo(); } $data = [ 'locations' => $locations, ]; // response return new ApiResponse(true, '', $data); } protected function findAdvanceNearestHubAndSlots(Point $coordinates, MapTools $map_tools, $hub = null) { $hub_data = []; if ($hub != null) { // get the slots of hub $hub_slots = $this->getHubRiderSlots($hub); $slots = $hub_slots['slot_data']; $hub_data = [ 'hub' => $hub, 'slots' => $slots, ]; return $hub_data; } // get the nearest 10 hubs $nearest_hubs_with_distance = []; $hubs = $map_tools->getClosestOpenHubs($coordinates, 10); foreach ($hubs as $hub) { $nearest_hubs_with_distance[] = $hub; // TODO: insert checking for branch code here when inventory manager is up } $nearest = null; $hub_slots = []; $slot_found = false; // find the nearest hub if (!empty($nearest_hubs_with_distance)) { // get slots of nearest hub right after getting nearest hub. // then check if hub has available slots. If not, get next nearest hub. foreach ($nearest_hubs_with_distance as $nhd) { if (empty($nearest)) { // get the slots for the hub to check if hub is available for assignment $hub_slots = $this->getHubRiderSlots($nhd['hub']); $flag_hub_available = $hub_slots['flag_hub_available']; if ($flag_hub_available == true) { $nearest = $nhd; } } else { if ($nhd['distance'] < $nearest['distance']) { // get the slots for nearest which is nhd right now $hub_slots = $this->getHubRiderSlots($nhd['hub']); $flag_hub_available = $hub_slots['flag_hub_available']; // if hub is available, set hub to nearest if ($flag_hub_available == true) { $nearest = $nhd; } } } } } if ($nearest != null) { // set hub data to what is in nearest $hub_data = [ 'hub' => $nearest['hub'], 'slots' => $hub_slots['slot_data'], ]; } return $hub_data; } protected function getHubRiderSlots(Hub $hub) { // check hub's advance orders for the day /* // get number of advance orders for the next day if request came in before midnight // or for current day if request came in after midnight // check request_time $request_time = time(); $midnight = strtotime('00:00'); */ $start_date = new DateTime(); $end_date = new DateTime(); // to keep things simple, just start on next day regardless of midnight timer $start_date->add(new DateInterval('P1D')); $end_date->add(new DateInterval('P3D')); /* if ($request_time < $midnight) { // add +1 to start date to get the next day // add +3 to date to end date to get the advance orders for the next three days $start_date->add(new DateInterval('P1D')); $end_date->add(new DateInterval('P1D')); } $end_date->add(new DateInterval('P2D')); */ // set time bounds for the start and end date $start_date->setTime(0, 1); $end_date->setTime(23, 59); // NOTE: get advance orders via query // get JOs assigned to hub that are advance orders and scheduled for the next three days with // for hub assignment status $query = $this->em->createQuery('select jo from App\Entity\JobOrder jo where jo.hub = :hub and jo.flag_advance = true and jo.date_schedule >= :date_start and jo.date_schedule <= :date_end and jo.status != :status_cancelled and jo.status != :status_fulfilled'); $jos_advance_orders = $query->setParameters([ 'hub' => $hub, 'date_start' => $start_date, 'date_end' => $end_date, 'status_cancelled' => JOStatus::CANCELLED, 'status_fulfilled' => JOStatus::FULFILLED, ]) ->getResult(); // check request_time // define slots $slots = [ '08_09' => '8:00 AM', '09_10' => '9:00 AM', '10_11' => '10:00 AM', '11_12' => '11:00 AM', '12_13' => '12:00 PM', '13_14' => '1:00 PM', '14_15' => '2:00 PM', '15_16' => '3:00 PM', '16_17' => '4:00 PM', ]; // get the dates for the next three days $first_date = $start_date->format('Y-m-d'); $second_date = $start_date->add(new DateInterval('P1D')); $sec_date = $second_date->format('Y-m-d'); $third_date = $end_date->format('Y-m-d'); // define days $days = [ $first_date => $first_date, $sec_date => $sec_date, $third_date => $third_date, ]; // initialize hub rider slots $hub_rider_slots = []; foreach ($days as $day) { foreach ($slots as $slot_key => $slot) { $hub_rider_slots[$day][$slot_key] = $hub->getRiderSlots(); } } // check each JO's date_schedule, decrement rider_slots if date schedule falls in that slot foreach ($jos_advance_orders as $jo) { // get date key $date_sched = $jo->getDateSchedule(); $date_string = $date_sched->format('Y-m-d'); $hour = $date_sched->format('H'); $slot_id = sprintf('%02d_%02d', $hour, $hour + 1); // error_log("SLOT - $date_string - $slot_id"); // decrement rider slot if (isset($hub_rider_slots[$date_string][$slot_id])) $hub_rider_slots[$date_string][$slot_id]--; // check if it goes through next slot (10 min allowance) $mins = $date_sched->format('i'); if ($mins > 10) { $next_slot_id = sprintf('%02d_%02d', $hour + 1, $hour + 2); // error_log("NEXT SLOT - $date_string - $next_slot_id"); // decrement rider slot if (isset($hub_rider_slots[$date_string][$next_slot_id])) $hub_rider_slots[$date_string][$next_slot_id]--; } } // error_log(print_r($hub_rider_slots, true)); $hub_slots = $this->generateHubSlots($hub_rider_slots, $slots); // error_log(print_r($hub_slots, true)); return $hub_slots; } protected function generateHubSlots($rider_slots, $slots) { $data = []; $total_rslots = 0; $total_unavailable_rslots = 0; foreach ($rider_slots as $day_id => $rslot) { $data[$day_id] = []; foreach ($rslot as $slot_id => $avail_slots) { // increment total rider slots $total_rslots++; $slot_data = [ 'id' => $slot_id, 'label' => $slots[$slot_id], 'available' => true, ]; // mark unavailable ones if ($avail_slots <= 0) { // increment total number of unavailable slots $total_unavailable_rslots++; $slot_data['available'] = false; } // add to day data $data[$day_id][] = $slot_data; } } // check if hub has available slots $hub_available = true; // error_log('total rider slots ' . $total_rslots); // error_log('total unavailable slots ' . $total_unavailable_rslots); if ($total_rslots == $total_unavailable_rslots) { // error_log('hub has no available slots'); $hub_available = false; } $hs_data = [ 'flag_hub_available' => $hub_available, 'slot_data' => $data, ]; return $hs_data; } protected function findNearestHub($jo, MapTools $map_tools) { // get the nearest 10 hubs $selected_hub = null; $hubs = $map_tools->getClosestOpenHubs($jo->getCoordinates(), 10, date("H:i:s")); $nearest_hubs_with_distance = []; $nearest_branch_codes = []; foreach ($hubs as $hub) { $nearest_hubs_with_distance[] = $hub; //if (!empty($hub['hub']->getBranchCode())) // $nearest_branch_codes[] = $hub['hub']->getBranchCode(); } // check if nearest hubs have branch codes //if (count($nearest_branch_codes) == 0) // return $selected_hub; // assume all 10 have stock // find the nearest hub with available riders $nearest = null; foreach ($nearest_hubs_with_distance as $nhd) { // get number of available riders $count_riders = count($nhd['hub']->getAvailableRiders()); // get number of advance orders in the next 3 hours $time_now = new DateTime(); $date_end = new DateTime(); $date_end->add(new DateInterval('PT2H')); // NOTE: get advance orders via query // get JOs assigned to hub that are advance orders and scheduled within X hours with // for rider assignment status $query = $this->em->createQuery('select count(jo) from App\Entity\JobOrder jo where jo.hub = :hub and jo.flag_advance = true and jo.date_schedule <= :date_end and jo.status = :status'); $count_advance_orders = $query->setParameters([ 'hub' => $nhd['hub'], 'date_end' => $date_end, 'status' => JOStatus::RIDER_ASSIGN, ]) ->setMaxResults(1) ->getSingleScalarResult(); // error_log('HUB - ' . $nhd['hub']->getID()); // error_log('RIDER COUNT - ' . $count_riders); // error_log('ADVANCE ORDER COUNT - ' . $count_advance_orders); // if (count($nhd['hub']->getAvailableRiders()) > 0) // if we have more riders than we have advance orders if ($count_riders - $count_advance_orders > 0) { if (empty($nearest)) $nearest = $nhd; else { if ($nhd['distance'] < $nearest['distance']) $nearest = $nhd; } } } $selected_hub = $nearest['hub']; return $selected_hub; } protected function findNearestHubWithInventory( $jo, Battery $batt, MapTools $map_tools, InventoryManager $im ) { // get the nearest 10 hubs $selected_hub = null; $hubs = $map_tools->getClosestOpenHubs($jo->getCoordinates(), 10, date("H:i:s")); $nearest_hubs_with_distance = []; $nearest_branch_codes = []; foreach ($hubs as $hub) { $nearest_hubs_with_distance[] = $hub; //if (!empty($hub['hub']->getBranchCode())) // $nearest_branch_codes[] = $hub['hub']->getBranchCode(); } // check if nearest hubs have branch codes //if (count($nearest_branch_codes) == 0) // return $selected_hub; // assume all 10 have stock // find the nearest hub with available riders $nearest = null; foreach ($nearest_hubs_with_distance as $nhd) { if (count($nhd['hub']->getAvailableRiders()) > 0) { if (empty($nearest)) $nearest = $nhd; else { if ($nhd['distance'] < $nearest['distance']) $nearest = $nhd; } } } $selected_hub = $nearest['hub']; // uncomment this snippet when inventory check becomes active // get battery sku /* if ($batt != null) { $skus[] = $batt->getSAPCode(); // api call to check inventory // pass the list of branch codes of nearest hubs and the skus // go through returned list of branch codes // bypass inventory check for now // $hubs_with_inventory = $im->getBranchesInventory($nearest_branch_codes, $skus); if (!empty($hubs_with_inventory)) { $nearest = []; $flag_hub_found = false; foreach ($hubs_with_inventory as $hub_with_inventory) { // find hub according to branch code $found_hub = $this->em->getRepository(Hub::class)->findOneBy(['branch_code' => $hub_with_inventory['BranchCode']]); if ($found_hub != null) { // check rider availability if (count($found_hub->getAvailableRiders()) > 0) { // check against nearest hubs with distance foreach ($nearest_hubs_with_distance as $nhd) { // get distance of hub from location, compare with $nearest. if less, replace nearest if ($found_hub->getID() == $nhd['hub']->getID()) { if (empty($nearest)) { $nearest = $nhd; $flag_hub_found = true; } else { if ($nhd['distance'] < $nearest['distance']) { $nearest = $nhd; $flag_hub_found = true; } } } } } } } $selected_hub = $nearest['hub']; } } */ return $selected_hub; } }