Files
dev-chiefworks 47f4fad75c Added Other AP
2022-04-26 11:30:34 -04:00

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);
}
}