Merge branch '374-auto-assign-hub-and-rider' into 'master'
Resolve "Auto-assign hub and rider" Closes #374 See merge request jankstudio/resq!420
This commit is contained in:
commit
e26f2dc7f8
19 changed files with 2506 additions and 10 deletions
|
|
@ -57,3 +57,12 @@ COUNTRY_CODE=+insert_country_code_here
|
||||||
|
|
||||||
# dashboard
|
# dashboard
|
||||||
DASHBOARD_ENABLE=set_to_true_or_false
|
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
|
||||||
|
|
||||||
|
# API logging
|
||||||
|
API_LOGGING=set_to_true_or_false
|
||||||
|
|
|
||||||
|
|
@ -264,6 +264,8 @@ access_keys:
|
||||||
label: Walk-in
|
label: Walk-in
|
||||||
- id: jo_walkin.edit
|
- id: jo_walkin.edit
|
||||||
label: Walk-in Edit
|
label: Walk-in Edit
|
||||||
|
- id: jo_autoassign.test
|
||||||
|
label: Autoassign Test
|
||||||
|
|
||||||
- id: support
|
- id: support
|
||||||
label: Customer Support Access
|
label: Customer Support Access
|
||||||
|
|
|
||||||
|
|
@ -122,6 +122,10 @@ main_menu:
|
||||||
acl: jo_all.list
|
acl: jo_all.list
|
||||||
label: View All
|
label: View All
|
||||||
parent: joborder
|
parent: joborder
|
||||||
|
- id: jo_autoassign
|
||||||
|
acl: jo_autoassign.test
|
||||||
|
label: Autoassign Test
|
||||||
|
parent: joborder
|
||||||
|
|
||||||
- id: support
|
- id: support
|
||||||
acl: support.menu
|
acl: support.menu
|
||||||
|
|
|
||||||
|
|
@ -226,3 +226,11 @@ services:
|
||||||
$redis_prov: "@App\\Service\\RedisClientProvider"
|
$redis_prov: "@App\\Service\\RedisClientProvider"
|
||||||
$loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%"
|
$loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%"
|
||||||
$status_key: "%env(STATUS_RIDER_KEY)%"
|
$status_key: "%env(STATUS_RIDER_KEY)%"
|
||||||
|
|
||||||
|
# inventory manager
|
||||||
|
App\Service\InventoryManager:
|
||||||
|
arguments:
|
||||||
|
$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)%"
|
||||||
|
|
|
||||||
|
|
@ -226,3 +226,12 @@ jo_walkin_edit_submit:
|
||||||
controller: App\Controller\JobOrderController::walkInEditSubmit
|
controller: App\Controller\JobOrderController::walkInEditSubmit
|
||||||
methods: [POST]
|
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]
|
||||||
|
|
|
||||||
|
|
@ -226,3 +226,16 @@ services:
|
||||||
$redis_prov: "@App\\Service\\RedisClientProvider"
|
$redis_prov: "@App\\Service\\RedisClientProvider"
|
||||||
$loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%"
|
$loc_key: "%env(LOCATION_RIDER_ACTIVE_KEY)%"
|
||||||
$status_key: "%env(STATUS_RIDER_KEY)%"
|
$status_key: "%env(STATUS_RIDER_KEY)%"
|
||||||
|
|
||||||
|
# inventory manager
|
||||||
|
App\Service\InventoryManager:
|
||||||
|
arguments:
|
||||||
|
$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)%"
|
||||||
|
|
||||||
|
# API logging
|
||||||
|
App\EventSubscriber\LogSubscriber:
|
||||||
|
arguments:
|
||||||
|
$api_log_flag: "%env(API_LOGGING)%"
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,9 @@ use App\Service\RisingTideGateway;
|
||||||
use App\Service\MQTTClient;
|
use App\Service\MQTTClient;
|
||||||
use App\Service\GeofenceTracker;
|
use App\Service\GeofenceTracker;
|
||||||
use App\Service\RiderTracker;
|
use App\Service\RiderTracker;
|
||||||
|
use App\Service\MapTools;
|
||||||
|
use App\Service\InventoryManager;
|
||||||
|
use App\Service\RiderAssignmentHandlerInterface;
|
||||||
|
|
||||||
use App\Entity\MobileSession;
|
use App\Entity\MobileSession;
|
||||||
use App\Entity\Customer;
|
use App\Entity\Customer;
|
||||||
|
|
@ -45,12 +48,13 @@ use App\Entity\Service;
|
||||||
use App\Entity\Partner;
|
use App\Entity\Partner;
|
||||||
use App\Entity\Review;
|
use App\Entity\Review;
|
||||||
use App\Entity\PrivacyPolicy;
|
use App\Entity\PrivacyPolicy;
|
||||||
|
use App\Entity\Hub;
|
||||||
|
|
||||||
use DateTime;
|
use DateTime;
|
||||||
use Exception;
|
use Exception;
|
||||||
|
|
||||||
// mobile API
|
// mobile API
|
||||||
class APIController extends Controller
|
class APIController extends Controller implements LoggedController
|
||||||
{
|
{
|
||||||
protected $session;
|
protected $session;
|
||||||
|
|
||||||
|
|
@ -817,7 +821,9 @@ class APIController extends Controller
|
||||||
return $res->getReturnResponse();
|
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, MQTTClient $mclient,
|
||||||
|
RiderAssignmentHandlerInterface $rah)
|
||||||
{
|
{
|
||||||
// check required parameters and api key
|
// check required parameters and api key
|
||||||
$required_params = [
|
$required_params = [
|
||||||
|
|
@ -982,16 +988,86 @@ class APIController extends Controller
|
||||||
$invoice = $ic->generateInvoice($icrit);
|
$invoice = $ic->generateInvoice($icrit);
|
||||||
$jo->setInvoice($invoice);
|
$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);
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
$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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$em->persist($jo);
|
$em->persist($jo);
|
||||||
$em->persist($invoice);
|
$em->persist($invoice);
|
||||||
|
|
||||||
// add event log
|
// add event log for JO
|
||||||
$event = new JOEvent();
|
$event = new JOEvent();
|
||||||
$event->setDateHappen(new DateTime())
|
$event->setDateHappen(new DateTime())
|
||||||
->setTypeID(JOEventType::CREATE)
|
->setTypeID(JOEventType::CREATE)
|
||||||
->setJobOrder($jo);
|
->setJobOrder($jo);
|
||||||
$em->persist($event);
|
$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);
|
||||||
|
|
||||||
|
$rider_assign_event = new JOEvent();
|
||||||
|
$rider_assign_event->setDateHappen(new DateTime())
|
||||||
|
->setTypeID(JOEventType::RIDER_ASSIGN)
|
||||||
|
->setJobOrder($jo);
|
||||||
|
|
||||||
|
$em->persist($rider_assign_event);
|
||||||
|
|
||||||
|
// user mqtt event
|
||||||
|
$payload = [
|
||||||
|
'event' => 'outlet_assign'
|
||||||
|
];
|
||||||
|
$mclient->sendEvent($jo, $payload);
|
||||||
|
|
||||||
|
$rah->assignJobOrder($jo, $jo->getRider());
|
||||||
|
}
|
||||||
|
|
||||||
$em->flush();
|
$em->flush();
|
||||||
|
|
||||||
// make invoice json data
|
// make invoice json data
|
||||||
|
|
@ -2213,4 +2289,152 @@ class APIController extends Controller
|
||||||
return $cust;
|
return $cust;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function findNearestHub($jo, EntityManagerInterface $em,
|
||||||
|
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)
|
||||||
|
{
|
||||||
|
if (count($nhd['hub']->getAvailableRiders()) > 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, EntityManagerInterface $em,
|
||||||
|
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 = $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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -181,11 +181,15 @@ class CustomerController extends Controller
|
||||||
|
|
||||||
public function getCustomerVehicles(Request $req, CustomerHandlerInterface $cust_handler)
|
public function getCustomerVehicles(Request $req, CustomerHandlerInterface $cust_handler)
|
||||||
{
|
{
|
||||||
|
/*
|
||||||
|
// TODO: fix
|
||||||
if ((!$this->isGranted('jo_onestep.form')) ||
|
if ((!$this->isGranted('jo_onestep.form')) ||
|
||||||
(!$this->isGranted('jo_walkin.form'))) {
|
(!$this->isGranted('jo_walkin.form')) ||
|
||||||
|
(!$this->isGranted('jo_in.list'))) {
|
||||||
$exception = $this->createAccessDeniedException('No access.');
|
$exception = $this->createAccessDeniedException('No access.');
|
||||||
throw $exception;
|
throw $exception;
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
$results = $cust_handler->getCustomerVehicles($req);
|
$results = $cust_handler->getCustomerVehicles($req);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -150,7 +150,9 @@ class HubController extends Controller
|
||||||
->setContactNumbers($req->request->get('contact_nums'))
|
->setContactNumbers($req->request->get('contact_nums'))
|
||||||
->setTimeOpen($time_open)
|
->setTimeOpen($time_open)
|
||||||
->setTimeClose($time_close)
|
->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)
|
protected function setQueryFilters($datatable, QueryBuilder $query)
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ namespace App\Controller;
|
||||||
use App\Ramcar\JOStatus;
|
use App\Ramcar\JOStatus;
|
||||||
use App\Ramcar\InvoiceCriteria;
|
use App\Ramcar\InvoiceCriteria;
|
||||||
use App\Ramcar\CMBServiceType;
|
use App\Ramcar\CMBServiceType;
|
||||||
|
use App\Ramcar\ServiceType;
|
||||||
|
|
||||||
use App\Entity\CustomerVehicle;
|
use App\Entity\CustomerVehicle;
|
||||||
use App\Entity\Promo;
|
use App\Entity\Promo;
|
||||||
|
|
@ -20,6 +21,7 @@ use App\Service\GISManagerInterface;
|
||||||
use App\Service\MapTools;
|
use App\Service\MapTools;
|
||||||
use App\Service\MQTTClient;
|
use App\Service\MQTTClient;
|
||||||
use App\Service\APNSClient;
|
use App\Service\APNSClient;
|
||||||
|
use App\Service\InventoryManager;
|
||||||
|
|
||||||
use Symfony\Component\HttpFoundation\Request;
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
use Symfony\Component\HttpFoundation\Response;
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
@ -96,7 +98,9 @@ class JobOrderController extends Controller
|
||||||
$this->denyAccessUnlessGranted('jo_open.edit', null, 'No access.');
|
$this->denyAccessUnlessGranted('jo_open.edit', null, 'No access.');
|
||||||
|
|
||||||
$error_array = [];
|
$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
|
// check if any errors were found
|
||||||
if (!empty($error_array)) {
|
if (!empty($error_array)) {
|
||||||
|
|
@ -145,7 +149,9 @@ class JobOrderController extends Controller
|
||||||
// initialize error list
|
// initialize error list
|
||||||
$error_array = [];
|
$error_array = [];
|
||||||
$id = -1;
|
$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
|
// check if any errors were found
|
||||||
if (!empty($error_array)) {
|
if (!empty($error_array)) {
|
||||||
|
|
@ -1053,5 +1059,177 @@ 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,
|
||||||
|
InventoryManager $im)
|
||||||
|
{
|
||||||
|
$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, $im);
|
||||||
|
|
||||||
|
// return successful response
|
||||||
|
return $this->json([
|
||||||
|
'success' => 'Changes have been saved!'
|
||||||
|
]);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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 = ['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->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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
7
src/Controller/LoggedController.php
Normal file
7
src/Controller/LoggedController.php
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
interface LoggedController
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -44,12 +44,25 @@ class Hub
|
||||||
*/
|
*/
|
||||||
protected $outlets;
|
protected $outlets;
|
||||||
|
|
||||||
|
// branch code
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="string", length=80)
|
||||||
|
*/
|
||||||
|
protected $branch_code;
|
||||||
|
|
||||||
|
// is hub open
|
||||||
|
/**
|
||||||
|
* @ORM\Column(type="boolean")
|
||||||
|
*/
|
||||||
|
protected $status_open;
|
||||||
|
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
$this->time_open = new DateTime();
|
$this->time_open = new DateTime();
|
||||||
$this->time_close = new DateTime();
|
$this->time_close = new DateTime();
|
||||||
$this->riders = new ArrayCollection();
|
$this->riders = new ArrayCollection();
|
||||||
$this->outlets = new ArrayCollection();
|
$this->outlets = new ArrayCollection();
|
||||||
|
$this->status_open = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getRiders()
|
public function getRiders()
|
||||||
|
|
@ -114,4 +127,27 @@ class Hub
|
||||||
{
|
{
|
||||||
return $this->outlets;
|
return $this->outlets;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function setBranchCode($branch_code)
|
||||||
|
{
|
||||||
|
$this->branch_code = $branch_code;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getBranchCode()
|
||||||
|
{
|
||||||
|
return $this->branch_code;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function setStatusOpen($status = true)
|
||||||
|
{
|
||||||
|
$this->status_open = $status;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isStatusOpen()
|
||||||
|
{
|
||||||
|
return $this->status_open;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
72
src/EventSubscriber/LogSubscriber.php
Normal file
72
src/EventSubscriber/LogSubscriber.php
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\EventSubscriber;
|
||||||
|
|
||||||
|
use App\Controller\LoggedController;
|
||||||
|
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
|
||||||
|
use Symfony\Component\HttpKernel\Event\ControllerEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Event\ResponseEvent;
|
||||||
|
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
|
||||||
|
use Symfony\Component\HttpKernel\KernelEvents;
|
||||||
|
|
||||||
|
class LogSubscriber implements EventSubscriberInterface
|
||||||
|
{
|
||||||
|
protected $allow;
|
||||||
|
protected $api_log_flag;
|
||||||
|
|
||||||
|
public function __construct($api_log_flag)
|
||||||
|
{
|
||||||
|
$this->api_log_flag = $api_log_flag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onKernelController(ControllerEvent $event)
|
||||||
|
{
|
||||||
|
if ($this->api_log_flag == 'false')
|
||||||
|
return;
|
||||||
|
|
||||||
|
$controller = $event->getController();
|
||||||
|
$this->allow = false;
|
||||||
|
|
||||||
|
// when a controller class defines multiple action methods, the controller
|
||||||
|
// is returned as [$controllerInstance, 'methodName']
|
||||||
|
if (is_array($controller))
|
||||||
|
$controller = $controller[0];
|
||||||
|
|
||||||
|
if (!($controller instanceof LoggedController))
|
||||||
|
return;
|
||||||
|
|
||||||
|
$this->allow = true;
|
||||||
|
|
||||||
|
$req = $event->getRequest();
|
||||||
|
$reqdata = [
|
||||||
|
$req->getPathInfo(),
|
||||||
|
$req->getMethod(),
|
||||||
|
$req->query->all(),
|
||||||
|
$req->request->all(),
|
||||||
|
];
|
||||||
|
error_log(print_r($reqdata, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function onKernelResponse(ResponseEvent $event)
|
||||||
|
{
|
||||||
|
if ($this->api_log_flag == 'false')
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (!$this->allow)
|
||||||
|
return;
|
||||||
|
|
||||||
|
$resp = $event->getResponse();
|
||||||
|
|
||||||
|
$content = $resp->getContent();
|
||||||
|
|
||||||
|
error_log(print_r($content, true));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getSubscribedEvents()
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
KernelEvents::CONTROLLER => 'onKernelController',
|
||||||
|
KernelEvents::RESPONSE => 'onKernelResponse',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
80
src/Service/InventoryManager.php
Normal file
80
src/Service/InventoryManager.php
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
class InventoryManager
|
||||||
|
{
|
||||||
|
protected $api_url;
|
||||||
|
protected $api_ocp_key;
|
||||||
|
protected $api_auth_prefix;
|
||||||
|
protected $api_auth_token;
|
||||||
|
|
||||||
|
public function __construct($api_url, $api_ocp_key, $api_auth_prefix, $api_auth_token)
|
||||||
|
{
|
||||||
|
$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 getBranchesInventory($nearest_branch_codes, $skus)
|
||||||
|
{
|
||||||
|
// api call to check inventory
|
||||||
|
// pass the list of branch codes and the skus
|
||||||
|
$data = [
|
||||||
|
"BranchCodes" => $nearest_branch_codes,
|
||||||
|
"Skus" => $skus,
|
||||||
|
];
|
||||||
|
|
||||||
|
$json_data = json_encode($data);
|
||||||
|
error_log('JSON ' . $json_data);
|
||||||
|
|
||||||
|
// initializes a new cURL session
|
||||||
|
$curl = curl_init($this->api_url);
|
||||||
|
|
||||||
|
// Set the CURLOPT_RETURNTRANSFER option to true
|
||||||
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
||||||
|
|
||||||
|
// Set the CURLOPT_POST option to true for POST request
|
||||||
|
curl_setopt($curl, CURLOPT_POST, true);
|
||||||
|
|
||||||
|
// Set the request data as JSON using json_encode function
|
||||||
|
curl_setopt($curl, CURLOPT_POSTFIELDS, $json_data);
|
||||||
|
|
||||||
|
// set timeout
|
||||||
|
curl_setopt($curl, CURLOPT_TIMEOUT, 300);
|
||||||
|
|
||||||
|
$authorization = $this->api_auth_prefix . ' ' . $this->api_auth_token;
|
||||||
|
$ocp = $this->api_ocp_key;
|
||||||
|
|
||||||
|
curl_setopt($curl, CURLOPT_HTTPHEADER, [
|
||||||
|
'Authorization: ' . $authorization,
|
||||||
|
'Content-Type: application/json',
|
||||||
|
'Ocp-Apim-Subscription-Key: ' . $ocp,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$response = curl_exec($curl);
|
||||||
|
|
||||||
|
// close cURL session
|
||||||
|
curl_close($curl);
|
||||||
|
|
||||||
|
// response is array of these
|
||||||
|
// {
|
||||||
|
// "Id": 37,
|
||||||
|
// "BranchCode": "WestAve",
|
||||||
|
// "BranchName": "West ave. Branch",
|
||||||
|
// "BranchRole": 0,
|
||||||
|
// "Name": "3SMF / GOLD ",
|
||||||
|
// "SapCode": "WMGD31EL-CPNM0-L",
|
||||||
|
// "Quantity": 38
|
||||||
|
// }
|
||||||
|
|
||||||
|
// check if the response is empty
|
||||||
|
$results = [];
|
||||||
|
if (!empty($response))
|
||||||
|
$results = json_decode($response, true);
|
||||||
|
|
||||||
|
return $results;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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
|
// dispatch job order
|
||||||
|
|
|
||||||
|
|
@ -148,6 +148,52 @@ class MapTools
|
||||||
return $final_data;
|
return $final_data;
|
||||||
*/
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getClosestOpenHubs(Point $point, $limit, $time = false)
|
||||||
|
{
|
||||||
|
// get closest hubs based on st_distance function from db
|
||||||
|
$query = $this->em->createQuery('SELECT h, st_distance(h.coordinates, point(:lng, :lat)) as dist FROM App\Entity\Hub h' . ($time ? ' WHERE h.status_open = true AND :time BETWEEN h.time_open AND h.time_close' : '') . ' ORDER BY dist')
|
||||||
|
->setParameter('lat', $point->getLatitude())
|
||||||
|
->setParameter('lng', $point->getLongitude());
|
||||||
|
|
||||||
|
if ($time) {
|
||||||
|
$query->setParameter('time', $time);
|
||||||
|
}
|
||||||
|
|
||||||
|
$query->setMaxResults($limit);
|
||||||
|
|
||||||
|
// error_log($query->getSql());
|
||||||
|
$result = $query->getResult();
|
||||||
|
|
||||||
|
$hubs = [];
|
||||||
|
$final_data = [];
|
||||||
|
foreach ($result as $row)
|
||||||
|
{
|
||||||
|
//error_log($row[0]->getName() . ' - ' . $row['dist']);
|
||||||
|
$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);
|
||||||
|
|
||||||
|
$final_data[] = [
|
||||||
|
'hub' => $row[0],
|
||||||
|
'db_distance' => $row['dist'],
|
||||||
|
'distance' => $dist,
|
||||||
|
'duration' => 0,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return $final_data;
|
||||||
|
}
|
||||||
|
|
||||||
protected function distance($lat1, $lon1, $lat2, $lon2)
|
protected function distance($lat1, $lon1, $lat2, $lon2)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -483,7 +483,9 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface
|
||||||
return $data;
|
return $data;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: tag rider as available
|
// tag rider as available
|
||||||
|
$rider = $this->session->getRider();
|
||||||
|
$rider->setAvailable(true);
|
||||||
|
|
||||||
$this->em->flush();
|
$this->em->flush();
|
||||||
|
|
||||||
|
|
@ -515,7 +517,8 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface
|
||||||
->setRider($rider);
|
->setRider($rider);
|
||||||
$this->em->persist($event);
|
$this->em->persist($event);
|
||||||
|
|
||||||
// TODO: tag rider as unavailable
|
// tag rider as unavailable
|
||||||
|
$rider->setAvailable(false);
|
||||||
|
|
||||||
// save to customer vehicle battery record
|
// save to customer vehicle battery record
|
||||||
$this->jo_handler->updateVehicleBattery($jo);
|
$this->jo_handler->updateVehicleBattery($jo);
|
||||||
|
|
|
||||||
|
|
@ -82,6 +82,24 @@
|
||||||
<div class="form-control-feedback hide" data-field="contact_nums"></div>
|
<div class="form-control-feedback hide" data-field="contact_nums"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group m-form__group row no-border">
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<label for="branch_code" data-field="branch_code">
|
||||||
|
Branch Code
|
||||||
|
</label>
|
||||||
|
<input type="text" name="branch_code" class="form-control m-input" value="{{ obj.getBranchCode() }}">
|
||||||
|
<div class="form-control-feedback hide" data-field="branch_code"></div>
|
||||||
|
</div>
|
||||||
|
<div class="col-lg-6">
|
||||||
|
<span class="m-switch m-switch--icon block-switch">
|
||||||
|
<label>
|
||||||
|
<input type="checkbox" name="status_open" id="status-open" value="1"{{ obj.isStatusOpen ? ' checked' }}>
|
||||||
|
<label class="switch-label">Open</label>
|
||||||
|
<span></span>
|
||||||
|
</label>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div class="form-group m-form__group row no-border">
|
<div class="form-group m-form__group row no-border">
|
||||||
<div class="col-lg-3">
|
<div class="col-lg-3">
|
||||||
<label for="time_open">
|
<label for="time_open">
|
||||||
|
|
|
||||||
1774
templates/job-order/autoassign.form.html.twig
Normal file
1774
templates/job-order/autoassign.form.html.twig
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue