545 lines
23 KiB
PHP
545 lines
23 KiB
PHP
<?php
|
|
|
|
class QuoteApi extends Api
|
|
{
|
|
public $apiName = 'quote';
|
|
const CACHE_TIMEOUT = 300; // 5 minutes
|
|
|
|
const UBER_DISTANCE_RESTRICTION = 320;
|
|
const LYFT_DISTANCE_RESTRICTION = 499;
|
|
const SINGAPORE_DISTANCE_RESTRICTION = 48;
|
|
const AUTOCAB_DISTANCE_RESTRICTION = 320; // TODO: Check and update late
|
|
|
|
|
|
public function __construct($requestUri, $encryption=true) {
|
|
// $this->cacheWhitelist = [
|
|
// "viewAction" => ['ttl' => 300], // 300 sec. = 5 min.
|
|
// "createAction" => ['ttl' => 300] // 300 sec. = 5 min.
|
|
// ];
|
|
parent::__construct($requestUri, $encryption);
|
|
}
|
|
public function indexAction() {
|
|
$origin = $this->requestParams["origin"] ?? "";
|
|
$destination = $this->requestParams["destination"] ?? "";
|
|
$transport_provider_id = $this->requestParams["transport_provider_id"] ?? "";
|
|
$limit = 10;
|
|
$offset = 0;
|
|
if ($destination && $origin) {
|
|
$db =new Db();
|
|
list($total, $quotes) = Quotes::getByOriginDestinationId(
|
|
$db->getConnect(),
|
|
$origin, $destination, $transport_provider_id,
|
|
$limit, $offset
|
|
);
|
|
if(is_array($quotes) && count($quotes)>0) {
|
|
return $this->response(array(
|
|
'origin' => $origin,
|
|
'destination' => $destination,
|
|
'count' => count($quotes),
|
|
'limit' => $limit,
|
|
'offset' => $offset,
|
|
'total' => $total,
|
|
'quotes' => $quotes
|
|
), 200);
|
|
}
|
|
}
|
|
return $this->response(
|
|
array(
|
|
'error' => 'Data not found'
|
|
), 404);
|
|
}
|
|
|
|
/**
|
|
* Method GET
|
|
* Get single record (by id)
|
|
* http://DOMAIN/quote/1
|
|
* @return string
|
|
*/
|
|
public function viewAction() {
|
|
//id must be the first parameter after /trips/x
|
|
$id = array_shift($this->requestUri);
|
|
$message = 'Data not found';
|
|
$code = 404;
|
|
try {
|
|
if($id && (int)$id>0) {
|
|
$db = new Db();
|
|
$quote = QuoteApi::viewReal($db, $id);
|
|
if (is_array($quote)) {
|
|
return $this->response($quote, 200);
|
|
}
|
|
}
|
|
} catch (Exception $e) {
|
|
$code = 500;
|
|
$messge = $e->getMessage();
|
|
}
|
|
return $this->response(
|
|
array(
|
|
'error'=> $message
|
|
), $code);
|
|
}
|
|
|
|
public static function viewReal($db, $id) {
|
|
$quote = Quotes::getById($db->getConnect(), (int)$id);
|
|
if(is_array($quote) && count($quote)>0) {
|
|
$log = [
|
|
'message' => 'viewReal quote',
|
|
'data' => $quote
|
|
];
|
|
Logger::debug($log);
|
|
$quote["deeplink"] = Quotes::deeplink($db->getConnect(), $quote);
|
|
// Check "completed"
|
|
if ($quote["completed"]!="" && $quote['travel_date']!="" && (int)$quote["cost"]!=0) {
|
|
/* $log = [
|
|
'message' => 'completed quote',
|
|
'data' => $quote
|
|
];
|
|
Logger::debug($log); */
|
|
return $quote; // OK!
|
|
}
|
|
// Call service if needed
|
|
if ($quote["automation_id"]>0) {
|
|
list($quoteService, $err) = QuoteAPI::checkQuote($quote["automation_id"]);
|
|
// Update
|
|
if (is_array($quoteService) && $quoteService["complete"]!="") {
|
|
$quoteService["automation_id"] = $quoteService["id"];
|
|
$quoteService["id"] = (int)$id;
|
|
$quoteService["completed"] = "now()";
|
|
$timezone = Address::getTzByAddressId($db->getConnect(),$quote["location_start_id"]);
|
|
$quoteService["travel_date"] = Utilities::convertUtcToLocal($quoteService["complete"],$timezone);
|
|
$quoteService["created"] = $quote["created"];
|
|
$quoteService["location_start_id"] = $quote["location_start_id"];
|
|
$quoteService["location_end_id"] = $quote["location_end_id"];
|
|
$quoteService["member_id"] = $quote["member_id"];
|
|
$quoteService["deeplink"] = $quote["deeplink"];
|
|
$log = [
|
|
'message' => 'QuoteApi::viewReal update',
|
|
'data' =>$quoteService
|
|
];
|
|
Logger::debug($log);
|
|
list ($res, $err) = Quotes::update($db->getConnect(), $quoteService);
|
|
/* $log = [
|
|
'message' => 'QuoteApi::viewReal update response',
|
|
'data' =>$res,
|
|
'error' =>$err
|
|
];
|
|
Logger::debug($log); */
|
|
if ($quoteService["cost"]==0 &&
|
|
$quoteService["message"]=="android_automation_job_detail") {
|
|
// We must take the details
|
|
$quoteService = QuoteApi::quoteDetails($db, $quoteService);
|
|
return $quoteService;
|
|
}
|
|
return $quoteService;
|
|
}
|
|
}
|
|
/* $log = [
|
|
'message' => 'can not checkquote from android_automation',
|
|
'data' =>$quote
|
|
];
|
|
Logger::debug($log); */
|
|
return $quote;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public function quoteDetails($db, $quoteService) {
|
|
list($details,$err) = self::getQuoteDetails($quoteService["automation_id"]);
|
|
if ($details==NULL) {
|
|
error_log($err);
|
|
$quoteService['error'] = $err;
|
|
}
|
|
//$quoteService['details'] = $details;
|
|
$low_estimate = $quoteService['cost'];
|
|
foreach ($details as $item) {
|
|
$c_low_estimate = sprintf("%0.02f", 0.01*$item["low_estimate"]);
|
|
// We show GrabShare only for now!
|
|
if ($c_low_estimate==0 || $item["name"]!='JustGrab') continue;
|
|
if ($low_estimate==0 || $low_estimate>$c_low_estimate) {
|
|
error_log("quoteDetails: ".$item["name"]." => ".$c_low_estimate);
|
|
$low_estimate = $c_low_estimate;
|
|
}
|
|
}
|
|
$quoteService['cost'] = $low_estimate;
|
|
// Update cost
|
|
$log = [
|
|
'message' => 'get automation detail and update quotes',
|
|
'data' =>$quoteService
|
|
];
|
|
Logger::debug($log);
|
|
list ($res, $err) = Quotes::update($db->getConnect(), $quoteService);
|
|
/* $log = [
|
|
'message' => 'QuoteApi::getQuoteDetails update response',
|
|
'data' =>$res,
|
|
'error' =>$err
|
|
];
|
|
Logger::debug($log); */
|
|
return $quoteService;
|
|
}
|
|
|
|
public static function createReal($db, $origin, $destination, $member_id, $transport_provider_id, $quote_group_id, $trackedemail_item_id, $country=GeocodeApi::DEFAULT_COUNTRY_CODE, $prefill='f',$pool=1) {
|
|
|
|
$originStr = is_array($origin) ? json_encode($origin) : $origin;
|
|
$destinationStr = is_array($destination) ? json_encode($destination) : $destination;
|
|
syslog(LOG_WARNING,"QuoteApi::createReal(\$db, $originStr, $destinationStr, $member_id, $transport_provider_id, $quote_group_id, $trackedemail_item_id, $country, $prefill, $pool");
|
|
$message = "Failed to schedule quote";
|
|
$code = 500;
|
|
$action = "";
|
|
$data = [];
|
|
try {
|
|
if (!in_array($transport_provider_id, [1,2,3,4,5,8])) {
|
|
throw new Exception('Unsupported transport provider ID');
|
|
}
|
|
if (!$destination || !$origin) {
|
|
throw new Exception('Invalid origin and/or destination address');
|
|
}
|
|
|
|
$originData= NULL;
|
|
$destinationData = NULL;
|
|
|
|
if (is_array($origin)) {
|
|
$address = $origin["address"];
|
|
if (0 === count(array_diff(['lat', 'lng', 'address', 'postal', 'timeZoneId', 'country'], array_keys($origin)))) {
|
|
$originData = $origin;
|
|
}
|
|
$origin = $address;
|
|
}
|
|
if (is_array($destination)) {
|
|
$address = $destination["address"];
|
|
if (0 === count(array_diff(['lat', 'lng', 'address', 'postal', 'timeZoneId', 'country'], array_keys($destination)))) {
|
|
$destinationData = $destination;
|
|
}
|
|
$destination = $address;
|
|
}
|
|
|
|
// Check if exists
|
|
if (isset($originData) && isset($destinationData)) {
|
|
$quoteUpdate = Quotes::getLastQuoteByOriginDestinationCoordinates($db->getConnect(),
|
|
$originData, $destinationData, $transport_provider_id,SELF::CACHE_TIMEOUT);
|
|
} else {
|
|
$quoteUpdate = Quotes::getLastQuoteByOriginDestinationId($db->getConnect(),
|
|
$origin, $destination, $transport_provider_id,SELF::CACHE_TIMEOUT);
|
|
}
|
|
|
|
/* $log = [
|
|
'message' => 'QuoteApi::createReal getLastQuoteByOriginDestinationId',
|
|
'quote' => $quoteUpdate
|
|
];
|
|
Logger::debug($log); */
|
|
|
|
if(isset($quoteUpdate)) {
|
|
$id = $quoteUpdate["id"];
|
|
$completed = $quoteUpdate["completed"];
|
|
if(empty($quoteUpdate['travel_date']) && $completed != "") {
|
|
if (isset($originData)) {
|
|
$timezone = $originData['timeZoneId'];
|
|
} else {
|
|
$timezone = Address::getTzByAddressId($db->getConnect(),$quoteUpdate["location_start_id"]);
|
|
}
|
|
$quoteUpdate["travel_date"] = Utilities::convertUtcToLocal($completed,$timezone);
|
|
|
|
list ($res, $err) = Quotes::update($db->getConnect(), $quoteUpdate);
|
|
/* $log = [
|
|
'message' => 'QuoteApi::createReal getQuoteFromCache update response',
|
|
'data' => $res,
|
|
'errror' => $err
|
|
];
|
|
Logger::debug($log); */
|
|
}
|
|
return ["Quote from cache",200,"view",["id"=>$id]];
|
|
}
|
|
|
|
// Geocode origin and destination
|
|
$headers = getallheaders();
|
|
$client_id = isset($headers['client_id'])?$headers['client_id']:"Unknown";
|
|
if (!isset($originData)) {
|
|
list($originData, $err) = GeocodeAPI::geocode($db, $origin, $country,$client_id);
|
|
|
|
/* $log = [
|
|
'message' => 'QuoteApi::geocode origin',
|
|
'data' => ['client_id'=>$client_id, 'address_input'=>$origin],
|
|
'errror' => $err
|
|
];
|
|
Logger::debug($log); */
|
|
if ($err || !isset($originData["lat"])) throw new Exception("Origin geocoding failed: ".$err);
|
|
}
|
|
|
|
if (!isset($destinationData)) {
|
|
list($destinationData, $err) = GeocodeAPI::geocode($db, $destination, $country,$client_id);
|
|
/* $log = [
|
|
'message' => 'QuoteApi::geocode destination',
|
|
'data' => ['client_id'=>$client_id, 'address_input'=>$destination],
|
|
'errror' => $err
|
|
];
|
|
Logger::debug($log); */
|
|
if ($err || !isset($destinationData["lat"])) throw new Exception("Destination geocoding failed: ".$err);
|
|
}
|
|
|
|
|
|
$availableTransportProviders = Gis::getCityServicesAvailableForCoordinates($db->getConnect(), $originData["lat"], $originData["lng"],[$transport_provider_id]);
|
|
if (count($availableTransportProviders) == 0) {
|
|
$availableTransportProviders = Gis::getCountryServicesAvailableForCoordinates( $db->getConnect(), $originData["lat"], $originData["lng"],[$transport_provider_id]);
|
|
}
|
|
|
|
if (count($availableTransportProviders) == 0) {
|
|
$message = "No transport providers available for origin coordinates (".$originData["lat"].",".$originData["lng"].").";
|
|
throw new Exception($message);
|
|
}
|
|
|
|
$distance = Gis::cosinesDistanceBetweenTwoGpsCoordinates($originData["lat"], $originData["lng"], $destinationData["lat"], $destinationData["lng"], "K");
|
|
|
|
$isDistanceRestricted = false;
|
|
switch ($transport_provider_id) {
|
|
case Quotes::VENDOR_UBER:
|
|
$isDistanceRestricted = $distance > QuoteApi::UBER_DISTANCE_RESTRICTION;
|
|
break;
|
|
case Quotes::VENDOR_LYFT:
|
|
$isDistanceRestricted = $distance > QuoteApi::LYFT_DISTANCE_RESTRICTION;
|
|
break;
|
|
case Quotes::VENDOR_GRAB: case Quotes::VENDOR_GOJEK: case Quotes::VENDOR_COMFORTDELGRO:
|
|
$isDistanceRestricted = $distance > QuoteApi::SINGAPORE_DISTANCE_RESTRICTION;
|
|
break;
|
|
case Quotes::VENDOR_AUTOCAB:
|
|
$isDistanceRestricted = $distance > QuoteApi::AUTOCAB_DISTANCE_RESTRICTION;
|
|
break;
|
|
}
|
|
|
|
if ($isDistanceRestricted) {
|
|
$message = "Distance restriction. Could not get quote for (".$originData["lat"].",".$originData["lng"].") and (".$destinationData["lat"].",".$destinationData["lng"]."). The distance (".$distance." km) too long.";
|
|
throw new Exception($message);
|
|
}
|
|
|
|
$isOriginWithin = false;
|
|
$isDestinationWithin = false;
|
|
// Check if we are within Singapore: for Grab, Gojek, CDG
|
|
if ($transport_provider_id>2 && $transport_provider_id<6) {
|
|
$city = 'Singapore';
|
|
$sgLat = 1.352083;
|
|
$sgLng = 103.819836;
|
|
$radius = 25; // km
|
|
$isOriginWithin = GeocodeAPI::withinCity(
|
|
$originData["address"],$originData["lat"],$originData["lng"],
|
|
$city,$sgLat,$sgLng,$radius);
|
|
$isDestinationWithin = GeocodeAPI::withinCity(
|
|
$destinationData["address"],$destinationData["lat"],$destinationData["lng"],
|
|
$city,$sgLat,$sgLng,$radius);
|
|
}
|
|
// Uber, Lyft and Autocab: no check required
|
|
if (in_array($transport_provider_id, [1,2,8])) {
|
|
$isOriginWithin = true;
|
|
$isDestinationWithin = true;
|
|
}
|
|
|
|
if ($isOriginWithin && $isDestinationWithin) {
|
|
syslog(LOG_WARNING,"'".$originData["address"]."' and '".$destinationData["address"]."' are within Singapore");
|
|
} else {
|
|
throw new Exception("Origin and/or destination is not within service area!");
|
|
}
|
|
// Schedule the quote
|
|
$requestData = array(
|
|
"location_start" => $originData["address"] ?? $origin,
|
|
"location_end" => $destinationData["address"] ?? $destination,
|
|
"trackedemail_item_id" => $trackedemail_item_id,
|
|
"transport_provider_id" => $transport_provider_id,
|
|
"location_start_lat" => $originData["lat"],
|
|
"location_start_lng" => $originData["lng"],
|
|
"location_start_country" => $originData["country"],
|
|
"location_end_lat" => $destinationData["lat"],
|
|
"location_end_lng" => $destinationData["lng"],
|
|
"location_end_country" => $destinationData["country"],
|
|
"pool" => $pool
|
|
);
|
|
list($res, $err) = QuoteAPI::scheduleQuote($requestData);
|
|
if ($res==NULL || !isset($res["id"])) {
|
|
throw new Exception("Quote schedule failed: " . ($err ?? $message));
|
|
}
|
|
// Save to DB
|
|
$requestData["location_start_tz"] = $originData["timeZoneId"];
|
|
$requestData["location_start_postal"] = $originData["postal"];
|
|
$requestData["location_start_country"] = $originData["country"];
|
|
$requestData["location_end_tz"] = $destinationData["timeZoneId"];
|
|
$requestData["location_end_postal"] = $destinationData["postal"];
|
|
$requestData["location_end_country"] = $destinationData["country"];
|
|
$requestData["automation_id"] = $res["id"];
|
|
$requestData["member_id"] = (int)$member_id;
|
|
$requestData["quote_group_id"] = (int)$quote_group_id;
|
|
$requestData["prefill"] = $prefill;
|
|
$requestData["pool"] = $pool;
|
|
error_log('quote_group_id='.((int)$quote_group_id));
|
|
unset($requestData["trackedemail_item_id"]);
|
|
$log = [
|
|
'message' => 'createReal quote',
|
|
'data' =>$requestData
|
|
];
|
|
Logger::debug($log);
|
|
list($quote,$err) = Quotes::create($db->getConnect(), $requestData);
|
|
/* $log = [
|
|
'message' => 'createReal Quotes::create response',
|
|
'quote' => $quote
|
|
];
|
|
Logger::debug($log); */
|
|
if ($quote!=NULL && is_array($quote)) {
|
|
$quote["deeplink"] = Quotes::deeplink($db->getConnect(), $quote, $requestData);
|
|
return ["Quote scheduled",200,"response",$quote];
|
|
} else {
|
|
$message = "Failed to create quote record: ".$err;
|
|
}
|
|
} catch (Exception $e) {
|
|
syslog(LOG_WARNING,json_encode($e));
|
|
$message = $e->getMessage();
|
|
}
|
|
syslog(LOG_WARNING,$message);
|
|
return [$message,$code,$action,$data];
|
|
}
|
|
|
|
public function createAction() {
|
|
syslog(LOG_WARNING,"QuoteApi::createAction()");
|
|
$origin = $this->requestParams["origin"] ?? "";
|
|
$destination = $this->requestParams["destination"] ?? "";
|
|
$member_id = $this->requestParams["member_id"] ?? 0;
|
|
$country = $this->requestParams["country"] ?? GeocodeApi::DEFAULT_COUNTRY_CODE;
|
|
$transport_provider_id = $this->requestParams["transport_provider_id"] ?? 0;
|
|
$group_quote_id = $this->requestParams["group_quote_id"] ?? 0;
|
|
$trackedemail_item_id = $this->requestParams["trackedemail_item_id"] ?? 0;
|
|
$prefill = $this->requestParams["prefill"] ?? 'f';
|
|
$pool = $this->requestParams["pool"] ?? 1;
|
|
|
|
if ($prefill!='t') $prefill = 'f';
|
|
// if ($pool!=2) $pool = 1; // 1 - main pool, 2 - dedicated pool
|
|
|
|
$db = new Db();
|
|
|
|
// DEBUG
|
|
$country = Geocode::mockGPSCountry($db->getConnect(), $member_id, $country, 'default');
|
|
|
|
list($message,$code,$action,$data) = QuoteApi::createReal(
|
|
$db, $origin, $destination, $member_id, $transport_provider_id,
|
|
$group_quote_id, $trackedemail_item_id, $country, $prefill, $pool
|
|
);
|
|
syslog(LOG_WARNING,"message=${message}");
|
|
syslog(LOG_WARNING,"code=${code}");
|
|
syslog(LOG_WARNING,"action=${action}");
|
|
syslog(LOG_WARNING,"data=".json_encode($data));
|
|
if ($code==200) {
|
|
if ($action=="view") {
|
|
$id = $data["id"];
|
|
array_unshift($this->requestUri, $id);
|
|
return $this->viewAction();
|
|
} else if ($action=="response") {
|
|
return $this->response($data, 200);
|
|
}
|
|
}
|
|
return $this->response(
|
|
array(
|
|
"error" => $message
|
|
), $code);
|
|
}
|
|
|
|
public function updateAction()
|
|
{
|
|
return $this->response(
|
|
array(
|
|
"error" => "Update error"
|
|
), 400);
|
|
}
|
|
|
|
public function deleteAction()
|
|
{
|
|
return $this->response(
|
|
array(
|
|
"error" => "Delete error"
|
|
), 500);
|
|
}
|
|
|
|
public static function getRideshareQuote($db, $origin, $destination, $member_id, $transport_provider_id, $country) {
|
|
$group_quote_id = 0;
|
|
$trackedemail_item_id = 0;
|
|
$prefill = 'f';
|
|
$id = 0;
|
|
list($message,$code,$action,$data) = QuoteApi::createReal(
|
|
$db, $origin, $destination, $member_id, $transport_provider_id,
|
|
$group_quote_id, $trackedemail_item_id, $country, $prefill
|
|
);
|
|
if ($code==200) {
|
|
// Do we have a cost? Most likely not now...
|
|
if (is_array($data) && array_key_exists("cost",$data)) {
|
|
return [$data["cost"],$data["id"]];
|
|
}
|
|
if ($action=="view") {
|
|
// "View" quote
|
|
sleep(5);
|
|
$quote = QuoteApi::viewReal($db, $data["id"]);
|
|
if (is_array($quote) && array_key_exists("cost",$quote)) {
|
|
return [$quote["cost"],$data["id"]];
|
|
}
|
|
}
|
|
if (is_array($data) && array_key_exists("id",$data) && $data["id"]>0) {
|
|
$id = $data["id"]; // No id - no way to check what is going on
|
|
sleep(5); // wait a second...
|
|
// Try to "view" quote
|
|
$quote = QuoteApi::viewReal($db, $data["id"]);
|
|
if (is_array($quote) && array_key_exists("cost",$quote)) {
|
|
return [$quote["cost"],$data["id"]];
|
|
}
|
|
}
|
|
}
|
|
return [0,$id];
|
|
}
|
|
|
|
public function checkQuote($id) {
|
|
return Quotes::checkQuote($id);
|
|
}
|
|
|
|
public function getQuoteDetails($id) {
|
|
return Quotes::getQuoteDetails($id);
|
|
}
|
|
|
|
/*
|
|
curl -d '{
|
|
"location_start":"Marina Bay Sands, 10 Bayfront Ave, Singapore 018956",
|
|
"location_end":"97 Meyer Road, Singapore",
|
|
"trackedemail_item_id":"15549",
|
|
"transport_provider_id":"3",
|
|
"location_start_lat":"1.2833754",
|
|
"location_start_lng":"103.8607264",
|
|
"location_end_lat":"1.2967624",
|
|
"location_end_lng":"103.8927854"
|
|
}' \
|
|
-H "Authorization: Server-Token 99dfe35fcb7de1ee" -H "Content-Type: application/json" \
|
|
-X POST http://automation.float.sg/api/automation-job
|
|
*/
|
|
public function scheduleQuote($data) {
|
|
syslog(LOG_WARNING,'QuoteApi::scheduleQuote($data)');
|
|
global $savvyext;
|
|
$httpAuthToken = $savvyext->cfgReadChar('system.automation_api_token');
|
|
$automation_api_url = $savvyext->cfgReadChar('system.automation_api_url');
|
|
|
|
$url = $automation_api_url."automation-job/";
|
|
$opts = array(
|
|
'http' => array(
|
|
'method' => "POST",
|
|
'header' =>
|
|
"Content-Type: application/json\r\n" .
|
|
"Accept: application/json\r\n" .
|
|
"Authorization: Server-Token ${httpAuthToken}\r\n",
|
|
'content' => json_encode($data)
|
|
),
|
|
"ssl" => array(
|
|
"verify_peer"=>false,
|
|
"verify_peer_name"=>false,
|
|
)
|
|
);
|
|
$context = stream_context_create($opts);
|
|
$body = file_get_contents($url, false, $context);
|
|
$schedule = json_decode($body,true);
|
|
if (is_array($schedule) && isset($schedule["id"])) {
|
|
return array($schedule, NULL);
|
|
} else if (is_array($schedule) && isset($schedule["message"])) {
|
|
$body = $schedule["message"];
|
|
syslog(LOG_WARNING,isset($schedule["error"]) ? json_encode($schedule["error"]) : $schedule["message"]);
|
|
}
|
|
syslog(LOG_WARNING,$body);
|
|
return array(NULL, $body);
|
|
}
|
|
|
|
}
|