From 9a661763a00dc7045554b059d52aace6ddd9c7cc Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Mon, 6 Apr 2020 09:46:54 +0000 Subject: [PATCH 01/21] Add auto assign to controller. #374 --- config/acl.yaml | 2 + config/menu.yaml | 4 + config/routes/job_order.yaml | 9 + src/Controller/JobOrderController.php | 164 +- .../JobOrderHandler/ResqJobOrderHandler.php | 9 +- templates/job-order/autoassign.form.html.twig | 1774 +++++++++++++++++ 6 files changed, 1959 insertions(+), 3 deletions(-) create mode 100644 templates/job-order/autoassign.form.html.twig diff --git a/config/acl.yaml b/config/acl.yaml index c6ccc243..bfd6c6cc 100644 --- a/config/acl.yaml +++ b/config/acl.yaml @@ -264,6 +264,8 @@ access_keys: label: Walk-in - id: jo_walkin.edit label: Walk-in Edit + - id: jo_autoassign.test + label: Autoassign Test - id: support label: Customer Support Access diff --git a/config/menu.yaml b/config/menu.yaml index b0096cf5..45ceab40 100644 --- a/config/menu.yaml +++ b/config/menu.yaml @@ -122,6 +122,10 @@ main_menu: acl: jo_all.list label: View All parent: joborder + - id: jo_autoassign + acl: jo_autoassign.test + label: Autoassign Test + parent: joborder - id: support acl: support.menu diff --git a/config/routes/job_order.yaml b/config/routes/job_order.yaml index d5cd8dfc..f239402d 100644 --- a/config/routes/job_order.yaml +++ b/config/routes/job_order.yaml @@ -226,3 +226,12 @@ jo_walkin_edit_submit: controller: App\Controller\JobOrderController::walkInEditSubmit methods: [POST] +jo_autoassign: + path: /job-order/autoassign + controller: App\Controller\JobOrderController::autoAssignForm + methods: [GET] + +jo_autoassign_test_submit: + path: /job-order/autoassign + controller: App\Controller\JobOrderController::autoAssignSubmit + methods: [POST] diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index 1d1d84c1..cdb2d665 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -5,6 +5,7 @@ namespace App\Controller; use App\Ramcar\JOStatus; use App\Ramcar\InvoiceCriteria; use App\Ramcar\CMBServiceType; +use App\Ramcar\ServiceType; use App\Entity\CustomerVehicle; use App\Entity\Promo; @@ -96,7 +97,9 @@ class JobOrderController extends Controller $this->denyAccessUnlessGranted('jo_open.edit', null, 'No access.'); $error_array = []; - $error_array = $jo_handler->generateJobOrder($req, $id); + $result = $jo_handler->generateJobOrder($req, $id); + + $error_array = $result['error_array']; // check if any errors were found if (!empty($error_array)) { @@ -145,7 +148,9 @@ class JobOrderController extends Controller // initialize error list $error_array = []; $id = -1; - $error_array = $jo_handler->generateJobOrder($req, $id); + $result = $jo_handler->generateJobOrder($req, $id); + + $error_array = $result['error_array']; // check if any errors were found if (!empty($error_array)) { @@ -1053,5 +1058,160 @@ class JobOrderController extends Controller ]); } + /** + * @Menu(selected="jo_autoassign") + */ + public function autoAssignForm(GISManagerInterface $gis, JobOrderHandlerInterface $jo_handler) + { + $this->denyAccessUnlessGranted('jo_autoassign.test', null, 'No access.'); + $params = $jo_handler->initializeIncomingForm(); + + $params['submit_url'] = $this->generateUrl('jo_autoassign_test_submit'); + $params['return_url'] = $this->generateUrl('jo_autoassign'); + $params['map_js_file'] = $gis->getJSJOFile(); + + $template = 'job-order/autoassign.form.html.twig'; + + // response + return $this->render($template, $params); + } + + public function autoAssignSubmit(Request $req, JobOrderHandlerInterface $jo_handler, + EntityManagerInterface $em, MapTools $map_tools) + { + $this->denyAccessUnlessGranted('jo_autoassign.test', null, 'No access.'); + + // initialize error list + $error_array = []; + $id = -1; + $result = $jo_handler->generateJobOrder($req, $id); + + $error_array = $result['error_array']; + + // check if any errors were found + if (!empty($error_array)) { + // return validation failure response + return $this->json([ + 'success' => false, + 'errors' => $error_array + ], 422); + } + + // get job order + $jo = $result['job_order']; + + if (($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) || + ($jo->getServicetype() == ServiceType::BATTERY_REPLACEMENT_WARRANTY)) + $this->autoAssignHubAndRider($jo, $em, $map_tools); + + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + + } + + protected function autoAssignHubAndRider($jo, EntityManagerInterface $em, MapTools $map_tools) + { + // get the nearest 10 hubs + // TODO: move this snippet to a function + $hubs = $map_tools->getClosestHubs($jo->getCoordinates(), 10, date("H:i:s")); + + $nearest_hubs = []; + $nearest_hubs_with_distance = []; + + foreach ($hubs as $hub) + { + $nearest_hubs_with_distance[] = $hub; + $nearest_hubs[] = $hub['hub']; + } + + // get battery sku + $invoice = $jo->getInvoice(); + if ($invoice != null) + { + $items = $invoice->getItems(); + $sku = ''; + foreach ($items as $item) + { + $sku = $item->getBattery()->getSAPCode(); + } + + // api call to check inventory + // pass the list of nearest hubs and the sku + // go through returned list of hubs for available riders + // for now, use all hubs + $hubs_with_inventory = $em->getRepository(Hub::class)->findAll(); + $nearest = []; + foreach ($hubs_with_inventory as $hub_with_inventory) + { + // check rider availability + if (count($hub_with_inventory->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 ($hub_with_inventory->getID() == $nhd['hub']->getID()) + { + if (empty($nearest)) + { + $nearest = $nhd; + } + else + { + if ($nhd['distance'] < $nearest['distance']) + $nearest = $nhd; + } + } + } + } + } + + // return $nearest + error_log('Nearest Hub ' . $nearest['hub']->getID()); + error_log('With distance ' . $nearest['distance']); + + $jo->setHub($nearest['hub']); + + $hub_riders = $nearest['hub']->getAvailableRiders(); + $rider = null; + if (count($hub_riders) > 1) + { + // this will no longer be necessary when the contents + // of randomizeRider changes + $available_riders = []; + foreach ($hub_riders as $rider) + { + $available_riders[] = $rider; + } + + $rider = $this->randomizeRider($available_riders); + } + else + $rider = $hub_riders[0]; + + $jo->setRider($rider); + + $jo->setStatus(JOStatus::ASSIGNED); + + $em->persist($jo); + $em->flush(); + + } + } + + protected function randomizeRider($riders) + { + // TODO: get redis to track the sales per rider per day + // check the time they came in + // for now, randomize the rider + $selected_index = array_rand($riders); + + $selected_rider = $riders[$selected_index]; + + return $selected_rider; + } } diff --git a/src/Service/JobOrderHandler/ResqJobOrderHandler.php b/src/Service/JobOrderHandler/ResqJobOrderHandler.php index a12e09d5..deeea114 100644 --- a/src/Service/JobOrderHandler/ResqJobOrderHandler.php +++ b/src/Service/JobOrderHandler/ResqJobOrderHandler.php @@ -405,7 +405,14 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } } - return $error_array; + $data['error_array'] = $error_array; + + if ($jo != null) + { + $data['job_order'] = $jo; + } + + return $data; } // dispatch job order diff --git a/templates/job-order/autoassign.form.html.twig b/templates/job-order/autoassign.form.html.twig new file mode 100644 index 00000000..bba70b39 --- /dev/null +++ b/templates/job-order/autoassign.form.html.twig @@ -0,0 +1,1774 @@ +{% extends 'base.html.twig' %} + +{% block body %} + +
+
+
+

