diff --git a/src/Controller/APIController.php b/src/Controller/APIController.php index eec92bf7..050d1bc5 100644 --- a/src/Controller/APIController.php +++ b/src/Controller/APIController.php @@ -989,117 +989,209 @@ 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)) + // check if advance order + // check for time when request came in + $request_time_in_int = time(); + $start = '17:00'; + $end = '08:00'; + + $start_time = strtotime($start); + $end_time = strtotime($end); + + if (($request_time_in_int > $start_time) || + (($request_time_in_int < $start_time) && ($request_time_in_int < $end_time))) { - // get nearest hub - // $nearest_hub = $this->findNearestHubWithInventory($jo, $batt, $em, $map_tools, $im); - $nearest_hub = $this->findNearestHub($jo, $em, $map_tools); + // advance order + // 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); + // TODO: when the inventory manager kicks in, create new function that finds the + // the nearest hub with inventory and rider slot + // convert to DateTime + $nearest_hub = $this->findAdvanceNearestHub($jo, $request_time_in_int, $em, $map_tools); + } + else + { + $nearest_hub = $this->findAdvanceNearestHub($jo, $request_time_in_int, $em, $map_tools); + } + + if (!empty($nearest_hub)) + { + $jo->setHub($nearest_hub); + $jo->setStatus(JOStatus::ASSIGNED); + } + + $em->persist($jo); + $em->persist($invoice); + + // add event log for JO + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CREATE) + ->setJobOrder($jo); + $em->persist($event); + + // check JO status + if ($jo->getStatus() == JOStatus::ASSIGNED) + { + // add event logs for hub and rider assignments + $hub_assign_event = new JOEvent(); + $hub_assign_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($jo); + + $em->persist($hub_assign_event); + } + + $em->flush(); + + // make invoice json data + $invoice_data = [ + 'total_price' => $invoice->getTotalPrice(), + 'vat_ex_price' => $invoice->getVATExclusivePrice(), + 'vat' => $invoice->getVAT(), + 'discount' => $invoice->getDiscount(), + 'trade_in' => $invoice->getTradeIn(), + ]; + $items = $invoice->getItems(); + $items_data = []; + foreach ($items as $item) + { + $items_data[] = [ + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity() + 0, + 'price' => $item->getPrice() + 0.0, + ]; + } + $invoice_data['items'] = $items_data; + + // make job order data + $data = [ + 'jo_id' => $jo->getID(), + 'invoice' => $invoice_data + ]; + + // set data + $res->setData($data); + } else { - $nearest_hub = $this->findNearestHub($jo, $em, $map_tools); - } - - if (!empty($nearest_hub)) - { - // assign rider - $available_riders = $nearest_hub->getAvailableRiders(); - if (count($available_riders) > 0) + // assign hub and rider + if (($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) || + ($jo->getServicetype() == ServiceType::BATTERY_REPLACEMENT_WARRANTY)) { - $assigned_rider = null; - 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) - { - $riders[] = $rider; - } - - $assigned_rider = $this->randomizeRider($riders); - } - else - $assigned_rider = $available_riders[0]; - - $jo->setHub($nearest_hub); - $jo->setRider($assigned_rider); - $jo->setStatus(JOStatus::ASSIGNED); - - $assigned_rider->setAvailable(false); + // 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); } - } - $em->persist($jo); - $em->persist($invoice); + if (!empty($nearest_hub)) + { + // assign rider + $available_riders = $nearest_hub->getAvailableRiders(); + if (count($available_riders) > 0) + { + $assigned_rider = null; + 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) + { + $riders[] = $rider; + } - // add event log for JO - $event = new JOEvent(); - $event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::CREATE) - ->setJobOrder($jo); - $em->persist($event); + $assigned_rider = $this->randomizeRider($riders); + } + else + $assigned_rider = $available_riders[0]; + + $jo->setHub($nearest_hub); + $jo->setRider($assigned_rider); + $jo->setStatus(JOStatus::ASSIGNED); - // check JO status - if ($jo->getStatus() == JOStatus::ASSIGNED) - { - // add event logs for hub and rider assignments - $hub_assign_event = new JOEvent(); - $hub_assign_event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::HUB_ASSIGN) + $assigned_rider->setAvailable(false); + } + } + + $em->persist($jo); + $em->persist($invoice); + + // add event log for JO + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CREATE) ->setJobOrder($jo); + $em->persist($event); - $em->persist($hub_assign_event); + // check JO status + if ($jo->getStatus() == JOStatus::ASSIGNED) + { + // add event logs for hub and rider assignments + $hub_assign_event = new JOEvent(); + $hub_assign_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($jo); - $rider_assign_event = new JOEvent(); - $rider_assign_event->setDateHappen(new DateTime()) - ->setTypeID(JOEventType::RIDER_ASSIGN) - ->setJobOrder($jo); + $em->persist($hub_assign_event); - $em->persist($rider_assign_event); + $rider_assign_event = new JOEvent(); + $rider_assign_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ASSIGN) + ->setJobOrder($jo); - // user mqtt event - $payload = [ - 'event' => 'outlet_assign' + $em->persist($rider_assign_event); + + // user mqtt event + $payload = [ + 'event' => 'outlet_assign' + ]; + $mclient->sendEvent($jo, $payload); + + $rah->assignJobOrder($jo, $jo->getRider()); + } + + $em->flush(); + + // make invoice json data + $invoice_data = [ + 'total_price' => $invoice->getTotalPrice(), + 'vat_ex_price' => $invoice->getVATExclusivePrice(), + 'vat' => $invoice->getVAT(), + 'discount' => $invoice->getDiscount(), + 'trade_in' => $invoice->getTradeIn(), ]; - $mclient->sendEvent($jo, $payload); + $items = $invoice->getItems(); + $items_data = []; + foreach ($items as $item) + { + $items_data[] = [ + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity() + 0, + 'price' => $item->getPrice() + 0.0, + ]; + } + $invoice_data['items'] = $items_data; - $rah->assignJobOrder($jo, $jo->getRider()); - } - - $em->flush(); - - // make invoice json data - $invoice_data = [ - 'total_price' => $invoice->getTotalPrice(), - 'vat_ex_price' => $invoice->getVATExclusivePrice(), - 'vat' => $invoice->getVAT(), - 'discount' => $invoice->getDiscount(), - 'trade_in' => $invoice->getTradeIn(), - ]; - $items = $invoice->getItems(); - $items_data = []; - foreach ($items as $item) - { - $items_data[] = [ - 'title' => $item->getTitle(), - 'qty' => $item->getQuantity() + 0, - 'price' => $item->getPrice() + 0.0, + // make job order data + $data = [ + 'jo_id' => $jo->getID(), + 'invoice' => $invoice_data ]; + + // set data + $res->setData($data); } - $invoice_data['items'] = $items_data; - - // make job order data - $data = [ - 'jo_id' => $jo->getID(), - 'invoice' => $invoice_data - ]; - - // set data - $res->setData($data); return $res->getReturnResponse(); } @@ -2464,4 +2556,306 @@ class APIController extends Controller implements LoggedController return $selected_rider; } + protected function findAdvanceNearestHub($jo, $request_time, EntityManagerInterface $em, MapTools $map_tools) + { + // get the nearest 10 hubs + $selected_hub = null; + $hubs = $map_tools->getClosestOpenHubs($jo->getCoordinates(), 10); + + $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; + $slot_found = false; + foreach ($nearest_hubs_with_distance as $nhd) + { + if (empty($nearest)) + $nearest = $nhd; + else + { + if ($nhd['distance'] < $nearest['distance']) + $nearest = $nhd; + } + + $result = $this->findRiderSlots($jo, $nearest['hub'], $request_time, $em); + + if ($result != null) + $slot_found = $result['slot_found']; + else + $nearest = null; + } + + if ($slot_found && $nearest != null) + { + $result = $this->findRiderSlots($jo, $nearest['hub'], $request_time, $em); + + if ($result != null) + { + $jo_date_schedule = $result['date_schedule']; + + $jo->setAdvanceOrder(true); + $jo->setDateSchedule($jo_date_schedule); + + $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 = $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; + } + + protected function findRiderSlots($jo, Hub $hub, $request_time, EntityManagerInterface $em) + { + $flag_found_slot = false; + $results = []; + + // array of # of riders that can handle JOs in a timeslot + $hub_rider_slots = []; + + // populate the array with the hub's rider slots + for ($i = 0; $i <=7; $i++) + { + $hub_rider_slots[$i] = $hub->getRiderSlots(); + } + + // 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 + $midnight = strtotime('00:00'); + $start_date = new DateTime(); + $end_date = new DateTime(); + + if ($request_time < $midnight) + { + // add +1 to date to start and end date + $start_date->add(new DateInterval('P1D')); + $end_date->add(new DateInterval('P1D')); + } + + // set time bounds for the start and end date + $start_date->setTime(0, 1); + $end_date->setTime(23, 59); + + $str_request_time = date('Y-m-d H:i:s', $request_time); + $time_of_request = DateTime::createFromFormat('Y-m-d H:i:s', $str_request_time); + + // NOTE: get advance orders via query + // get JOs assigned to hub that are advance orders and scheduled for the next day(if before midnight) or current day(if after midnight) with + // for hub assignment status + $query = $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'); + $jos_advance_orders = $query->setParameters([ + 'hub' => $hub, + 'date_start' => $start_date, + 'date_end' => $end_date, + 'status' => JOStatus::ASSIGNED, + ]) + ->getResult(); + + // check each JO's date_schedule, decrement rider_slots if date schedule falls in that slot + // index - equivalent time + // 0 - 8-9 + // 1 - 9-10 + // 2 - 10-11 + // 3 - 11-12 + // 4 - 12 -13 + // 5 - 13-14 + // 6 - 14-15 + // 7 - 15-16 + foreach ($jos_advance_orders as $advance_jo) + { + // get time schedule + $date_schedule = $advance_jo->getDateSchedule(); + $time_schedule = $date_schedule->format('H:i'); + + $hour_schedule = $date_schedule->format('H'); + $minute_schedule = $date_schedule->format('i'); + + // check minutes. for now, if not 00, take up current slot and next one + // if hour fits in the last slot but minutes is not 00, move to the next available hub + // TODO: need to add custom minutes threshold + if ($minute_schedule != '00') + { + switch($hour_schedule) { + case '8': + $hub_rider_slots[0]--; + $hub_rider_slots[1]--; + break; + case '9': + $hub_rider_slots[1]--; + $hub_rider_slots[2]--; + break; + case '10': + $hub_rider_slots[2]--; + $hub_rider_slots[3]--; + break; + case '11': + $hub_rider_slots[3]--; + $hub_rider_slots[4]--; + break; + case '12': + $hub_rider_slots[4]--; + $hub_rider_slots[5]--; + break; + case '13': + $hub_rider_slots[5]--; + $hub_rider_slots[6]--; + break; + case '14': + $hub_rider_slots[6]--; + $hub_rider_slots[7]--; + break; + case '15': + error_log('No slots for the day'); + break; + default: + error_log('Does not fit in any time slot. ' . $time_schedule); + } + } + else + { + switch ($hour_schedule) { + case '8': + $hub_rider_slots[0]--; + break; + case '9': + $hub_rider_slots[1]--; + break; + case '10': + $hub_rider_slots[2]--; + break; + case '11': + $hub_rider_slots[3]--; + break; + case '12': + $hub_rider_slots[4]--; + break; + case '13': + $hub_rider_slots[5]--; + break; + case '14': + $hub_rider_slots[6]--; + break; + case '15': + $hub_rider_slots[7]--; + break; + default: + error_log('Does not fit in any time slot.' . $time_schedule); + } + } + + } + + // find first free slot + foreach ($hub_rider_slots as $index => $rider_slot) + { + if ($rider_slot > 0) + { + $flag_found_slot = true; + + $jo_date_schedule = $start_date; + switch ($index) { + case '0': + $jo_date_schedule->setTime(8,0); + break; + case '1': + $jo_date_schedule->setTime(9,0); + break; + case '2': + $jo_date_schedule->setTime(10,0); + break; + case '3': + $jo_date_schedule->setTime(11,0); + break; + case '4': + $jo_date_schedule->setTime(12,0); + break; + case '5': + $jo_date_schedule->setTime(13,0); + break; + case '6': + $jo_date_schedule->setTime(14,0); + break; + case '7': + $jo_date_schedule->setTime(15,0); + break; + default: + error_log('Cannot find time slot.'); + } + + $results = [ + 'slot_found' => $flag_found_slot, + 'date_schedule' => $jo_date_schedule, + ]; + + break; + } + } + return $results; + } } diff --git a/src/Controller/HubController.php b/src/Controller/HubController.php index d6334c80..5c8adb04 100644 --- a/src/Controller/HubController.php +++ b/src/Controller/HubController.php @@ -152,7 +152,8 @@ class HubController extends Controller ->setTimeClose($time_close) ->setCoordinates($point) ->setBranchCode($req->request->get('branch_code', '')) - ->setStatusOpen($req->request->get('status_open') ?? false); + ->setStatusOpen($req->request->get('status_open') ?? false) + ->setRiderSlots($req->request->get('rider_slots', 0)); } protected function setQueryFilters($datatable, QueryBuilder $query) diff --git a/src/Entity/Hub.php b/src/Entity/Hub.php index f92a424e..9edcb88a 100644 --- a/src/Entity/Hub.php +++ b/src/Entity/Hub.php @@ -56,6 +56,12 @@ class Hub */ protected $status_open; + // number of rider slots per day + /** + * @ORM\Column(type="integer") + */ + protected $rider_slots; + public function __construct() { $this->time_open = new DateTime(); @@ -150,4 +156,15 @@ class Hub return $this->status_open; } + public function setRiderSlots($rider_slots) + { + $this->rider_slots = $rider_slots; + return $this; + } + + public function getRiderSlots() + { + return $this->rider_slots; + } + } diff --git a/templates/hub/form.html.twig b/templates/hub/form.html.twig index aed63021..b47b546d 100644 --- a/templates/hub/form.html.twig +++ b/templates/hub/form.html.twig @@ -90,7 +90,14 @@
-
+
+ + + +
+