resq/src/Service/RiderAPIHandler/CMBRiderAPIHandler.php
2020-01-31 09:04:04 +00:00

592 lines
17 KiB
PHP

<?php
namespace App\Service\RiderAPIHandler;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
use App\Ramcar\CMBServiceType;
use App\Ramcar\CMBTradeInType;
use App\Ramcar\JOStatus;
use App\Ramcar\JOEventType;
use App\Service\RiderAPIHandlerInterface;
use App\Service\RedisClientProvider;
use App\Service\RiderCache;
use App\Service\MQTTClient;
use App\Entity\RiderSession;
use App\Entity\Rider;
use App\Entity\User;
use App\Entity\JOEvent;
use DateTime;
class CMBRiderAPIHandler implements RiderAPIHandlerInterface
{
protected $em;
protected $redis;
protected $ef;
protected $rcache;
protected $country_code;
protected $mclient;
protected $session;
public function __construct(EntityManagerInterface $em, RedisClientProvider $redis,
EncoderFactoryInterface $ef, RiderCache $rcache,
string $country_code, MQTTClient $mclient)
{
$this->em = $em;
$this->redis = $redis;
$this->ef = $ef;
$this->rcache = $rcache;
$this->country_code = $country_code;
$this->mclient = $mclient;
// one device = one session, since we have control over the devices
// when a rider logs in, we just change the rider assigned to the device
// when a rider logs out, we remove the rider assigned to the device
$this->session = null;
}
public function register(Request $req)
{
// confirm parameters
$required_params = [
'phone_number',
'device_push_id'
];
$missing = $this->checkMissingParameters($req, $required_params);
if (count($missing) > 0)
{
$params = implode(', ', $missing);
$data = [
'error' => 'Missing parameter(s): ' . $params
];
return $data;
}
// retry until we get a unique id
while (true)
{
try
{
// instantiate session
$sess = new RiderSession();
$sess->setPhoneNumber($req->request->get('phone_number'))
->setDevicePushID($req->request->get('device_push_id'));
// reopen in case we get an exception
if (!$this->em->isOpen())
{
$this->em = $this->em->create(
$this->em->getConnection(),
$this->em->getConfiguration()
);
}
// save
$this->em->persist($sess);
$this->em->flush();
// create redis entry for the session
$redis_client = $this->redis->getRedisClient();
$redis_key = 'rider.id.' . $sess->getID();
error_log('redis_key: ' . $redis_key);
$redis_client->set($redis_key, '');
}
catch (DBALException $e)
{
error_log($e->getMessage());
// delay one second and try again
sleep(1);
continue;
}
break;
}
// return data
$data = [
'session_id' => $sess->getID()
];
return $data;
}
public function login(Request $req)
{
$required_params = [
'user',
'pass',
];
$data = $this->checkParamsAndKey($req, $required_params);
if (isset($data['error']))
return $data;
// check if session has a rider already
if ($this->session->hasRider())
{
$data = [
'error' => 'Another rider is already logged in. Please logout first.'
];
return $data;
}
// look for rider with username
$rider = $this->em->getRepository(Rider::class)->findOneBy(['username' => $req->request->get('user')]);
if ($rider == null)
{
$data = [
'error' => 'Invalid username or password.'
];
return $data;
}
// check if rider password is correct
$encoder = $this->ef->getEncoder(new User());
if (!$encoder->isPasswordValid($rider->getPassword(), $req->request->get('pass'), ''))
{
$data = [
'error' => 'Invalid username or password.'
];
return $data;
}
// assign rider to session
$this->session->setRider($rider);
$rider->setAvailable(true);
$rider_id = $rider->getID();
// cache rider location (default to hub)
// TODO: figure out longitude / latitude default
$this->rcache->addActiveRider($rider_id, 0, 0);
// TODO: log rider logging in
$this->em->flush();
// update redis rider.id.<session id> with the rider id
$redis_client = $this->redis->getRedisClient();
$redis_key = 'rider.id.' . $this->session->getID();
$rider_id = $rider->getID();
$redis_client->set($redis_key, $rider_id);
$hub = $rider->getHub();
if ($hub == null)
$hub_data = null;
else
{
$coord = $hub->getCoordinates();
$hub_data = [
'id' => $hub->getID(),
'name' => $hub->getName(),
'branch' => $hub->getBranch(),
'longitude' => $coord->getLongitude(),
'latitude' => $coord->getLatitude(),
'contact_nums' => $hub->getContactNumbers(),
];
}
// data
$data = [
'hub' => $hub_data,
'rider_id' => $rider_id,
];
return $data;
}
public function logout(Request $req)
{
$required_params = [];
$data = $this->checkParamsAndKey($req, $required_params);
if (isset($data['error']))
return $data;
// make rider unavailable
$rider = $this->session->getRider();
$rider->setAvailable(false);
// remove from cache
$this->rcache->removeActiveRider($rider->getID());
// remove rider from session
$this->session->setRider(null);
// TODO: log rider logging out
$this->em->flush();
return $data;
}
public function getJobOrder(Request $req)
{
// get the job order of the rider assigned to this session
$required_params = [];
$data = $this->checkParamsAndKey($req, $required_params);
if (isset($data['error']))
return $data;
// are we logged in?
if (!$this->session->hasRider())
{
$data = [
'error' => 'No logged in rider.'
];
return $data;
}
$rider = $this->session->getRider();
// do we have a job order?
$jo = $rider->getActiveJobOrder();
if ($jo == null)
{
$data = [
'job_order' => null
];
}
else
{
$coord = $jo->getCoordinates();
$cust = $jo->getCustomer();
$cv = $jo->getCustomerVehicle();
$v = $cv->getVehicle();
$inv = $jo->getInvoice();
$promo = $inv->getPromo();
// invoice items
$inv_items = [];
foreach ($inv->getItems() as $item)
{
$item_batt = $item->getBattery();
if ($item_batt == null)
$batt_id = null;
else
$batt_id = $item_batt->getID();
$inv_items[] = [
'id' => $item->getID(),
'title' => $item->getTitle(),
'qty' => $item->getQuantity(),
'price' => $item->getPrice(),
'batt_id' => $batt_id,
];
}
// promo
if ($promo != null)
{
$promo_data = [
'id' => $promo->getID(),
'name' => $promo->getName(),
'code' => $promo->getCode(),
'discount_rate' => $promo->getDiscountRate(),
'discount_apply' => $promo->getDiscountApply(),
];
}
else
{
$promo_data = null;
}
$trade_in_type = $jo->getTradeInType();
if (empty($trade_in_type))
$trade_in_type = 'none';
$data = [
'job_order' => [
'id' => $jo->getID(),
'service_type' => $jo->getServiceType(),
'date_schedule' => $jo->getDateSchedule()->format('Ymd H:i:s'),
'longitude' => $coord->getLongitude(),
'latitude' => $coord->getLatitude(),
'status' => $jo->getStatus(),
'customer' => [
'title' => $cust->getTitle(),
'first_name' => $cust->getFirstName(),
'last_name' => $cust->getLastName(),
'phone_mobile' => $this->country_code . $cust->getPhoneMobile(),
],
'vehicle' => [
'manufacturer' => $v->getManufacturer()->getName(),
'make' => $v->getMake(),
'model' => $cv->getModelYear(),
'plate_number' => $cv->getPlateNumber(),
'color' => $cv->getColor(),
],
'or_num' => $jo->getORNum(),
'or_name' => $jo->getORName(),
'delivery_instructions' => $jo->getDeliveryInstructions(),
'delivery_address' => $jo->getDeliveryAddress(),
'landmark' => $jo->getLandmark(),
'invoice' => [
'discount' => $inv->getDiscount(),
'trade_in' => $inv->getTradeIn(),
'total_price' => $inv->getTotalPrice(),
'vat' => $inv->getVat(),
'items' => $inv_items,
],
'mode_of_payment' => $jo->getModeOfPayment(),
'trade_in_type' => $trade_in_type,
'promo' => $promo_data,
// TODO: load the actual
'has_warranty_doc' => false,
'flag_coolant' => $jo->hasCoolant(),
'has_motolite' => $cv->hasMotoliteBattery(),
]
];
}
return $data;
}
public function acceptJobOrder(Request $req)
{
$required_params = ['jo_id'];
$data = $this->checkJO($req, $required_params, $jo);
if (isset($data['error']))
return $data;
// TODO: refactor this into a jo handler class, so we don't have to repeat for control center
// set jo status to in transit
$jo->setStatus(JOStatus::IN_TRANSIT);
// TODO: send mqtt event (?)
// add event log
$rider = $this->session->getRider();
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::RIDER_ACCEPT)
->setJobOrder($jo)
->setRider($rider);
$this->em->persist($event);
$this->em->flush();
return $data;
}
public function cancelJobOrder(Request $req)
{
$required_params = ['jo_id'];
$data = $this->checkJO($req, $required_params, $jo);
if (isset($data['error']))
return $data;
// $jo->cancel("rider cancelled");
// requeue it, instead of cancelling it
$jo->requeue();
// add event log
$rider = $this->session->getRider();
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::REQUEUE)
->setJobOrder($jo)
->setRider($rider);
$this->em->persist($event);
$this->em->flush();
// send mqtt event
// send outlet assign since order should go back to hub and await reassignment to another rider
$payload = [
'event' => 'outlet_assign',
'jo_id' => $jo->getID(),
];
$this->mclient->sendEvent($jo, $payload);
return $data;
}
public function arrive(Request $req)
{
$required_params = ['jo_id'];
$data = $this->checkJO($req, $required_params, $jo);
if (isset($data['error']))
return $data;
// TODO: refactor this into a jo handler class, so we don't have to repeat for control center
// set jo status to in progress
$jo->setStatus(JOStatus::IN_PROGRESS);
// add event log
$rider = $this->session->getRider();
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::RIDER_ARRIVE)
->setJobOrder($jo)
->setRider($rider);
$this->em->persist($event);
$this->em->flush();
// send mqtt event
$rider = $this->session->getRider();
$image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif';
if ($rider->getImageFile() != null)
$image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile();
$payload = [
'event' => 'driver_arrived',
'jo_id' => $jo->getID(),
'driver_image' => $image_url,
'driver_name' => $rider->getFullName(),
'driver_id' => $rider->getID(),
];
$this->mclient->sendEvent($jo, $payload);
return $data;
}
public function hubArrive(Request $req)
{
$required_params = [];
$data = $this->checkParamsAndKey($req, $required_params);
if (isset($data['error']))
return $data;
// are we logged in?
if (!$this->session->hasRider())
{
$data = [
'error' => 'No logged in rider.'
];
return $data;
}
// TODO: tag rider as available
$this->em->flush();
return $data;
}
protected function checkMissingParameters(Request $req, $params = [])
{
$missing = [];
// check if parameters are there
foreach ($params as $param)
{
if ($req->getMethod() == 'GET')
{
$check = $req->query->get($param);
if ($check == null)
$missing[] = $param;
}
else if ($req->getMethod() == 'POST')
{
$check = $req->request->get($param);
if ($check == null)
$missing[] = $param;
}
else
return $params;
}
return $missing;
}
protected function checkParamsAndKey(Request $req, $params)
{
$data = [];
// check for api_key in query string
$api_key = $req->query->get('api_key');
if (empty($api_key))
{
$data = [
'error' => 'Missing API key'
];
return $data;
}
// check missing parameters
$missing = $this->checkMissingParameters($req, $params);
if (count($missing) > 0)
{
$miss_string = implode(', ', $missing);
$data = [
'error' => 'Missing parameter(s): ' . $miss_string
];
return $data;
}
// check api key
$sess = $this->checkAPIKey($req->query->get('api_key'));
if ($sess == null)
{
$data = [
'error' => 'Invalid API Key'
];
return $data;
}
// store session
$this->session = $sess;
return $data;
}
protected function checkAPIKey($api_key)
{
// find the api key (session id)
$session = $this->em->getRepository(RiderSession::class)->find($api_key);
if ($session == null)
return null;
return $session;
}
protected function checkJO(Request $req, $required_params, &$jo = null)
{
// set jo status to in transit
$data = $this->checkParamsAndKey($req, $required_params);
if (isset($data['error']))
return $data;
// are we logged in?
if (!$this->session->hasRider())
{
$data = [
'error' => 'No logged in rider.'
];
return $data;
}
$rider = $this->session->getRider();
// check if we have an active JO
$jo = $rider->getActiveJobOrder();
if ($jo == null)
{
$data = [
'error' => 'No active job order.'
];
return $data;
}
// check if the jo_id sent is the same as our active jo
if ($req->request->get('jo_id') != $jo->getID())
{
$data = [
'error' => 'Job order selected is not active job order.'
];
return $data;
}
return $data;
}
}