Job Order

+
+
+
+ +
+ +
+
+
+
+
+
+ + + +

+ {% if mode == 'update-processing' %} + Dispatch + {{ obj.getID() }} + {% elseif mode == 'update-assigning' %} + Rider Assignment + {{ obj.getID() }} + {% elseif mode == 'update-reassign-hub' %} + Re-assign Hub + {{ obj.getID() }} + {% elseif mode == 'update-reassign-rider' %} + Re-assign Rider + {{ obj.getID() }} + {% elseif mode == 'update-all' %} + Viewing + {{ obj.getID() }} + {% else %} + Incoming + {% endif %} +

+
+
+
+
+ + + {% if mode in ['update-assigning', 'update-processing', 'update-reassign-hub', 'update-reassign-rider', 'update-all', 'open_edit'] %} + + {% endif %} +
+ + {% if ftags.vehicle_dropdown %} +
+
+
+ + + +
+ +
+
+
+
+
+ + + +
+
+
+ {% else %} + + {% endif %} + {% if obj.getReferenceJO %} +
+
+
+ + + +
+
+
+ {% endif %} + +
+
+

+ Customer Details +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+ +
+ {% trans %}country_code_prefix{% endtrans %} + + +
+
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+

+ Vehicle Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+

+ Battery Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+
+
+

+ Transaction Details +

+ + + +
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ +
+ + + + +
+ +
+
+ +
+ + + + +
+ +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+ + + +
+
+
+ + +
+
+
+
+ + + +
+
+
+ + + +
+
+
+ + + +
+
+
+
+
+
+ +
+ + +
+
+
+
+
+ + + +
+
+
+ +
+
+
+
+

+ Location +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + + +
+ + + + +
+
+
+
+
+
+
+
+

+ Invoice +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + {% if ftags.invoice_edit %} + + + {% else %} + + {% endif %} +
+
+ + +
+
+ + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ + + + + + + + + + + + {% if not obj.getInvoice or (obj.getInvoice and obj.getInvoice.getItems|length == 0) %} + + + + {% else %} + {% for item in obj.getInvoice.getItems %} + + + + + + + {% endfor %} + {% endif %} + +
ItemQuantityUnit PriceAmount
+ No items to display. +
{{ item.getTitle }}{{ item.getQuantity|number_format }}{{ item.getPrice|number_format(2) }}{{ (item.getPrice * item.getQuantity)|number_format(2) }}
+
+
+ {% if ftags.invoice_edit %} +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + +
+
+ {% endif %} +
+ + {% if mode in ['update-processing', 'update-reassign-hub'] %} +
+
+
+

+ Nearest Hubs +

+
+
+
+ + +
+ + + + + + + + + + + + + + + + + + {% for hub in hubs %} + + + + + + + + + + {% endfor %} + +
HubBranchAvailable RidersJobs For AssignmentContact NumbersAction
+ No items to display. +
{{ hub.hub.getName }}{{ hub.hub.getBranch }}{{ hub.rider_count }}{{ hub.jo_count }}{{ hub.hub.getContactNumbers|replace({"\n": ', '}) }} + {% if hub.flag_rejected %} + + {% else %} + + {% endif %} +
+
+
+
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+
+
+
+
+
+ {% endif %} + + {% if mode in ['update-assigning', 'update-fulfillment', 'update-reassign-rider', 'update-all'] %} +
+ {% if obj.getHub %} +
+
+

+ Hub Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+ {% endif %} + +
+ + {% if mode in ['update-assigning', 'update-reassign-rider'] %} +
+
+

+ Rider Assignment +

+
+
+
+ + +
+ + + + + + + + + + + + + {% set avail_riders = obj.getHub.getAvailableRiders|default([]) %} + + + + + {% if obj.getHub %} + {% for rider in avail_riders %} + + + + + + + + + {% endfor %} + {% endif %} + +
First NameLast NameContact No.Plate NumberStatus
+ No riders available. +
+
+
{{ rider.getFirstName }}{{ rider.getLastName }}{{ rider.getContactNumber }}{{ rider.getPlateNumber }}
+
+
+
+
+ {% endif %} + + {% if mode in ['update-fulfillment', 'update-all'] %} + {% if obj.getRider %} +
+
+

+ Rider Details +

+
+
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+ +
+
+
+
+ {% endif %} + {% endif %} + {% endif %} + + {% if mode == 'update-all' %} +
+ +
+
+

+ Timeline +

+
+
+
+
+
+ {% for event in obj.getEvents %} +
+ + {{ event.getDateHappen|date("M j, Y") }} +
{{ event.getDateHappen|date("h:i:s a") }}
+
+
+ +
+
+ {{ event.getTypeName }} by {{ event.getUser.getFullName|default('Application') }} {% if event.getRider %} - Rider - {{ event.getRider.getFullName }}{% endif %} +
+
+ {% endfor %} +
+
+
+
+
+ {% endif %} + + {% if ftags.ticket_table %} +
+ +
+
+

+ Tickets +

+
+
+
+
+
+
+
+ +
+
+ {% endif %} + +
+
+
+
+
+ {% if mode != 'update-all' %} + + {% endif %} + {% if ftags.set_map_coordinate and is_granted('joborder.cancel') and not obj.isCancelled %} + Cancel Job Order + {% endif %} + {% if mode != 'create' %} + Back + {% endif %} +
+
+
+
+
+
+
+
+
+ + {% if mode in ['update-processing', 'update-reassign-hub'] %} + + + {% endif %} +{% endblock %} + +{% block scripts %} +{{ include('map/' ~ map_js_file) }} + + + + +{% endblock %} + From c62775c9909cbd75f618d2484e2e601ec346ac21 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Tue, 7 Apr 2020 03:27:39 +0000 Subject: [PATCH 02/21] Moved the autoAssign code into a service. #374 --- config/resq.services.yaml | 6 ++ config/services.yaml | 6 ++ src/Controller/JobOrderController.php | 108 +---------------------- src/Entity/Hub.php | 18 ++++ src/Service/InventoryManager.php | 122 ++++++++++++++++++++++++++ 5 files changed, 155 insertions(+), 105 deletions(-) create mode 100644 src/Service/InventoryManager.php diff --git a/config/resq.services.yaml b/config/resq.services.yaml index 1756cf98..f38949c5 100644 --- a/config/resq.services.yaml +++ b/config/resq.services.yaml @@ -226,3 +226,9 @@ services: $redis_prov: "@App\\Service\\RedisClientProvider" $loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%" $status_key: "%env(STATUS_RIDER_KEY)%" + + # inventory manager + App\Service\InventoryManager: + arguments: + $em: "@doctrine.orm.entity_manager" + $map_tools: "@App\\Service\\MapTools" diff --git a/config/services.yaml b/config/services.yaml index 1756cf98..f38949c5 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -226,3 +226,9 @@ services: $redis_prov: "@App\\Service\\RedisClientProvider" $loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%" $status_key: "%env(STATUS_RIDER_KEY)%" + + # inventory manager + App\Service\InventoryManager: + arguments: + $em: "@doctrine.orm.entity_manager" + $map_tools: "@App\\Service\\MapTools" diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index cdb2d665..97b49565 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -21,6 +21,7 @@ use App\Service\GISManagerInterface; use App\Service\MapTools; use App\Service\MQTTClient; use App\Service\APNSClient; +use App\Service\InventoryManager; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -1078,7 +1079,7 @@ class JobOrderController extends Controller } public function autoAssignSubmit(Request $req, JobOrderHandlerInterface $jo_handler, - EntityManagerInterface $em, MapTools $map_tools) + InventoryManager $im) { $this->denyAccessUnlessGranted('jo_autoassign.test', null, 'No access.'); @@ -1103,8 +1104,7 @@ class JobOrderController extends Controller if (($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) || ($jo->getServicetype() == ServiceType::BATTERY_REPLACEMENT_WARRANTY)) - $this->autoAssignHubAndRider($jo, $em, $map_tools); - + $im->autoAssignHubAndRider($jo); // return successful response return $this->json([ @@ -1112,106 +1112,4 @@ class JobOrderController extends Controller ]); } - - protected function autoAssignHubAndRider($jo, EntityManagerInterface $em, MapTools $map_tools) - { - // get the nearest 10 hubs - // TODO: move this snippet to a function - $hubs = $map_tools->getClosestHubs($jo->getCoordinates(), 10, date("H:i:s")); - - $nearest_hubs = []; - $nearest_hubs_with_distance = []; - - foreach ($hubs as $hub) - { - $nearest_hubs_with_distance[] = $hub; - $nearest_hubs[] = $hub['hub']; - } - - // get battery sku - $invoice = $jo->getInvoice(); - if ($invoice != null) - { - $items = $invoice->getItems(); - $sku = ''; - foreach ($items as $item) - { - $sku = $item->getBattery()->getSAPCode(); - } - - // api call to check inventory - // pass the list of nearest hubs and the sku - // go through returned list of hubs for available riders - // for now, use all hubs - $hubs_with_inventory = $em->getRepository(Hub::class)->findAll(); - $nearest = []; - foreach ($hubs_with_inventory as $hub_with_inventory) - { - // check rider availability - if (count($hub_with_inventory->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 ($hub_with_inventory->getID() == $nhd['hub']->getID()) - { - if (empty($nearest)) - { - $nearest = $nhd; - } - else - { - if ($nhd['distance'] < $nearest['distance']) - $nearest = $nhd; - } - } - } - } - } - - // return $nearest - error_log('Nearest Hub ' . $nearest['hub']->getID()); - error_log('With distance ' . $nearest['distance']); - - $jo->setHub($nearest['hub']); - - $hub_riders = $nearest['hub']->getAvailableRiders(); - $rider = null; - if (count($hub_riders) > 1) - { - // this will no longer be necessary when the contents - // of randomizeRider changes - $available_riders = []; - foreach ($hub_riders as $rider) - { - $available_riders[] = $rider; - } - - $rider = $this->randomizeRider($available_riders); - } - else - $rider = $hub_riders[0]; - - $jo->setRider($rider); - - $jo->setStatus(JOStatus::ASSIGNED); - - $em->persist($jo); - $em->flush(); - - } - } - - protected function randomizeRider($riders) - { - // TODO: get redis to track the sales per rider per day - // check the time they came in - // for now, randomize the rider - $selected_index = array_rand($riders); - - $selected_rider = $riders[$selected_index]; - - return $selected_rider; - } } diff --git a/src/Entity/Hub.php b/src/Entity/Hub.php index 840f6d8c..03e0b202 100644 --- a/src/Entity/Hub.php +++ b/src/Entity/Hub.php @@ -44,6 +44,12 @@ class Hub */ protected $outlets; + // branch code + /** + * @ORM\Column(type="string", length=80) + */ + protected $branch_code; + public function __construct() { $this->time_open = new DateTime(); @@ -114,4 +120,16 @@ class Hub { return $this->outlets; } + + public function setBranchCode($branch_code) + { + $this->branch_code = $branch_code; + return $this; + } + + public function getBranchCode() + { + return $this->branch_code; + } + } diff --git a/src/Service/InventoryManager.php b/src/Service/InventoryManager.php new file mode 100644 index 00000000..6620e9ab --- /dev/null +++ b/src/Service/InventoryManager.php @@ -0,0 +1,122 @@ +em = $em; + $this->map_tools = $map_tools; + } + + public function autoAssignHubAndRider(JobOrder $jo) + { + // get the nearest 10 hubs + // TODO: move this snippet to a function + $hubs = $this->map_tools->getClosestHubs($jo->getCoordinates(), 10, date("H:i:s")); + + $nearest_hubs = []; + $nearest_hubs_with_distance = []; + + foreach ($hubs as $hub) + { + $nearest_hubs_with_distance[] = $hub; + // TODO: uncomment this when we have branch codes in data + //$nearest_hubs[] = $hub['hub']->getBranchCode(); + $nearest_hubs[] = $hub['hub']; + } + + // get battery sku + $invoice = $jo->getInvoice(); + if ($invoice != null) + { + $items = $invoice->getItems(); + $sku = ''; + foreach ($items as $item) + { + $sku = $item->getBattery()->getSAPCode(); + } + + // api call to check inventory + // pass the list of nearest hubs and the sku + // go through returned list of hubs for available riders + // for now, use all hubs + $hubs_with_inventory = $this->em->getRepository(Hub::class)->findAll(); + $nearest = []; + foreach ($hubs_with_inventory as $hub_with_inventory) + { + // check rider availability + if (count($hub_with_inventory->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 ($hub_with_inventory->getID() == $nhd['hub']->getID()) + { + if (empty($nearest)) + { + $nearest = $nhd; + } + else + { + if ($nhd['distance'] < $nearest['distance']) + $nearest = $nhd; + } + } + } + } + } + + $jo->setHub($nearest['hub']); + + $hub_riders = $nearest['hub']->getAvailableRiders(); + $rider = null; + if (count($hub_riders) > 1) + { + // this will no longer be necessary when the contents + // of randomizeRider changes + $available_riders = []; + foreach ($hub_riders as $rider) + { + $available_riders[] = $rider; + } + + $rider = $this->randomizeRider($available_riders); + } + else + $rider = $hub_riders[0]; + + $jo->setRider($rider); + + $jo->setStatus(JOStatus::ASSIGNED); + + $this->em->persist($jo); + $this->em->flush(); + + } + } + + protected function randomizeRider($riders) + { + // TODO: get redis to track the sales per rider per day + // check the time they came in + // for now, randomize the rider + $selected_index = array_rand($riders); + + $selected_rider = $riders[$selected_index]; + + return $selected_rider; + } +} From eb5710b84686233d157a181ddd8a5055ef897eb0 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Tue, 7 Apr 2020 10:10:44 +0000 Subject: [PATCH 03/21] Create InventoryManager service to connect to API. #374 --- .env.dist | 6 ++ config/resq.services.yaml | 6 +- config/services.yaml | 6 +- src/Controller/JobOrderController.php | 125 +++++++++++++++++++++- src/Service/InventoryManager.php | 145 +++++++++----------------- 5 files changed, 186 insertions(+), 102 deletions(-) diff --git a/.env.dist b/.env.dist index 30f9a925..831c5f98 100644 --- a/.env.dist +++ b/.env.dist @@ -57,3 +57,9 @@ COUNTRY_CODE=+insert_country_code_here # dashboard DASHBOARD_ENABLE=set_to_true_or_false + +# auth token for Inventory API +INVENTORY_API_URL=insert_api_url_here +INVENTORY_API_OCP=insert_ocp_text_here +INVENTORY_API_AUTH_TOKEN_PREFIX=Bearer +INVENTORY_API_AUTH_TOKEN=insert_auth_token_here diff --git a/config/resq.services.yaml b/config/resq.services.yaml index f38949c5..0c927f98 100644 --- a/config/resq.services.yaml +++ b/config/resq.services.yaml @@ -230,5 +230,7 @@ services: # inventory manager App\Service\InventoryManager: arguments: - $em: "@doctrine.orm.entity_manager" - $map_tools: "@App\\Service\\MapTools" + $api_url: "%env(INVENTORY_API_URL)%" + $api_ocp_key: "%env(INVENTORY_API_OCP)%" + $api_auth_prefix: "%env(INVENTORY_API_AUTH_TOKEN_PREFIX)%" + $api_auth_token: "%env(INVENTORY_API_AUTH_TOKEN)%" diff --git a/config/services.yaml b/config/services.yaml index f38949c5..d3d6bd5a 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -230,5 +230,7 @@ services: # inventory manager App\Service\InventoryManager: arguments: - $em: "@doctrine.orm.entity_manager" - $map_tools: "@App\\Service\\MapTools" + $api_url: "%env(INVENTORY_API_URL)%" + $api_ocp_key: "%env(INVENTORY_API_OCP)%" + $api_auth_prefix: "%env(INVENTORY_API_AUTH_TOKEN_PREFIX)%" + $api_auth_token: "%env(INVENTORY_API_AUTH_TOKEN)%" diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index 97b49565..26c9cb88 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -1079,6 +1079,7 @@ class JobOrderController extends Controller } public function autoAssignSubmit(Request $req, JobOrderHandlerInterface $jo_handler, + EntityManagerInterface $em, MapTools $map_tools, InventoryManager $im) { $this->denyAccessUnlessGranted('jo_autoassign.test', null, 'No access.'); @@ -1104,7 +1105,7 @@ class JobOrderController extends Controller if (($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) || ($jo->getServicetype() == ServiceType::BATTERY_REPLACEMENT_WARRANTY)) - $im->autoAssignHubAndRider($jo); + $this->autoAssignHubAndRider($jo, $em, $map_tools, $im); // return successful response return $this->json([ @@ -1112,4 +1113,126 @@ class JobOrderController extends Controller ]); } + + protected function autoAssignHubAndRider($jo, EntityManagerInterface $em, + MapTools $map_tools, InventoryManager $im) + { + // get the nearest 10 hubs + // TODO: move this snippet to a function + $hubs = $map_tools->getClosestHubs($jo->getCoordinates(), 10, date("H:i:s")); + + $nearest_hubs = []; + $nearest_hubs_with_distance = []; + + foreach ($hubs as $hub) + { + $nearest_hubs_with_distance[] = $hub; + // TODO: uncomment this when we have branch codes in data + // and when they have real codes and not test ones + //$nearest_branch_codes[] = $hub['hub']->getBranchCode(); + $nearest_branch_codes[] = $hub['hub']; + $nearest_branch_codes = ['WestAve','jay_franchise']; + } + + // get battery sku + $invoice = $jo->getInvoice(); + if ($invoice != null) + { + $items = $invoice->getItems(); + $skus = []; + foreach ($items as $item) + { + // TODO: uncomment this when they have real codes and not test ones + //$skus[] = $item->getBattery()->getSAPCode(); + $skus[] = 'WMGD31EL-CPNM0-L'; + } + + // api call to check inventory + // pass the list of branch codes of nearest hubs and the skus + // go through returned list of branch codes + $hubs_with_inventory = $im->getBranchInventory($nearest_branch_codes, $skus); + // TODO: check for empty result for now. + if (!empty($hubs_with_inventory)) + { + $nearest = []; + $flag_hub_found = false; + foreach ($hubs_with_inventory as $hub_with_inventory) + { + error_log($hub_with_inventory['BranchCode']); + // 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; + } + } + } + } + } + } + } + + if ($flag_hub_found) + { + $jo->setHub($nearest['hub']); + + $hub_riders = $nearest['hub']->getAvailableRiders(); + $rider = null; + if (count($hub_riders) > 1) + { + // TODO: this will no longer be necessary when the contents + // of randomizeRider changes + $available_riders = []; + foreach ($hub_riders as $rider) + { + $available_riders[] = $rider; + } + + $rider = $this->randomizeRider($available_riders); + } + else + $rider = $hub_riders[0]; + + $jo->setRider($rider); + + $jo->setStatus(JOStatus::ASSIGNED); + + $em->persist($jo); + $em->flush(); + } + } + } + } + + protected function randomizeRider($riders) + { + // TODO: get redis to track the sales per rider per day + // check the time they came in + // for now, randomize the rider + $selected_index = array_rand($riders); + + $selected_rider = $riders[$selected_index]; + + return $selected_rider; + } + } diff --git a/src/Service/InventoryManager.php b/src/Service/InventoryManager.php index 6620e9ab..7a3250c5 100644 --- a/src/Service/InventoryManager.php +++ b/src/Service/InventoryManager.php @@ -2,121 +2,72 @@ namespace App\Service; -use Doctrine\ORM\EntityManagerInterface; - -use App\Entity\JobOrder; -use App\Entity\Hub; - -use App\Ramcar\JOStatus; - class InventoryManager { - protected $em; - protected $map_tools; + protected $api_url; + protected $api_ocp_key; + protected $api_auth_prefix; + protected $api_auth_token; - public function __construct(EntityManagerInterface $em, MapTools $map_tools) + public function __construct($api_url, $api_ocp_key, $api_auth_prefix, $api_auth_token) { - $this->em = $em; - $this->map_tools = $map_tools; + $this->api_url = $api_url; + $this->api_ocp_key = $api_ocp_key; + $this->api_auth_prefix = $api_auth_prefix; + $this->api_auth_token = $api_auth_token; } - public function autoAssignHubAndRider(JobOrder $jo) + public function getBranchInventory($nearest_branch_codes, $skus) { - // get the nearest 10 hubs - // TODO: move this snippet to a function - $hubs = $this->map_tools->getClosestHubs($jo->getCoordinates(), 10, date("H:i:s")); + // api call to check inventory + // pass the list of branch codes and the skus + $data = [ + "BranchCodes" => $nearest_branch_codes, + "Skus" => $skus, + ]; - $nearest_hubs = []; - $nearest_hubs_with_distance = []; + $json_data = json_encode($data); + error_log('JSON ' . $json_data); - foreach ($hubs as $hub) - { - $nearest_hubs_with_distance[] = $hub; - // TODO: uncomment this when we have branch codes in data - //$nearest_hubs[] = $hub['hub']->getBranchCode(); - $nearest_hubs[] = $hub['hub']; - } + // initializes a new cURL session + $curl = curl_init($this->api_url); - // get battery sku - $invoice = $jo->getInvoice(); - if ($invoice != null) - { - $items = $invoice->getItems(); - $sku = ''; - foreach ($items as $item) - { - $sku = $item->getBattery()->getSAPCode(); - } + // Set the CURLOPT_RETURNTRANSFER option to true + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); - // api call to check inventory - // pass the list of nearest hubs and the sku - // go through returned list of hubs for available riders - // for now, use all hubs - $hubs_with_inventory = $this->em->getRepository(Hub::class)->findAll(); - $nearest = []; - foreach ($hubs_with_inventory as $hub_with_inventory) - { - // check rider availability - if (count($hub_with_inventory->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 ($hub_with_inventory->getID() == $nhd['hub']->getID()) - { - if (empty($nearest)) - { - $nearest = $nhd; - } - else - { - if ($nhd['distance'] < $nearest['distance']) - $nearest = $nhd; - } - } - } - } - } + // Set the CURLOPT_POST option to true for POST request + curl_setopt($curl, CURLOPT_POST, true); - $jo->setHub($nearest['hub']); + // Set the request data as JSON using json_encode function + curl_setopt($curl, CURLOPT_POSTFIELDS, $json_data); - $hub_riders = $nearest['hub']->getAvailableRiders(); - $rider = null; - if (count($hub_riders) > 1) - { - // this will no longer be necessary when the contents - // of randomizeRider changes - $available_riders = []; - foreach ($hub_riders as $rider) - { - $available_riders[] = $rider; - } + // set timeout + curl_setopt($curl, CURLOPT_TIMEOUT, 300); - $rider = $this->randomizeRider($available_riders); - } - else - $rider = $hub_riders[0]; + $authorization = $this->api_auth_prefix . ' ' . $this->api_auth_token; + $ocp = $this->api_ocp_key; - $jo->setRider($rider); + curl_setopt($curl, CURLOPT_HTTPHEADER, [ + 'Authorization: ' . $authorization, + 'Content-Type: application/json', + 'Ocp-Apim-Subscription-Key: ' . $ocp, + ]); - $jo->setStatus(JOStatus::ASSIGNED); + $response = curl_exec($curl); - $this->em->persist($jo); - $this->em->flush(); + // close cURL session + curl_close($curl); - } + // response is array of these + // { + // "BranchCode": "TestBranch1", + // "BranchName": "Test Branch", + // "SapCode": "WMGD31EL-CPNM0-L", + // "Quantity": 4 + // }, + $results = json_decode($response, true); + + return $results; } - protected function randomizeRider($riders) - { - // TODO: get redis to track the sales per rider per day - // check the time they came in - // for now, randomize the rider - $selected_index = array_rand($riders); - - $selected_rider = $riders[$selected_index]; - - return $selected_rider; - } } From 6c02c303706049480119d5110d774c420be4e71e Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 8 Apr 2020 02:32:16 +0000 Subject: [PATCH 04/21] Update function name. Add checking for empty response in getBranchesInventory. #374 --- src/Controller/JobOrderController.php | 4 +--- src/Service/InventoryManager.php | 19 +++++++++++++------ 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index 26c9cb88..bee059bc 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -1150,15 +1150,13 @@ class JobOrderController extends Controller // api call to check inventory // pass the list of branch codes of nearest hubs and the skus // go through returned list of branch codes - $hubs_with_inventory = $im->getBranchInventory($nearest_branch_codes, $skus); - // TODO: check for empty result 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) { - error_log($hub_with_inventory['BranchCode']); // find hub according to branch code $found_hub = $em->getRepository(Hub::class)->findOneBy(['branch_code' => $hub_with_inventory['BranchCode']]); if ($found_hub != null) diff --git a/src/Service/InventoryManager.php b/src/Service/InventoryManager.php index 7a3250c5..4dc5a8b1 100644 --- a/src/Service/InventoryManager.php +++ b/src/Service/InventoryManager.php @@ -17,7 +17,7 @@ class InventoryManager $this->api_auth_token = $api_auth_token; } - public function getBranchInventory($nearest_branch_codes, $skus) + public function getBranchesInventory($nearest_branch_codes, $skus) { // api call to check inventory // pass the list of branch codes and the skus @@ -60,12 +60,19 @@ class InventoryManager // response is array of these // { - // "BranchCode": "TestBranch1", - // "BranchName": "Test Branch", + // "Id": 37, + // "BranchCode": "WestAve", + // "BranchName": "West ave. Branch", + // "BranchRole": 0, + // "Name": "3SMF / GOLD ", // "SapCode": "WMGD31EL-CPNM0-L", - // "Quantity": 4 - // }, - $results = json_decode($response, true); + // "Quantity": 38 + // } + + // check if the response is empty + $results = []; + if (!empty($response)) + $results = json_decode($response, true); return $results; } From ab6ed4a185ded47ed677fb65775b6291d3c92b83 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 8 Apr 2020 07:34:28 +0000 Subject: [PATCH 05/21] Add auto assignment of hub and rider to APIController. #374 --- src/Controller/APIController.php | 126 +++++++++++++++++++++++++- src/Controller/JobOrderController.php | 1 - 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/src/Controller/APIController.php b/src/Controller/APIController.php index 9765ad8f..194c14be 100644 --- a/src/Controller/APIController.php +++ b/src/Controller/APIController.php @@ -29,6 +29,8 @@ use App\Service\RisingTideGateway; use App\Service\MQTTClient; use App\Service\GeofenceTracker; use App\Service\RiderTracker; +use App\Service\MapTools; +use App\Service\InventoryManager; use App\Entity\MobileSession; use App\Entity\Customer; @@ -45,6 +47,7 @@ use App\Entity\Service; use App\Entity\Partner; use App\Entity\Review; use App\Entity\PrivacyPolicy; +use App\Entity\Hub; use DateTime; use Exception; @@ -817,7 +820,8 @@ class APIController extends Controller return $res->getReturnResponse(); } - public function requestJobOrder(Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo) + public function requestJobOrder(Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo, + MapTools $map_tools, InventoryManager $im) { // check required parameters and api key $required_params = [ @@ -982,6 +986,42 @@ class APIController extends Controller $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); + 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; + } + + $assigned_rider = $this->randomizeRider($riders); + } + else + $assigned_rider = $available_riders[0]; + + $jo->setHub($nearest_hub); + $jo->setRider($assigned_rider); + $jo->setStatus(JOStatus::ASSIGNED); + } + } + } + $em->persist($jo); $em->persist($invoice); @@ -2213,4 +2253,88 @@ class APIController extends Controller return $cust; } + protected function findNearestHubWithInventory($jo, Battery $batt, EntityManagerInterface $em, + MapTools $map_tools, InventoryManager $im) + { + // get the nearest 10 hubs + $selected_hub = null; + $hubs = $map_tools->getClosestHubs($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; + + // 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 + $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 randomizeRider($riders) + { + // TODO: get redis to track the sales per rider per day + // check the time they came in + // for now, randomize the rider + $selected_index = array_rand($riders); + + $selected_rider = $riders[$selected_index]; + + return $selected_rider; + } + } diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index bee059bc..c92de36a 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -1130,7 +1130,6 @@ class JobOrderController extends Controller // TODO: uncomment this when we have branch codes in data // and when they have real codes and not test ones //$nearest_branch_codes[] = $hub['hub']->getBranchCode(); - $nearest_branch_codes[] = $hub['hub']; $nearest_branch_codes = ['WestAve','jay_franchise']; } From 917459f228ce80ec8e49eb5bc90b357784751ddd Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 8 Apr 2020 09:31:28 +0000 Subject: [PATCH 06/21] Comment out call to inventory manager. #374 --- src/Controller/APIController.php | 36 +++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/src/Controller/APIController.php b/src/Controller/APIController.php index 194c14be..d0099e73 100644 --- a/src/Controller/APIController.php +++ b/src/Controller/APIController.php @@ -2265,15 +2265,36 @@ class APIController extends Controller foreach ($hubs as $hub) { $nearest_hubs_with_distance[] = $hub; - if (!empty($hub['hub']->getBranchCode())) - $nearest_branch_codes[] = $hub['hub']->getBranchCode(); + //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; + //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(); @@ -2281,7 +2302,8 @@ class APIController extends Controller // api call to check inventory // pass the list of branch codes of nearest hubs and the skus // go through returned list of branch codes - $hubs_with_inventory = $im->getBranchesInventory($nearest_branch_codes, $skus); + // bypass inventory check for now + // $hubs_with_inventory = $im->getBranchesInventory($nearest_branch_codes, $skus); if (!empty($hubs_with_inventory)) { $nearest = []; @@ -2320,8 +2342,8 @@ class APIController extends Controller } } $selected_hub = $nearest['hub']; - } - } + } + } */ return $selected_hub; } From 8619c493978dbaf71c9de36a9021859d21996d33 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 8 Apr 2020 09:39:42 +0000 Subject: [PATCH 07/21] Add the status_open field in Hub. #374 --- src/Entity/Hub.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/Entity/Hub.php b/src/Entity/Hub.php index 03e0b202..f92a424e 100644 --- a/src/Entity/Hub.php +++ b/src/Entity/Hub.php @@ -50,12 +50,19 @@ class Hub */ protected $branch_code; + // is hub open + /** + * @ORM\Column(type="boolean") + */ + protected $status_open; + public function __construct() { $this->time_open = new DateTime(); $this->time_close = new DateTime(); $this->riders = new ArrayCollection(); $this->outlets = new ArrayCollection(); + $this->status_open = true; } public function getRiders() @@ -132,4 +139,15 @@ class Hub return $this->branch_code; } + public function setStatusOpen($status = true) + { + $this->status_open = $status; + return $this; + } + + public function isStatusOpen() + { + return $this->status_open; + } + } From e1b27dea9a75f0df0cd740010e55b88406041220 Mon Sep 17 00:00:00 2001 From: Korina Cordero Date: Wed, 8 Apr 2020 10:11:43 +0000 Subject: [PATCH 08/21] Add branch_code and status_open to hub form. #374 --- src/Controller/HubController.php | 4 +++- templates/hub/form.html.twig | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/Controller/HubController.php b/src/Controller/HubController.php index e1d41525..d6334c80 100644 --- a/src/Controller/HubController.php +++ b/src/Controller/HubController.php @@ -150,7 +150,9 @@ class HubController extends Controller ->setContactNumbers($req->request->get('contact_nums')) ->setTimeOpen($time_open) ->setTimeClose($time_close) - ->setCoordinates($point); + ->setCoordinates($point) + ->setBranchCode($req->request->get('branch_code', '')) + ->setStatusOpen($req->request->get('status_open') ?? false); } protected function setQueryFilters($datatable, QueryBuilder $query) diff --git a/templates/hub/form.html.twig b/templates/hub/form.html.twig index df8a7190..aed63021 100644 --- a/templates/hub/form.html.twig +++ b/templates/hub/form.html.twig @@ -82,6 +82,24 @@ +
+
+ + + +
+
+ + + +
+