resq/src/Controller/APIController.php
2022-03-24 05:38:18 +00:00

4695 lines
153 KiB
PHP

<?php
namespace App\Controller;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Doctrine\DBAL\DBALException;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Dotenv\Dotenv;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpKernel\KernelInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use App\Ramcar\APIResult;
use App\Ramcar\JOStatus;
use App\Ramcar\InvoiceCriteria;
use App\Ramcar\ServiceType;
use App\Ramcar\WarrantyClass;
use App\Ramcar\APIRiderStatus;
use App\Ramcar\TransactionOrigin;
use App\Ramcar\TradeInType;
use App\Ramcar\JOEventType;
use App\Ramcar\AdvanceOrderSlot;
use App\Ramcar\AutoAssignStatus;
use App\Ramcar\WarrantySource;
use App\Ramcar\HubCriteria;
use App\Ramcar\CustomerSource;
use App\Ramcar\DeliveryStatus;
use App\Service\InvoiceGeneratorInterface;
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\Service\RiderAssignmentHandlerInterface;
use App\Service\WarrantyAPILogger;
use App\Service\PromoLogger;
use App\Service\HubSelector;
use App\Service\HubDistributor;
use App\Service\HubFilterLogger;
use App\Service\HubFilteringGeoChecker;
use App\Service\HashGenerator;
use App\Entity\MobileSession;
use App\Entity\Customer;
use App\Entity\VehicleManufacturer;
use App\Entity\Vehicle;
use App\Entity\CustomerVehicle;
use App\Entity\JobOrder;
use App\Entity\Promo;
use App\Entity\Battery;
use App\Entity\RiderRating;
use App\Entity\JOEvent;
use App\Entity\Warranty;
use App\Entity\Service;
use App\Entity\Partner;
use App\Entity\Review;
use App\Entity\PrivacyPolicy;
use App\Entity\Hub;
use App\Entity\SAPBattery;
use App\Entity\WarrantySerial;
use App\Entity\CustomerMetadata;
use DateTime;
use DateInterval;
use Exception;
// mobile API
class APIController extends Controller implements LoggedController
{
const RIDER_SLOT_ARRAY_LENGTH = 8;
protected $session;
public function __construct()
{
$this->session = null;
}
protected function debugRequest(Request $req)
{
$all = $req->request->all();
error_log(print_r($all, true));
}
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 (empty($check))
$missing[] = $param;
}
else if ($req->getMethod() == 'POST')
{
$check = $req->request->get($param);
if (empty($check))
$missing[] = $param;
}
else
return $params;
}
return $missing;
}
// TODO: type hint entity manager
protected function checkAPIKey($em, $api_key)
{
// find the api key (session id)
$session = $em->getRepository(MobileSession::class)->find($api_key);
if ($session == null)
return null;
return $session;
}
protected function checkParamsAndKey(Request $req, $em, $params)
{
// returns APIResult object
$res = new APIResult();
// check for api_key in query string
$api_key = $req->query->get('api_key');
if (empty($api_key))
{
$res->setError(true)
->setErrorMessage('Missing API key');
return $res;
}
// check missing parameters
$missing = $this->checkMissingParameters($req, $params);
if (count($missing) > 0)
{
$miss_string = implode(', ', $missing);
$res->setError(true)
->setErrorMessage('Missing parameter(s): ' . $miss_string);
return $res;
}
// check api key
$sess = $this->checkAPIKey($em, $req->query->get('api_key'));
if ($sess == null)
{
$res->setError(true)
->setErrorMessage('Invalid API Key');
return $res;
}
// store session
$this->session = $sess;
return $res;
}
public function register(Request $req)
{
$res = new APIResult();
// confirm parameters
$required_params = [
'phone_model',
'os_type',
'os_version',
'phone_id'
];
$missing = $this->checkMissingParameters($req, $required_params);
if (count($missing) > 0)
{
$params = implode(', ', $missing);
$res->setError(true)
->setErrorMessage('Missing parameter(s): ' . $params);
return $res->getReturnResponse();
}
$em = $this->getDoctrine()->getManager();
// retry until we get a unique id
while (true)
{
try
{
// instantiate session
$sess = new MobileSession();
$sess->setPhoneModel($req->request->get('phone_model'))
->setOSType($req->request->get('os_type'))
->setOSVersion($req->request->get('os_version'))
->setPhoneID($req->request->get('phone_id'));
// reopen in case we get an exception
if (!$em->isOpen())
{
$em = $em->create(
$em->getConnection(),
$em->getConfiguration()
);
}
// save
$em->persist($sess);
$em->flush();
}
catch (DBALException $e)
{
error_log($e->getMessage());
// delay one second and try again
sleep(1);
continue;
}
break;
}
// return data
$data = [
'session_id' => $sess->getID()
];
$res->setData($data);
// response
return $res->getReturnResponse();
}
protected function generateConfirmCode()
{
return sprintf("%06d", mt_rand(100000, 999999));
}
protected function sendConfirmationCode(RisingTideGateway $rt, $phone_number, $code, TranslatorInterface $translator)
{
// send sms to number
$message = $translator->trans('message.confirmation_code') . ' ' . $code;
$rt->sendSMS($phone_number, $translator->trans('message.battery_brand_allcaps'), $message);
}
public function confirmNumber(RisingTideGateway $rt, Request $req)
{
// check parameters
$required_params = [
'phone_number',
];
// check required parameters and api key
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// phone number
$phone_number = $req->request->get('phone_number');
// get otp_mode from .env
$dotenv = new Dotenv();
$dotenv->loadEnv(__DIR__.'/../../.env');
$otp_mode = $_ENV['OTP_MODE'];
// check for hardcoded phone number for app store testing
if ($phone_number == '639221111111')
{
$code = '123456';
$this->session->setConfirmCode($code)
->setPhoneNumber($phone_number);
$em->flush();
return $res->getReturnResponse();
}
// check if otp_mode is test
if ($otp_mode == 'test')
{
$code = '123456';
$this->session->setConfirmCode($code)
->setPhoneNumber($phone_number);
$em->flush();
return $res->getReturnResponse();
}
// TODO: spam protection
// TODO: validate phone number
// generate code and save
$code = $this->generateConfirmCode();
$this->session->setConfirmCode($code)
->setPhoneNumber($phone_number);
$em->flush();
if ($otp_mode != 'test')
{
// send sms to number
$this->sendConfirmationCode($rt, $phone_number, $code);
}
// response
return $res->getReturnResponse();
}
// TODO: find session customer by phone number
protected function findNumberSession($number)
{
$em = $this->getDoctrine()->getManager();
$query = $em->getRepository(MobileSession::class)->createQueryBuilder('s')
->where('s.phone_number = :number')
->andWhere('s.customer is not null')
->andWhere('s.confirm_flag = 1')
->setParameter('number', $number)
->setMaxResults(1)
->getQuery();
// we just need one
$res = $query->getOneOrNullResult();
return $res;
}
public function validateCode(Request $req)
{
// check parameters
$required_params = [
'code',
];
// check required parameters and api key
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// code is wrong
$code = $req->request->get('code');
if ($this->session->getConfirmCode() != $code)
{
$res->setError(true)
->setErrorMessage('Wrong confirm code');
return $res->getReturnResponse();
}
// set confirm date
$date = new DateTime();
$this->session->setDateConfirmed($date)
->setConfirmed();
// TODO: check if we have the number registered before and merge
$dupe_sess = $this->findNumberSession($this->session->getPhoneNumber());
if ($dupe_sess != null)
{
$dupe_cust = $dupe_sess->getCustomer();
$this->session->setCustomer($dupe_cust);
}
// TODO: check if mobile matches mobile of customer
$customer = $this->findCustomerByNumber($this->session->getPhoneNumber());
if ($customer != null)
{
// TODO: if there is a dupe_sess, do we need to check if
// dupe_cust is the same as the customer we found?
$this->session->setCustomer($customer);
}
$em->flush();
// response
return $res->getReturnResponse();
}
public function getInfo(Request $req)
{
// check required parameters and api key
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// if no customer found
$cust = $this->session->getCustomer();
if ($cust == null)
{
$data = [
'first_name' => '',
'last_name' => '',
'priv_third_party' => (bool) false,
'priv_promo' => (bool) false,
];
$res->setData($data);
return $res->getReturnResponse();
}
// send back customer details
$data = [
'first_name' => $cust->getFirstName(),
'last_name' => $cust->getLastName(),
'priv_third_party' => (bool) $cust->getPrivacyThirdParty(),
'priv_promo' => (bool) $cust->getPrivacyPromo(),
];
$res->setData($data);
return $res->getReturnResponse();
}
protected function updateCustomerInfo($req, $em)
{
// create new customer if it's not there
$cust = $this->session->getCustomer();
if ($cust == null)
{
$cust = new Customer();
// set customer source
$cust->setCreateSource(CustomerSource::MOBILE);
$em->persist($cust);
$this->session->setCustomer($cust);
}
$cust->setFirstName($req->request->get('first_name'))
->setLastName($req->request->get('last_name'))
->setEmail($req->request->get('email', ''))
->setConfirmed($this->session->isConfirmed());
// update mobile phone of customer
$cust->setPhoneMobile(substr($this->session->getPhoneNumber(), 2));
return $cust;
}
public function updateInfo(Request $req, EntityManagerInterface $em)
{
// check required parameters and api key
$required_params = [
'first_name',
'last_name',
];
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
$cust = $this->updateCustomerInfo($req, $em);
// get privacy policy for mobile
$dotenv = new Dotenv();
$dotenv->loadEnv(__DIR__.'/../../.env');
$policy_mobile_id = $_ENV['POLICY_MOBILE'];
$mobile_policy = $em->getRepository(PrivacyPolicy::class)->find($policy_mobile_id);
// set policy id
if ($mobile_policy != null)
{
$cust->setPrivacyPolicyMobile($mobile_policy);
}
$em->flush();
return $res->getReturnResponse();
}
public function getStatus(Request $req)
{
// check required parameters and api key
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// set data
$data = [];
if ($this->session->isConfirmed())
$data['status'] = 'confirmed';
else
$data['status'] = 'unconfirmed';
$res->setData($data);
return $res->getReturnResponse();
}
public function listVehicleManufacturers(Request $req)
{
// check required parameters and api key
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get manufacturer list
$mfgs = $em->getRepository(VehicleManufacturer::class)->findBy(['flag_mobile' => true], ['name' => 'asc']);
$mfg_list = [];
foreach ($mfgs as $mfg)
{
$mfg_list[] = [
'id' => $mfg->getID(),
'name' => $mfg->getName(),
];
}
$data = [
'manufacturers' => $mfg_list
];
$res->setData($data);
return $res->getReturnResponse();
}
public function listVehicleMakes(Request $req, $mfg_id)
{
// check required parameters and api key
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get manufacturer
$mfg = $em->getRepository(VehicleManufacturer::class)->find($mfg_id);
if ($mfg == null)
{
$res->setError(true)
->setErrorMessage('Invalid vehicle manufacturer id');
return $res->getReturnResponse();
}
// get makes
$vehicles = $em->getRepository(Vehicle::class)->findBy(
[
'flag_mobile' => true,
'manufacturer' => $mfg_id,
],
['make' => 'asc']
);
// $vehicles = $mfg->getVehicles();
$vlist = [];
foreach ($vehicles as $v)
{
$vlist[] = [
'id' => $v->getID(),
'make' => trim($v->getMake() . ' ' . $v->getModelYearFormatted(false)),
// 'make' => $v->getMake() . ' ' . $v->getModelYearFrom() . '-' . $v->getModelYearTo(),
];
}
$data = [
'manufacturer' => [
'id' => $mfg->getID(),
'name' => $mfg->getName(),
],
'makes' => $vlist,
];
$res->setData($data);
return $res->getReturnResponse();
}
protected function checkVehicleRequirements(Request $req)
{
// check required parameters and api key
$required_params = [
'make_id',
'name',
'plate_num',
'model_year',
'color',
'condition',
'fuel_type',
];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res;
// TODO: check valid plate number
// TODO: check valid fuel type (gas / diesel)
// TODO: check current battery id
// TODO: check condition (brand new / second-hand)
// TODO: check is_motolite and is_active (1 or 0)
// TODO: check warranty expiration date (YYYYMMDD)
// TODO: check model year coverage if it fits in between
return $res;
}
protected function setCustomerVehicleObject(Request $req, APIResult $res, CustomerVehicle $cv)
{
$em = $this->getDoctrine()->getManager();
// check customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res;
}
// get vehicle
$vehicle = $em->getRepository(Vehicle::class)->find($req->request->get('make_id'));
if ($vehicle == null)
{
$res->setError(true)
->setErrorMessage('Invalid vehicle make id');
return $res;
}
$cv->setCustomer($cust)
->setVehicle($vehicle)
->setName($req->request->get('name'))
->setPlateNumber($req->request->get('plate_num'))
->setModelYear($req->request->get('model_year'))
->setColor($req->request->get('color'))
->setFuelType($req->request->get('fuel_type'))
->setStatusCondition($req->request->get('condition'));
// set warranty code and expiration
// TODO: check warranty requirements
if (!empty($req->request->get('wty_code')))
$cv->setWarrantyCode($req->request->get('wty_code'));
if (!empty($req->request->get('wty_expire')))
$cv->setWarrantyExpiration(new DateTime($req->request->get('wty_expire')));
// TODO: get current battery
// is motolite
if ($req->request->get('is_motolite') == 0)
$cv->setHasMotoliteBattery(false);
else
$cv->setHasMotoliteBattery(true);
// is active
if ($req->request->get('is_active') == 0)
$cv->setActive(false);
else
$cv->setActive(true);
// save
$em->persist($cv);
$em->flush();
// data
$data = [
'cv_id' => $cv->getID()
];
$res->setData($data);
return $res;
}
public function addVehicle(Request $req)
{
// check requirements
$res = $this->checkVehicleRequirements($req);
if ($res->isError())
return $res->getReturnResponse();
// customer vehicle
$cv = new CustomerVehicle();
$res = $this->setCustomerVehicleObject($req, $res, $cv);
return $res->getReturnResponse();
}
public function updateVehicle(Request $req, $id)
{
// check requirements
$res = $this->checkVehicleRequirements($req);
if ($res->isError())
return $res->getReturnResponse();
// get customer vehicle
$em = $this->getDoctrine()->getManager();
$cv = $em->getRepository(CustomerVehicle::class)->find($id);
// check if it exists
if ($cv == null)
{
$res->setError(true)
->setErrorMessage('Vehicle does not exist');
return $res->getReturnResponse();
}
// check if it's owned by customer
if ($cv->getCustomer()->getID() != $this->session->getCustomer()->getID())
{
$res->setError(true)
->setErrorMessage('Invalid vehicle');
return $res->getReturnResponse();
}
$res = $this->setCustomerVehicleObject($req, $res, $cv);
return $res->getReturnResponse();
}
protected function getBatteryImageURL($req, $batt)
{
// TODO: workaround for now, we get static image of battery based on model name
$filename = trim(strtolower($batt->getModel()->getName())) . '_mobile.jpg';
$file_path = $req->getSchemeAndHttpHost() . $this->generateUrl('static_battery_image') . '/' . $filename;
return $file_path;
}
public function listVehicles(Request $req)
{
// check required parameters and api key
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// vehicles
$cv_list = [];
// $cvs = $cust->getVehicles();
// only get the customer's vehicles whose flag_active is true
$cvs = $em->getRepository(CustomerVehicle::class)->findBy(['flag_active' => true, 'customer' => $cust]);
foreach ($cvs as $cv)
{
$battery_id = null;
if ($cv->getCurrentBattery() != null)
$battery_id = $cv->getCurrentBattery()->getID();
$wty_ex = null;
if ($cv->getWarrantyExpiration() != null)
$wty_ex = $cv->getWarrantyExpiration()->format('Y-m-d');
$warranty = $this->findWarranty($cv->getPlateNumber());
$cv_name = '';
if ($cv->getName() != null)
$cv_name = $cv->getName();
$cv_list[] = [
'cv_id' => $cv->getID(),
'mfg_id' => $cv->getVehicle()->getManufacturer()->getID(),
'make_id' => $cv->getVehicle()->getID(),
'name' => $cv_name,
'plate_num' => $cv->getPlateNumber(),
'model_year' => $cv->getModelYear(),
'color' => $cv->getColor(),
'condition' => $cv->getStatusCondition(),
'fuel_type' => $cv->getFuelType(),
'wty_code' => $cv->getWarrantyCode(),
'wty_expire' => $wty_ex,
'curr_batt_id' => $battery_id,
'is_motolite' => $cv->hasMotoliteBattery() ? 1 : 0,
'is_active' => $cv->isActive() ? 1 : 0,
'warranty' => $warranty,
];
}
// data
$data = [
'vehicles' => $cv_list
];
$res->setData($data);
return $res->getReturnResponse();
}
public function listPromos(Request $req)
{
// check required parameters and api key
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
return $res->getReturnResponse();
}
public function getCompatibleBatteries(Request $req, $vid)
{
// check required parameters and api key
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get vehicle
$vehicle = $em->getRepository(Vehicle::class)->find($vid);
if ($vehicle == null)
{
$res->setError(true)
->setErrorMessage('Invalid vehicle');
return $res->getReturnResponse();
}
// batteries
$batt_list = [];
$batts = $vehicle->getBatteries();
foreach ($batts as $batt)
{
// TODO: Add warranty_tnv to battery information
$batt_list[] = [
'id' => $batt->getID(),
'mfg_id' => $batt->getManufacturer()->getID(),
'mfg_name' => $batt->getManufacturer()->getName(),
'model_id' => $batt->getModel()->getID(),
'model_name' => $batt->getModel()->getName(),
'size_id' => $batt->getSize()->getID(),
'size_name' => $batt->getSize()->getName(),
'price' => $batt->getSellingPrice(),
'wty_private' => $batt->getWarrantyPrivate(),
'wty_commercial' => $batt->getWarrantyCommercial(),
'image_url' => $this->getBatteryImageURL($req, $batt),
];
}
// data
$data = [
'vehicle' => [
'id' => $vehicle->getID(),
'mfg_id' => $vehicle->getManufacturer()->getID(),
'mfg_name' => $vehicle->getManufacturer()->getName(),
'make' => $vehicle->getMake(),
'model_year_from' => $vehicle->getModelYearFrom(),
'model_year_to' => $vehicle->getModelYearTo(),
],
'batteries' => $batt_list,
];
$res->setData($data);
return $res->getReturnResponse();
}
// TODO: remove later
// mobile app no longer calls this
public function requestJobOrder(Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo,
MapTools $map_tools, InventoryManager $im, MQTTClient $mclient,
RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger,
HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger,
HubFilteringGeoChecker $hub_geofence)
{
// check required parameters and api key
$required_params = [
'service_type',
'cv_id',
// 'batt_id',
'trade_in',
'long',
'lat',
'warranty',
'mode_of_payment',
];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// trade in type
$trade_in = $req->request->get('trade_in');
// address
$address = $req->request->get('delivery_address', 'Set by mobile application');
// instructions
$instructions = $req->request->get('delivery_instructions', '');
// longitude and latitude
$long = $req->request->get('long');
$lat = $req->request->get('lat');
// geofence
$is_covered = $geo->isCovered($long, $lat);
if (!$is_covered)
{
// TODO: put geofence error message in config file somewhere
$res->setError(true)
->setErrorMessage('Oops! Our service is limited to some areas in Metro Manila, Laguna, and Baguio only. We will update you as soon as we are able to cover your area');
return $res->getReturnResponse();
}
$jo = new JobOrder();
$jo->setSource(TransactionOrigin::MOBILE_APP)
->setStatus(JOStatus::PENDING)
->setDeliveryInstructions('')
->setTier1Notes('')
->setTier2Notes('')
->setDeliveryAddress($address)
->setTradeInType($trade_in)
->setDeliveryInstructions($instructions)
// TODO: error check for valid mode of payment
->setModeOfPayment($req->request->get('mode_of_payment'));
// customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
$jo->setCustomer($cust);
// validate service type
$stype = $req->request->get('service_type');
if (!ServiceType::validate($stype))
{
$res->setError(true)
->setErrorMessage('Invalid service type');
return $res->getReturnResponse();
}
$jo->setServiceType($stype);
// validate warranty
$warr = $req->request->get('warranty');
if (!WarrantyClass::validate($warr))
{
$res->setError(true)
->setErrorMessage('Invalid warranty class');
return $res->getReturnResponse();
}
$jo->setWarrantyClass($warr);
// set coordinates
$point = new Point($long, $lat);
$jo->setCoordinates($point);
// make invoice criteria
$icrit = new InvoiceCriteria();
$icrit->setServiceType($stype);
// check promo
$promo_id = $req->request->get('promo_id');
if (!empty($promo_id))
{
$promo = $em->getRepository(Promo::class)->find($promo_id);
if ($promo == null)
{
$res->setError(true)
->setErrorMessage('Invalid promo id');
return $res->getReturnResponse();
}
// put in criteria
$icrit->addPromo($promo);
}
// check customer vehicle
$cv = $em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id'));
if ($cv == null)
{
$res->setError(true)
->setErrorMessage('Invalid customer vehicle id');
return $res->getReturnResponse();
}
$icrit->setCustomerVehicle($cv);
$jo->setCustomerVehicle($cv);
// check if customer owns vehicle
if ($cust->getID() != $cv->getCustomer()->getID())
{
$res->setError(true)
->setErrorMessage('Customer does not own vehicle');
return $res->getReturnResponse();
}
// check battery
$batt_id = $req->request->get('batt_id');
if ($batt_id != null)
{
$batt = $em->getRepository(Battery::class)->find($batt_id);
if ($batt == null)
{
$res->setError(true)
->setErrorMessage('Invalid battery id');
return $res->getReturnResponse();
}
}
else
$batt = null;
/*
// put battery in criteria
$icrit->addBattery($batt);
*/
// check trade-in
// only allow motolite, other, none
switch ($trade_in)
{
case TradeInType::MOTOLITE:
case TradeInType::OTHER:
break;
default:
$trade_in = '';
break;
}
$icrit->addEntry($batt, $trade_in, 1);
// send to invoice generator
$invoice = $ic->generateInvoice($icrit);
$jo->setInvoice($invoice);
// set more hub criteria fields
$hub_criteria = new HubCriteria();
$hub_criteria->setPoint($jo->getCoordinates());
// get distance limit for mobile from env
$dotenv = new Dotenv();
$dotenv->loadEnv(__DIR__.'/../../.env');
$limit_distance = $_ENV['CUST_DISTANCE_LIMIT'];
// set distance limit
$hub_criteria->setLimitDistance($limit_distance);
if ($hub_geofence->isCovered($long, $lat))
{
// TODO: set this properly, since the other flags
// are on default values.
// if true, set other values for HubCriteria
error_log('Area is covered by hub filtering');
$hub_criteria->setJoType($jo->getServiceType())
->setPaymentMethod($jo->getModeOfPayment())
->setRoundRobin(true);
}
// add battery to items
$sku = $batt->getSAPCode();
if (!empty($sku))
$hub_criteria->addItem($batt->getSAPCode(), 1);
// get customer id. No JO id at this point
$customer_id = $cust->getID();
$hub_criteria->setCustomerId($customer_id);
// find nearest hubs
$nearest_hubs = $hub_select->find($hub_criteria);
$assigned_rider = null;
if (!empty($nearest_hubs))
{
// go through the hub list, find the nearest hub
// with an available rider
//error_log('found nearest hub ' . $nearest_hub->getID());
foreach ($nearest_hubs as $nearest_hub)
{
$available_riders = $nearest_hub['hub']->getAvailableRiders();
if (count($available_riders) >= 1)
{
if (count($available_riders) == 1)
{
$assigned_rider = $available_riders[0];
}
else
{
// 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);
}
$jo->setHub($nearest_hub['hub']);
$jo->setRider($assigned_rider);
$jo->setStatus(JOStatus::ASSIGNED);
$jo->setStatusAutoAssign(AutoAssignStatus::HUB_AND_RIDER_ASSIGNED);
$assigned_rider->setAvailable(false);
// set rider's current job order
$assigned_rider->setCurrentJobOrder($jo);
// update redis hub_jo_count for hub
$hub_dist->incrementJoCountForHub($nearest_hub['hub']);
// break out of loop
break;
}
else
{
// log hub into hub_filter_log
$hub_filter_logger->logFilteredHub($nearest_hub['hub'], 'no_available_rider', null, $cust->getID());
// continue to go through list to find hub with an available rider
}
}
}
$em->persist($jo);
$em->persist($invoice);
// add event log for JO
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::CREATE)
->setJobOrder($jo);
$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();
// make invoice json data
$invoice_data = [
'total_price' => $invoice->getTotalPrice(),
'vat_ex_price' => (float) $invoice->getVATExclusivePrice(),
'vat' => $invoice->getVAT(),
'discount' => $invoice->getDiscount(),
'trade_in' => $invoice->getTradeIn(),
];
$items = $invoice->getItems();
$items_data = [];
foreach ($items as $item)
{
$items_data[] = [
'title' => $item->getTitle(),
'qty' => $item->getQuantity() + 0,
'price' => $item->getPrice() + 0.0,
];
}
$invoice_data['items'] = $items_data;
// make job order data
$data = [
'jo_id' => $jo->getID(),
'invoice' => $invoice_data
];
// check service type
if ($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW)
{
$customer = $cv->getCustomer();
$customer_tags = $customer->getCustomerTagObjects();
if (!empty($customer_tags))
{
foreach ($customer_tags as $customer_tag)
{
// TODO: not too comfy with this being hardcoded
if ($customer_tag->getID() == $invoice->getUsedCustomerTagId())
{
// remove associated entity
$customer->removeCustomerTag($customer_tag);
// log the availment of promo from customer
$created_by = $req->query->get('api_key');;
$cust_id = $jo->getCustomer()->getID();
$cust_fname = $jo->getCustomer()->getFirstName();
$cust_lname = $jo->getCustomer()->getLastName();
$jo_id = $jo->getID();
$invoice_id = $jo->getInvoice()->getID();
// TODO: check if we store total price of invoice or just the discounted amount
$amount = $jo->getInvoice()->getTotalPrice();
$promo_logger->logPromoInfo($created_by, $cust_id, $cust_fname, $cust_lname, $jo_id,
$invoice_id, $amount);
}
}
}
}
// set data
$res->setData($data);
return $res->getReturnResponse();
}
public function getEstimate(Request $req, InvoiceGeneratorInterface $ic)
{
// $this->debugRequest($req);
// check required parameters and api key
$required_params = [
'service_type',
'cv_id',
// 'batt_id',
'trade_in',
];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// make invoice criteria
$icrit = new InvoiceCriteria();
$icrit->setServiceType($req->request->get('service_type'));
// check promo
$promo_id = $req->request->get('promo_id');
if (!empty($promo_id))
{
$promo = $em->getRepository(Promo::class)->find($promo_id);
if ($promo == null)
{
$res->setError(true)
->setErrorMessage('Invalid promo id');
return $res->getReturnResponse();
}
// put in criteria
$icrit->addPromo($promo);
}
// check customer vehicle
$cv = $em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id'));
if ($cv == null)
{
$res->setError(true)
->setErrorMessage('Invalid customer vehicle id');
return $res->getReturnResponse();
}
$icrit->setCustomerVehicle($cv);
// check if customer owns vehicle
if ($cust->getID() != $cv->getCustomer()->getID())
{
$res->setError(true)
->setErrorMessage('Customer does not own vehicle');
return $res->getReturnResponse();
}
// check battery
$batt_id = $req->request->get('batt_id');
if ($batt_id != null)
{
$batt = $em->getRepository(Battery::class)->find($batt_id);
if ($batt == null)
{
$res->setError(true)
->setErrorMessage('Invalid battery id');
return $res->getReturnResponse();
}
}
else
$batt = null;
/*
// put battery in criteria
$icrit->addBattery($batt);
*/
// check trade-in
// only allow motolite, other, none
$trade_in = $req->request->get('trade_in');
switch ($trade_in)
{
case TradeInType::MOTOLITE:
case TradeInType::OTHER:
break;
default:
$trade_in = '';
break;
}
$icrit->addEntry($batt, $trade_in, 1);
// send to invoice generator
$invoice = $ic->generateInvoice($icrit);
// make invoice json data
$data = [
'total_price' => (float) $invoice->getTotalPrice(),
'vat_ex_price' => (float) $invoice->getVATExclusivePrice(),
'vat' => (float) $invoice->getVAT(),
'discount' => (float) $invoice->getDiscount(),
'trade_in' => (float) $invoice->getTradeIn(),
];
$items = $invoice->getItems();
$items_data = [];
foreach ($items as $item)
{
$my_data = [
'title' => $item->getTitle(),
'qty' => (int) $item->getQuantity() + 0,
'price' => (float) $item->getPrice() + 0.0,
];
$item_batt = $item->getBattery();
if ($item_batt != null)
{
$my_data['image_url'] = $this->getBatteryImageURL($req, $item_batt);
}
$items_data[] = $my_data;
}
$data['items'] = $items_data;
// error_log(print_r($data, true));
// set data
$res->setData($data);
return $res->getReturnResponse();
}
public function getRiderStatus(Request $req, RiderTracker $rt)
{
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// check if we have an ongoing job order
/*
$ongoing_jos = $em->getRepository(JobOrder::class)->findBy([
'customer' => $cust,
'status' => [JOStatus::PENDING, JOStatus::RIDER_ASSIGN, JOStatus::IN_TRANSIT, JOStatus::ASSIGNED, JOStatus::IN_PROGRESS],
]);
*/
$ongoing_jos = $this->getOngoingJobOrders($cust);
// $res->setData(['here' => count($ongoing_jos)]);
// return $res->getReturnResponse();
if (count($ongoing_jos) <= 0)
{
try
{
// check if the latest fulfilled jo they have needs rider rating
$query = $em->createQuery('select jo from App\Entity\JobOrder jo where jo.customer = :cust and jo.status = :status order by jo.date_fulfill desc');
$fulfill_jo = $query->setParameters([
'cust' => $cust,
'status' => JOStatus::FULFILLED,
])
->setMaxResults(1)
->getSingleResult();
}
catch (Exception $e)
{
// no pending
$res->setData([
'status' => APIRiderStatus::NO_PENDING_JO
]);
return $res->getReturnResponse();
}
// we got a recently fulfilled job order
if ($fulfill_jo)
{
// check if the rider has been rated
if (!$fulfill_jo->hasRiderRating())
{
$dest = $fulfill_jo->getCoordinates();
$data = [
'jo_id' => $fulfill_jo->getID(),
'service_type' => $fulfill_jo->getServiceType(),
'destination' => [
'long' => $dest->getLongitude(),
'lat' => $dest->getLatitude(),
],
'delivery_address' => $fulfill_jo->getDeliveryAddress(),
'delivery_instructions' => $fulfill_jo->getDeliveryInstructions(),
];
$rider = $fulfill_jo->getRider();
// default image url
$url_prefix = $req->getSchemeAndHttpHost();
$image_url = $url_prefix . '/assets/images/user.gif';
if ($rider->getImageFile() != null)
$image_url = $url_prefix . '/uploads/' . $rider->getImageFile();
$data['status'] = APIRiderStatus::RIDER_RATING;
// default rider location to hub
$data['rider'] = [
'id' => $rider->getID(),
'name' => $rider->getFullName(),
'plate_num' => $rider->getPlateNumber(),
'contact_num' => $rider->getContactNumber(),
'image_url' => $image_url,
];
$res->setData($data);
return $res->getReturnResponse();
}
}
// no pending
$res->setData([
'status' => APIRiderStatus::NO_PENDING_JO
]);
return $res->getReturnResponse();
}
// get first jo that's pending
$jo = $ongoing_jos[0];
$dest = $jo->getCoordinates();
$data = [
'jo_id' => $jo->getID(),
'service_type' => $jo->getServiceType(),
'destination' => [
'long' => $dest->getLongitude(),
'lat' => $dest->getLatitude(),
],
'delivery_address' => $jo->getDeliveryAddress(),
'delivery_instructions' => $jo->getDeliveryInstructions(),
];
switch ($jo->getStatus())
{
case JOStatus::PENDING:
$data['status'] = APIRiderStatus::OUTLET_ASSIGN;
$res->setData($data);
return $res->getReturnResponse();
case JOStatus::RIDER_ASSIGN:
$data['status'] = APIRiderStatus::RIDER_ASSIGN;
$res->setData($data);
return $res->getReturnResponse();
case JOStatus::ASSIGNED:
case JOStatus::IN_TRANSIT:
case JOStatus::IN_PROGRESS:
$rider = $jo->getRider();
// get rider coordinates from redis
$coord = $rt->getRiderLocation($rider->getID());
// default image url
$url_prefix = $req->getSchemeAndHttpHost();
$image_url = $url_prefix . '/assets/images/user.gif';
if ($rider->getImageFile() != null)
$image_url = $url_prefix . '/uploads/' . $rider->getImageFile();
$data['status'] = APIRiderStatus::RIDER_PICK_UP;
// TODO: fix this to actual location of rider
// default rider location to hub
$data['rider'] = [
'id' => $rider->getID(),
'name' => $rider->getFullName(),
'plate_num' => $rider->getPlateNumber(),
'contact_num' => $rider->getContactNumber(),
'image_url' => $image_url,
'location' => [
'long' => $coord->getLongitude(),
'lat' => $coord->getLatitude()
]
];
$res->setData($data);
return $res->getReturnResponse();
}
$res->setData($data);
return $res->getReturnResponse();
}
protected function generateAPIRiderStatus($status)
{
switch ($status)
{
case JOStatus::PENDING:
return APIRiderStatus::OUTLET_ASSIGN;
case JOStatus::RIDER_ASSIGN:
return APIRiderStatus::RIDER_ASSIGN;
case JOStatus::ASSIGNED:
case JOStatus::IN_TRANSIT:
case JOStatus::IN_PROGRESS:
return APIRiderStatus::RIDER_PICK_UP;
}
return 'unknown';
}
protected function getOngoingJobOrders($cust)
{
$em = $this->getDoctrine()->getManager();
$ongoing_jos = $em->getRepository(JobOrder::class)->findBy([
'customer' => $cust,
'status' => [JOStatus::PENDING, JOStatus::RIDER_ASSIGN, JOStatus::IN_TRANSIT, JOStatus::ASSIGNED, JOStatus::IN_PROGRESS],
], ['date_schedule' => 'desc']);
return $ongoing_jos;
}
public function getOngoing(Request $req)
{
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
/*
// check if we have an ongoing job order
$ongoing_jos = $em->getRepository(JobOrder::class)->findBy([
'customer' => $cust,
'status' => [JOStatus::PENDING, JOStatus::RIDER_ASSIGN, JOStatus::IN_TRANSIT, JOStatus::ASSIGNED, JOStatus::IN_PROGRESS],
]);
*/
$ongoing_jos = $this->getOngoingJobOrders($cust);
// initialize data
$data = [];
// no ongoing
if (count($ongoing_jos) <= 0)
{
$data = [
'has_ongoing' => false,
];
}
else
{
$data = [
'has_ongoing' => true,
];
}
$res->setData($data);
return $res->getReturnResponse();
}
public function addRiderRating(Request $req)
{
$required_params = [
'jo_id',
'rating',
];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// get job order
$jo_id = $req->request->get('jo_id');
$jo = $em->getRepository(JobOrder::class)->find($jo_id);
if ($jo == null)
{
$res->setError(true)
->setErrorMessage('No job order found');
return $res->getReturnResponse();
}
// get rider
$rider = $jo->getRider();
if ($rider == null)
{
$res->setError(true)
->setErrorMessage('No rider found');
return $res->getReturnResponse();
}
// check that the customer owns the job order
$jo_cust = $jo->getCustomer();
if ($jo_cust->getID() != $cust->getID())
{
$res->setError(true)
->setErrorMessage('Job order was not initiated by customer');
return $res->getReturnResponse();
}
// TODO: check job order status, if it's complete
// add rider rating
$rating_num = $req->request->get('rating', -1);
// if rating is -1
if ($rating_num == -1)
{
$jo->setHasRiderRating();
$em->flush();
$res->setData([]);
return $res->getReturnResponse();
}
$rating = new RiderRating();
$rating->setRider($rider)
->setCustomer($cust)
->setJobOrder($jo)
->setRating($rating_num);
// rider rating comment
$comment = $req->request->get('comment');
if (!empty($comment))
$rating->setComment($comment);
// mark jo as rider rated already
$jo->setHasRiderRating();
$em->persist($rating);
$em->flush();
// TODO: set average rating in rider entity
$res->setData([]);
return $res->getReturnResponse();
}
public function getJOInvoice(Request $req)
{
$required_params = [
'jo_id',
];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get job order
$jo_id = $req->query->get('jo_id');
$jo = $em->getRepository(JobOrder::class)->find($jo_id);
if ($jo == null)
{
$res->setError(true)
->setErrorMessage('No job order found');
return $res->getReturnResponse();
}
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// check that the customer owns the job order
$jo_cust = $jo->getCustomer();
if ($jo_cust->getID() != $cust->getID())
{
$res->setError(true)
->setErrorMessage('Job order was not initiated by customer');
return $res->getReturnResponse();
}
$invoice = $jo->getInvoice();
// make invoice json data
$data = [
'total_price' => (float) $invoice->getTotalPrice(),
'vat_ex_price' => (float) $invoice->getVATExclusivePrice(),
'vat' => (float) $invoice->getVAT(),
'discount' => (float) $invoice->getDiscount(),
'trade_in' => (float) $invoice->getTradeIn(),
];
$items = $invoice->getItems();
$items_data = [];
foreach ($items as $item)
{
$my_data = [
'title' => $item->getTitle(),
'qty' => (int) $item->getQuantity() + 0,
'price' => (float) $item->getPrice() + 0.0,
];
$item_batt = $item->getBattery();
if ($item_batt != null)
{
$my_data['image_url'] = $this->getBatteryImageURL($req, $item_batt);
}
$items_data[] = $my_data;
}
$data['items'] = $items_data;
// set data
$res->setData($data);
/*
// 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,
];
}
$data = [
'invoice' => [
'discount' => $inv->getDiscount(),
'trade_in' => $inv->getTradeIn(),
'total_price' => $inv->getTotalPrice(),
'vat' => $inv->getVat(),
'items' => $inv_items,
],
];
$res->setData($data);
*/
return $res->getReturnResponse();
}
public function cancelJobOrder(Request $req, MQTTClient $mclient)
{
$required_params = [
'jo_id',
'reason'
];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get job order
$jo_id = $req->request->get('jo_id');
$jo = $em->getRepository(JobOrder::class)->find($jo_id);
if ($jo == null)
{
$res->setError(true)
->setErrorMessage('No job order found');
return $res->getReturnResponse();
}
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// check that the customer owns the job order
$jo_cust = $jo->getCustomer();
if ($jo_cust->getID() != $cust->getID())
{
$res->setError(true)
->setErrorMessage('Job order was not initiated by customer');
return $res->getReturnResponse();
}
// TODO: check job order status, if it's cancellable
$cancel_reason = $req->request->get('reason');
$jo->cancel($cancel_reason);
// add event log
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::CANCEL)
->setJobOrder($jo);
$em->persist($event);
$em->flush();
// send mobile app event
$payload = [
'event' => 'cancelled',
'reason' => $cancel_reason,
'jo_id' => $jo->getID(),
];
// $mclient->sendEvent($jo, $payload);
$mclient->sendRiderEvent($jo, $payload);
$res->setData([]);
return $res->getReturnResponse();
}
// we can't use param converter for now because we want to output the proper 404
public function getJobOrderInfo($id, Request $req, EntityManagerInterface $em, RiderTracker $rt)
{
// check required parameters and api key
$res = $this->checkParamsAndKey($req, $em, []);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// get job order data
$jo = $em->getRepository(JobOrder::class)->find($id);
if ($jo == null)
{
$res->setError(true)
->setErrorMessage('No job order information found');
return $res->getReturnResponse();
}
// check if job order belongs to customer / user
if ($cust->getID() != $jo->getCustomer()->getID())
{
$res->setError(true)
->setErrorMessage('No job order information found');
return $res->getReturnResponse();
}
// put into job order data array
$jo_data = $this->generateJobOrderData($req, $jo, $rt);
$data = [
'job_order' => $jo_data
];
$res->setData($data);
return $res->getReturnResponse();
}
protected function generateJobOrderData($req, $jo, $rt)
{
$status = $jo->getStatus();
$dest = $jo->getCoordinates();
$jo_data = [
'id' => $jo->getID(),
'date_create' => $jo->getDateCreate()->format('M d, Y'),
'service_type' => $jo->getServiceType(),
'destination' => [
'long' => $dest->getLongitude(),
'lat' => $dest->getLatitude(),
],
'delivery_address' => $jo->getDeliveryAddress(),
'delivery_instructions' => $jo->getDeliveryInstructions(),
'jo_status' => $status,
'status' => $this->generateAPIRiderStatus($status),
];
// customer vehicle and warranty
$cv = $jo->getCustomerVehicle();
// get latest warranty using plate number
$warranty = $this->findWarranty($cv->getPlateNumber());
$jo_data['customer_vehicle'] = [
'id' => $cv->getID(),
'plate_number' => $cv->getPlateNumber(),
'warranty' => $warranty,
];
// customer information
$customer = $jo->getCustomer();
$jo_data['customer'] = [
'first_name' => $customer->getFirstName(),
'last_name' => $customer->getLastName(),
'mobile_number' => $customer->getPhoneMobile(),
];
// rider
$rider = $jo->getRider();
if ($rider != null)
{
// default image url
$url_prefix = $req->getSchemeAndHttpHost();
$image_url = $url_prefix . '/assets/images/user.gif';
if ($rider->getImageFile() != null)
$image_url = $url_prefix . '/uploads/' . $rider->getImageFile();
$coord = $rt->getRiderLocation($rider->getID());
$jo_data['rider'] = [
'id' => $rider->getID(),
'name' => $rider->getFullName(),
'plate_num' => $rider->getPlateNumber(),
'contact_num' => $rider->getContactNumber(),
'image_url' => $image_url,
'location' => [
'long' => $coord->getLongitude(),
'lat' => $coord->getLatitude()
]
];
}
else
{
$jo_data['rider'] = null;
}
// invoice items
$items = [];
$jo_items = $jo->getInvoice()->getItems();
foreach ($jo_items as $item)
{
$items[] = [
'id' => $item->getID(),
'title' => $item->getTitle(),
'qty' => $item->getQuantity(),
'price' => $item->getPrice(),
];
}
$jo_data['items'] = $items;
// dates depending on status
switch ($status)
{
case JOStatus::FULFILLED:
if ($jo->getDateFulfill() == null)
$jo_data['date_fulfilled'] = '';
else
$jo_data['date_fulfilled'] = $jo->getDateFulfill()->format('M d, Y');
break;
case JOStatus::CANCELLED:
$date_cancel = $jo->getDateCancel();
if ($date_cancel == null)
$date_cancel = new DateTime();
$jo_data['date_cancelled'] = $date_cancel->format('M d, Y');
break;
}
return $jo_data;
}
public function getJOHistory(Request $req)
{
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, []);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// get job orders
$all_jo_data = [];
// $jos = $cust->getJobOrders();
// get the fulfilled and cancelled job orders, since ongoing jos are not yet part of history
$jos = $em->getRepository(JobOrder::class)->findBy([
'customer' => $cust,
'status' => [JOStatus::CANCELLED, JOStatus::FULFILLED]
], ['date_schedule' => 'DESC']);
foreach ($jos as $jo)
{
// NOTE: use generateJobOrderData method, maybe?
$status = $jo->getStatus();
$jo_data = [
'id' => $jo->getID(),
'date_create' => $jo->getDateCreate()->format('M d, Y'),
'service_type' => $jo->getServiceType(),
'status' => $status,
];
// customer vehicle and warranty
$cv = $jo->getCustomerVehicle();
// get latest warranty using plate number
$warranty = $this->findWarranty($cv->getPlateNumber());
$jo_data['customer_vehicle'] = [
'id' => $cv->getID(),
'plate_number' => $cv->getPlateNumber(),
'warranty' => $warranty,
];
// rider
$rider = $jo->getRider();
// check if jo has rider rating set to true
$has_rider_rating = $jo->hasRiderRating();
$rating = 0;
$comment = '';
if ($rider != null)
{
$jo_data['rider'] = $rider->getFullName();
// find the rider rating if any
if ($has_rider_rating)
{
$jo_rating = $jo->getRating();
if ($jo_rating != null)
{
$rating = $jo_rating->getRating();
// get comment
$comment = $jo_rating->getComment();
}
}
}
// rider rating for jo
$jo_data['has_rider_rating'] = $has_rider_rating;
$jo_data['rider_rating'] = $rating;
$jo_data['comment'] = $comment;
// invoice items
$items = [];
$jo_items = $jo->getInvoice()->getItems();
foreach ($jo_items as $item)
{
$items[] = [
'id' => $item->getID(),
'title' => $item->getTitle(),
'qty' => $item->getQuantity(),
'price' => $item->getPrice(),
];
}
$jo_data['items'] = $items;
// dates depending on status
switch ($status)
{
case JOStatus::FULFILLED:
if ($jo->getDateFulfill() == null)
$jo_data['date_fulfilled'] = '';
else
$jo_data['date_fulfilled'] = $jo->getDateFulfill()->format('M d, Y');
break;
case JOStatus::CANCELLED:
$date_cancel = $jo->getDateCancel();
if ($date_cancel == null)
$date_cancel = new DateTime();
$jo_data['date_cancelled'] = $date_cancel->format('M d, Y');
break;
}
$all_jo_data[] = $jo_data;
}
// return data
$data = [
'job_orders' => $all_jo_data
];
$res->setData($data);
// response
return $res->getReturnResponse();
}
public function updateDeviceID(Request $req)
{
$required_params = [
'device_id',
];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
$device_id = $req->request->get('device_id');
$this->session->setDevicePushID($device_id);
$em->flush();
// response
return $res->getReturnResponse();
}
public function resendCode(Request $req, RisingTideGateway $rt)
{
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// already confirmed
if ($this->session->isConfirmed())
{
$res->setError(true)
->setErrorMessage('User is already confirmed.');
return $res->getReturnResponse();
}
// have sent code before
if ($this->session->getDateCodeSent() != null)
{
$res->setError(true)
->setErrorMessage('Can only send confirm code every 5 mins.');
return $res->getReturnResponse();
}
// TODO: send via sms
$phone_number = $this->session->getPhoneNumber();
$code = $this->session->getConfirmCode();
$this->sendConfirmationCode($rt, $phone_number, $code);
return $res->getReturnResponse();
}
public function privacySettings(Request $req)
{
$required_params = [
'priv_third_party',
// 'priv_promo',
];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// set privacy settings
$priv_promo = $req->request->get('priv_promo', false);
$priv_third_party = $req->request->get('priv_third_party');
$cust->setPrivacyThirdParty($priv_third_party)
->setPrivacyPromo($priv_promo);
// get the policy ids from .env
$dotenv = new Dotenv();
$dotenv->loadEnv(__DIR__.'/../../.env');
$policy_promo_id = $_ENV['POLICY_PROMO'];
$policy_third_party_id = $_ENV['POLICY_THIRD_PARTY'];
// check if privacy settings are true
// if true, set the private policy for the customer
if ($priv_promo)
{
// find the promo policy
$policy = $em->getRepository(PrivacyPolicy::class)->find($policy_promo_id);
// set policy id
if ($policy != null)
{
$cust->setPrivacyPolicyPromo($policy);
}
}
if ($priv_third_party)
{
// find the third party policy
$policy = $em->getRepository(PrivacyPolicy::class)->find($policy_third_party_id);
// set policy id
if ($policy != null)
{
$cust->setPrivacyPolicyThirdParty($policy);
}
}
$em->flush();
return $res->getReturnResponse();
}
public function locationSupport(Request $req, GeofenceTracker $geo)
{
$required_params = [
'longitude',
'latitude',
];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
$long = $req->query->get('longitude');
$lat = $req->query->get('latitude');
// NOTE: had to add this for promo tag
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
$is_covered = false;
// check if customer still has promo
if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) ||
($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')))
{
// if has customer tag, customer has not availed of promo
$is_covered = true;
}
else
{
// geofence
$is_covered = $geo->isCovered($long, $lat);
}
// geofence
// $is_covered = $geo->isCovered($long, $lat);
$data = [
'longitude' => $long,
'latitude' => $lat,
'supported' => $is_covered,
];
$res->setData($data);
// check if is_covered is false. If so, we need to set the error part in the response
if (!$is_covered)
{
$res->setError(true)
->setErrorMessage('Oops! Our service is limited to some areas in Metro Manila, Laguna, and Baguio only. We will update you as soon as we are able to cover your area');
}
return $res->getReturnResponse();
}
public function activateWarranty(Request $req)
{
$required_params = ['plate_number'];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
$plate_number = $req->request->get('plate_number');
// find warranty using plate number
$warranty_results = $em->getRepository(Warranty::class)->findBy(['plate_number' => $plate_number],
['date_create' => 'desc']);
// check if warranty_results is empty
if (empty($warranty_results))
{
$res->setError(true)
->setErrorMessage('No warranty found for plate number');
return $res->getReturnResponse();
}
// activate all entries
foreach ($warranty_results as $warranty)
{
$warranty->setActivated();
}
$em->flush();
return $res->getReturnResponse();
}
protected function findWarranty($plate_number)
{
$em = $this->getDoctrine()->getManager();
// NOTE: Modify the search for the latest warranty. This seems hacky.
// get latest warranty using plate number
$warranty_results = $em->getRepository(Warranty::class)->findBy(['plate_number' => $plate_number],
['date_create' => 'desc']);
$warr = [];
// check if warranty_results is empty
if (empty($warranty_results))
{
/*
$res->setError(true)
->setErrorMessage('No warranty found for plate number');
return $res->getReturnResponse();
*/
return $warr;
}
// get first entry
$warranty = current($warranty_results);
// check for null values for battery and date claim and date expire
$batt_model = '';
$batt_size = '';
$sap_batt = '';
$claim_date = '';
$expiry_date = '';
if (!(is_null($warranty->getBatteryModel()))) {
$batt_model = $warranty->getBatteryModel()->getName();
}
if (!(is_null($warranty->getBatterySize()))) {
$batt_size = $warranty->getBatterySize()->getName();
}
if (!(is_null($warranty->getSAPBattery()))) {
$sap_batt = $warranty->getSAPBattery()->getID();
}
if (!(is_null($warranty->getDateClaim()))) {
$claim_date = $warranty->getDateClaim()->format("d M Y");
}
if (!(is_null($warranty->getDateExpire()))) {
$expiry_date = $warranty->getDateExpire()->format("d M Y");
}
$warr[] = [
'id' => $warranty->getID(),
'serial' => $warranty->getSerial(),
'warranty_class' => $warranty->getWarrantyClass(),
'plate_number' => $warranty->getPlateNumber(),
'first_name' => $warranty->getFirstName(),
'last_name' => $warranty->getLastName(),
'mobile_number' => $warranty->getMobileNumber(),
'battery_model' => $batt_model,
'battery_size' => $batt_size,
'sap_battery' => $sap_batt,
'status' => $warranty->getStatus(),
'date_create' => $warranty->getDateCreate()->format("d M Y g:i A"),
'date_purchase' => $warranty->getDatePurchase()->format("d M Y"),
'date_expire' => $expiry_date,
'date_claim' => $claim_date,
'claim_from' => $warranty->getClaimedFrom(),
'is_activated' => $warranty->isActivated() ? 1 : 0,
];
return $warr;
}
public function listServices(Request $req)
{
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// services
$results = $em->getRepository(Service::class)->findAll();
if (empty($results))
{
$res->setError(true)
->setErrorMessage('No services available.');
return $res->getReturnResponse();
}
$services = [];
foreach ($results as $result)
{
/*
// get partners
$partners = [];
$service_partners = $result->getPartners();
foreach($service_partners as $sp)
{
$partners[] = [
'id' => $sp->getID(),
'name' => $sp->getName(),
'branch' => $sp->getBranch(),
'address' => $sp->getAddress(),
'contact_nums' => $sp->getContactNumbers(),
'time_open' => $sp->getTimeOpen()->format("g:i A"),
'time_close' => $sp->getTimeClose()->format("g:i A"),
];
}
*/
$services[] = [
'id' => $result->getID(),
'name' => $result->getName(),
// 'partners' => $partners,
];
}
$data['services'] = $services;
$res->setData($data);
return $res->getReturnResponse();
}
public function getPartnerInformation(Request $req, $pid)
{
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get partner
$partner = $em->getRepository(Partner::class)->findOneBy(['id' => $pid]);
if ($partner == null)
{
$res->setError(true)
->setErrorMessage('No partner found.');
return $res->getReturnResponse();
}
// get reviews for partner
$reviews = $em->getRepository(Review::class)->findBy(['partner' => $partner]);
// get average rating for all reviews
$average_rating = 0;
if (!empty($reviews))
{
$rating = 0;
foreach($reviews as $review)
{
$rating = $rating + $review->getRating();
}
$average_rating = $rating / sizeof($reviews);
}
$data['partner'] = [
'id' => $partner->getID(),
'name' => $partner->getName(),
'branch' => $partner->getBranch(),
'address' => $partner->getAddress(),
'contact_nums' => $partner->getContactNumbers(),
'time_open' => $partner->getTimeOpen()->format("g:i A"),
'time_close' => $partner->getTimeClose()->format("g:i A"),
'longitude' => $partner->getCoordinates()->getLongitude(),
'latitude' => $partner->getCoordinates()->getLatitude(),
'average_rating' => $average_rating,
];
$res->setData($data);
return $res->getReturnResponse();
}
public function getClosestPartners(Request $req)
{
$required_params = [
'longitude',
'latitude',
'service_id',
'limit',
];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
$long = $req->query->get('longitude');
$lat = $req->query->get('latitude');
$service_id = $req->query->get('service_id');
$limit = $req->query->get('limit');
// get partners within range
$query = $em->createQuery('SELECT p, st_distance(p.coordinates, point(:lng, :lat)) as dist FROM App\Entity\Partner p
JOIN App\Entity\Service s where s.id = :service_id ORDER BY dist')
->setParameter('lat', $lat)
->setParameter('lng', $long)
->setParameter('service_id', $service_id);
$query->setMaxResults($limit);
$result = $query->getResult();
$data = [];
$partners = [];
foreach($result as $row)
{
// get all the reviews for each partner and average the ratings
$partner_id = $row[0]->getID();
$partner = $em->getRepository(Partner::class)->find($partner_id);
$partner_reviews = $em->getRepository(Review::class)->findBy(['partner' => $partner]);
$average_rating = 0;
if (count($partner_reviews) > 0)
{
$rating = 0;
foreach ($partner_reviews as $review)
{
$rating = $rating + $review->getRating();
}
$average_rating = $rating / sizeof($partner_reviews);
}
$partners[] = [
'id' => $row[0]->getID(),
'name' => $row[0]->getName(),
'branch' => $row[0]->getBranch(),
'address' => $row[0]->getAddress(),
'contact_nums' => $row[0]->getContactNumbers(),
'time_open' => $row[0]->getTimeOpen()->format("g:i A"),
'time_close' => $row[0]->getTimeClose()->format("g:i A"),
'longitude' => $row[0]->getCoordinates()->getLongitude(),
'latitude' => $row[0]->getCoordinates()->getLatitude(),
'db_distance' => $row['dist'],
'rating' => $average_rating,
];
}
$data['partners'] = $partners;
$res->setData($data);
return $res->getReturnResponse();
}
public function reviewPartner($pid, Request $req, EntityManagerInterface $em)
{
$required_params = [
'rating',
'message',
];
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
$rating = $req->request->get('rating');
$msg = $req->request->get('message');
// TODO: check rating if 1 - 5
// check if partner exists
$partner = $em->getRepository(Partner::class)->find($pid);
if ($partner == null)
{
$res->setError(true)
->setErrorMessage('No partner found.');
return $res->getReturnResponse();
}
$rev = new Review();
$rev->setRating($rating)
->setMessage($msg)
->setPartner($partner)
->setMobileSession($this->session);
// save to db
$em->persist($rev);
$em->flush();
$data = [];
$res->setData($data);
return $res->getReturnResponse();
}
public function getNearestHubAndSlots(Request $req, EntityManagerInterface $em,
MapTools $map_tools)
{
$required_params = [
'longitude',
'latitude',
];
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
$coordinates = new Point($req->query->get('longitude'), $req->query->get('latitude'));
// add checking if customer has a pre-registered hub
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// check if customer has customer tag promo
if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) ||
($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')))
{
// if has customer tag, customer has not availed of promo, get the hub where customer is pre-registered
$car_club_cust_hub = $cust->getCarClubCustomerHub();
if ($car_club_cust_hub != null)
{
// need to get the rider slots for the pre-registered hub
$hub = $car_club_cust_hub->getHub();
$nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $em, $map_tools, $hub);
}
else
{
$nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $em, $map_tools);
if (empty($nearest_hub_slots['hub']))
{
$res->setError(true)
->setErrorMessage('Thank you for reaching out to us. Due to the General Community Quarantine, our Operations are from 8AM to 6PM only. Please expect a call from us tomorrow and we will assist you with your request. Thank you and stay safe!');
return $res->getReturnResponse();
}
}
}
else
{
$nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $em, $map_tools);
if (empty($nearest_hub_slots['hub']))
{
$res->setError(true)
->setErrorMessage('Thank you for reaching out to us. Due to the General Community Quarantine, our Operations are from 8AM to 6PM only. Please expect a call from us tomorrow and we will assist you with your request. Thank you and stay safe!');
return $res->getReturnResponse();
}
}
// make hub data
$data = [
'hub_id' => $nearest_hub_slots['hub']->getID(),
'hub_slots' => $nearest_hub_slots['slots'],
];
$res->setData($data);
return $res->getReturnResponse();
}
public function newRequestJobOrder(Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo,
MapTools $map_tools, InventoryManager $im, MQTTClient $mclient,
RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger,
HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger,
HubFilteringGeoChecker $hub_geofence)
{
// check required parameters and api key
$required_params = [
'service_type',
'cv_id',
'trade_in',
'long',
'lat',
'warranty',
'mode_of_payment',
];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// trade in type
$trade_in = $req->request->get('trade_in');
// address
$address = $req->request->get('delivery_address', 'Set by mobile application');
// instructions
$instructions = $req->request->get('delivery_instructions', '');
// landmark
$landmark = $req->request->get('landmark', ' ');
// longitude and latitude
$long = $req->request->get('long');
$lat = $req->request->get('lat');
// NOTE: had to move this up so we can check for promo before geofence
// customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
$is_covered = false;
// check if customer still has promo
if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) ||
($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')))
{
// if has customer tag, customer has not availed of promo
$is_covered = true;
}
else
{
// geofence
$is_covered = $geo->isCovered($long, $lat);
}
if (!$is_covered)
{
// TODO: put geofence error message in config file somewhere
$res->setError(true)
->setErrorMessage('Oops! Our service is limited to some areas in Metro Manila, Laguna, and Baguio only. We will update you as soon as we are able to cover your area');
return $res->getReturnResponse();
}
$hub = null;
$hub_id = $req->request->get('hub_id');
// check if hub_id is -1 which means user clicked Book Now before 5 PM
// but confirmed the order after 5 PM
if ($hub_id == -1)
{
$res->setError(true)
->setErrorMessage('Book Now no longer available.');
return $res->getReturnResponse();
}
if (strlen($hub_id) > 0)
$hub = $em->getRepository(Hub::class)->find($hub_id);
$schedule_date = $req->request->get('date_schedule');
$slot_id = $req->request->get('slot_id');
// process the jo date schedule
$date_schedule = null;
if ((strlen($schedule_date) > 0) && (strlen($slot_id) > 0))
{
$time_schedule = $this->getTimeFromSlot($slot_id);
if (!empty($time_schedule))
{
$s_date = $schedule_date . ' ' . $time_schedule;
$date_schedule = DateTime::createFromFormat('Y-m-d H:i', $s_date);
// error_log($date_schedule->format('Y-m-d H:i'));
}
}
$advance_order = $req->request->get('flag_advance_order');
// check for 'false' text
if ($advance_order === false || $advance_order === 0 || $advance_order === '0' || $advance_order == 'false')
$flag_advance_order = false;
else
$flag_advance_order = true;
// $flag_advance_order = $advance_order ? true : false;
$jo = new JobOrder();
$jo->setSource(TransactionOrigin::MOBILE_APP)
->setStatus(JOStatus::PENDING)
->setDeliveryInstructions('')
->setTier1Notes('')
->setTier2Notes('')
->setDeliveryAddress($address)
->setTradeInType($trade_in)
->setDeliveryInstructions($instructions)
// TODO: error check for valid mode of payment
->setModeOfPayment($req->request->get('mode_of_payment'))
->setAdvanceOrder($flag_advance_order)
->setStatusAutoAssign(AutoAssignStatus::NOT_ASSIGNED)
->setLandmark($landmark);
// customer
// $cust = $this->session->getCustomer();
// if ($cust == null)
// {
// $res->setError(true)
// ->setErrorMessage('No customer information found');
// return $res->getReturnResponse();
// }
$jo->setCustomer($cust);
// validate service type
$stype = $req->request->get('service_type');
if (!ServiceType::validate($stype))
{
$res->setError(true)
->setErrorMessage('Invalid service type');
return $res->getReturnResponse();
}
$jo->setServiceType($stype);
// validate warranty
$warr = $req->request->get('warranty');
if (!WarrantyClass::validate($warr))
{
$res->setError(true)
->setErrorMessage('Invalid warranty class');
return $res->getReturnResponse();
}
$jo->setWarrantyClass($warr);
// set coordinates
$point = new Point($long, $lat);
$jo->setCoordinates($point);
// make invoice criteria
$icrit = new InvoiceCriteria();
$icrit->setServiceType($stype);
// check promo
$promo_id = $req->request->get('promo_id');
if (!empty($promo_id))
{
$promo = $em->getRepository(Promo::class)->find($promo_id);
if ($promo == null)
{
$res->setError(true)
->setErrorMessage('Invalid promo id');
return $res->getReturnResponse();
}
// put in criteria
$icrit->addPromo($promo);
}
// check customer vehicle
$cv = $em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id'));
if ($cv == null)
{
$res->setError(true)
->setErrorMessage('Invalid customer vehicle id');
return $res->getReturnResponse();
}
$icrit->setCustomerVehicle($cv);
$jo->setCustomerVehicle($cv);
// check if customer owns vehicle
if ($cust->getID() != $cv->getCustomer()->getID())
{
$res->setError(true)
->setErrorMessage('Customer does not own vehicle');
return $res->getReturnResponse();
}
// check battery
$batt_id = $req->request->get('batt_id');
if ($batt_id != null)
{
$batt = $em->getRepository(Battery::class)->find($batt_id);
if ($batt == null)
{
$res->setError(true)
->setErrorMessage('Invalid battery id');
return $res->getReturnResponse();
}
}
else
$batt = null;
/*
// put battery in criteria
$icrit->addBattery($batt);
*/
// check trade-in
// only allow motolite, other, none
switch ($trade_in)
{
case TradeInType::MOTOLITE:
case TradeInType::OTHER:
break;
default:
$trade_in = '';
break;
}
$icrit->addEntry($batt, $trade_in, 1);
// send to invoice generator
$invoice = $ic->generateInvoice($icrit);
$jo->setInvoice($invoice);
// assign hub and rider
// check if hub is null
if ($hub == null)
{
// TODO: need to factor out the setting of HubCriteria fields
$hub_criteria = new HubCriteria();
$hub_criteria->setPoint($jo->getCoordinates());
// get distance limit for mobile from env
// get value of hub_filter_enable from env
$dotenv = new Dotenv();
$dotenv->loadEnv(__DIR__.'/../../.env');
$limit_distance = $_ENV['CUST_DISTANCE_LIMIT'];
$hub_filter_enabled = $_ENV['HUB_FILTER_ENABLE'];
// set distance limit
$hub_criteria->setLimitDistance($limit_distance);
// check if hub filter is enabled. If not, use default values
// for the rest of the HubCriteria fields
if ($hub_filter_enabled == 'true')
{
error_log('hub filter is enabled');
// check if customer location is in hub filter area
if ($hub_geofence->isCovered($long, $lat))
{
// if true, set other values for HubCriteria
// TODO: set this properly, since the other flags
// are on default values
error_log('Area is covered by hub filtering');
$hub_criteria->setJoType($jo->getServiceType())
->setPaymentMethod($jo->getModeOfPayment())
->setRoundRobin(true);
}
}
// check if batt is null
if ($batt != null)
{
// add battery to items
$sku = $batt->getSAPCode();
if (!empty($sku))
$hub_criteria->addItem($batt->getSAPCode(), 1);
}
// get customer id. No JO id at this point
$customer_id = $cust->getID();
$hub_criteria->setCustomerId($customer_id);
// find nearest hubs
$nearest_hubs = $hub_select->find($hub_criteria);
if (!empty($nearest_hubs))
{
// go through the hub list, find the nearest hub
// with an available rider
// error_log('found nearest hub ' . $nearest_hub->getID());
foreach ($nearest_hubs as $nearest_hub)
{
// check if hub can be auto assigned
// if not, move on to the next hub in the list
if (($nearest_hub['hub']->isHubAutoAssign()))
{
// check if hub has riders that can be auto assigned
// if not, move on to the next hub
if (($nearest_hub['hub']->isRiderAutoAssign()))
{
$available_riders = $nearest_hub['hub']->getAvailableRiders();
if (count($available_riders) >= 1)
{
$assigned_rider = null;
if (count($available_riders) == 1)
{
$assigned_rider = $available_riders[0];
}
else
{
// 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);
}
$jo->setHub($nearest_hub['hub']);
$jo->setRider($assigned_rider);
$jo->setStatus(JOStatus::ASSIGNED);
$jo->setStatusAutoAssign(AutoAssignStatus::HUB_AND_RIDER_ASSIGNED);
$jo->setDeliveryStatus(DeliveryStatus::RIDER_ASSIGN);
// set date_assigned for job order
$jo->setDateAssign(new DateTime());
$assigned_rider->setAvailable(false);
// set rider's current job order
$assigned_rider->setCurrentJobOrder($jo);
// update redis hub_jo_count for hub
$hub_dist->incrementJoCountForHub($nearest_hub['hub']);
// break out of loop
break;
}
else
{
// we just create the JO and let admin panel handle the hub assignment
// log hub into hub_filter_log
$hub_filter_logger->logFilteredHub($nearest_hub['hub'], 'no_available_rider', null, $cust->getID());
// continue to go through list to find hub with an available rider
}
}
else
{
// TODO: log hub as cannot be auto rider assigned somewhere
// assign hub
// error_log('Rider cannot be auto assigned ' . $nearest_hub['hub']->getID());
$jo->setHub($nearest_hub['hub']);
$jo->setStatus(JOStatus::RIDER_ASSIGN);
$jo->setStatusAutoAssign(AutoAssignStatus::HUB_ASSIGNED);
// update redis hub_jo_count for hub
$hub_dist->incrementJoCountForHub($nearest_hub['hub']);
break;
}
}
else
{
// TODO: log hub as cannot be auto assigned somewhere
// move to next hub
error_log('Hub cannot be auto-assigned ' . $nearest_hub['hub']->getID());
continue;
}
}
}
}
else
{
$jo->setHub($hub);
$jo->setStatus(JOStatus::RIDER_ASSIGN);
$jo->setStatusAutoAssign(AutoAssignStatus::HUB_ASSIGNED);
if ($date_schedule != null)
$jo->setDateSchedule($date_schedule);
// update redis hub_jo_count for hub
$hub_dist->incrementJoCountForHub($hub);
}
$em->persist($jo);
$em->persist($invoice);
// add event log for JO
$event = new JOEvent();
$event->setDateHappen(new DateTime())
->setTypeID(JOEventType::CREATE)
->setJobOrder($jo);
$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());
}
if ($jo->getStatus() == JOStatus::RIDER_ASSIGN)
{
// add event logs for hub assignments
$hub_assign_event = new JOEvent();
$hub_assign_event->setDateHappen(new DateTime())
->setTypeID(JOEventType::HUB_ASSIGN)
->setJobOrder($jo);
$em->persist($hub_assign_event);
// user mqtt event
$payload = [
'event' => 'outlet_assign'
];
$mclient->sendEvent($jo, $payload);
}
$em->flush();
// make invoice json data
$invoice_data = [
'total_price' => $invoice->getTotalPrice(),
'vat_ex_price' => (float) $invoice->getVATExclusivePrice(),
'vat' => $invoice->getVAT(),
'discount' => $invoice->getDiscount(),
'trade_in' => $invoice->getTradeIn(),
];
$items = $invoice->getItems();
$items_data = [];
foreach ($items as $item)
{
$items_data[] = [
'title' => $item->getTitle(),
'qty' => $item->getQuantity() + 0,
'price' => $item->getPrice() + 0.0,
];
}
$invoice_data['items'] = $items_data;
// make job order data
$data = [
'jo_id' => $jo->getID(),
'invoice' => $invoice_data
];
// need to check for customer tag/promo
// check service type
if ($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW)
{
$customer = $cv->getCustomer();
$customer_tags = $customer->getCustomerTagObjects();
if (!empty($customer_tags))
{
foreach ($customer_tags as $customer_tag)
{
// TODO: not too comfy with this being hardcoded
if ($customer_tag->getID() == $invoice->getUsedCustomerTagId())
{
// remove associated entity
$customer->removeCustomerTag($customer_tag);
// log the availment of promo from customer
$created_by = $req->query->get('api_key');;
$cust_id = $jo->getCustomer()->getID();
$cust_fname = $jo->getCustomer()->getFirstName();
$cust_lname = $jo->getCustomer()->getLastName();
$jo_id = $jo->getID();
$invoice_id = $jo->getInvoice()->getID();
// TODO: check if we store total price of invoice or just the discounted amount
$amount = $jo->getInvoice()->getTotalPrice();
$promo_logger->logPromoInfo($created_by, $cust_id, $cust_fname, $cust_lname, $jo_id,
$invoice_id, $amount);
}
}
}
}
// set data
$res->setData($data);
return $res->getReturnResponse();
}
public function versionCheck(Request $req)
{
$res = new APIResult();
$required_params = [
'version',
];
$missing = $this->checkMissingParameters($req, $required_params);
if (count($missing) > 0)
{
$params = implode(', ', $missing);
$res->setError(true)
->setErrorMessage('Missing parameter(s): ' . $params);
return $res->getReturnResponse();
}
$need_update = false;
$msg = 'Version is up to date.';
$api_version = $this->getParameter('api_version');
$app_version = $req->query->get('version');
// putting this in for the future, in case we have diverging versions
$os = $req->query->get('os');
$platform = $req->query->get('platform');
$api_v = explode('.', $api_version);
$app_v = explode('.', $app_version);
if ($api_v[0] < $app_v[0])
{
$res->setError(true)
->setErrorMessage('Invalid application version: ' . $app_version);
return $res->getReturnResponse();
}
if ($api_v[0] > $app_v[0])
{
$need_update = true;
$msg = 'Your version is outdated and needs an update to use the latest features RES-Q has to offer.';
}
$data = [
'need_update' => $need_update,
'message' => $msg,
];
$res->setData($data);
return $res->getReturnResponse();
}
public function scheduleOptionStatus(Request $req)
{
// check required parameters and api key
$required_params = [];
$em = $this->getDoctrine()->getManager();
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
$schedule_choice = true;
// remove the time check after ECQ. This will then always return true
// get current time
$current_datetime = new DateTime();
//$current_datetime = DateTime::createFromFormat('Y-m-d H:i', '2020-04-30 17:01');
// get the hour
$hour = $current_datetime->format('G');
// commenting out the time check since we can now book 24/7
// this will get uncommented out if and when ECQ will kick in
//if (($hour < 8) || ($hour > 16))
// $schedule_choice = false;
// add checking if customer has a pre-registered hub
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// check if customer has customer tag promo
if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) ||
($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')))
{
// if has customer tag, customer has not availed of promo, get the hub where customer is pre-registered
$car_club_hub = $cust->getCarClubCustomerHub();
if ($car_club_hub != null)
{
$schedule_choice = false;
}
}
// schedule_choice will always be true aka customer can opt to
// Book Now or Schedule Order EXCEPT if customer has customer tag promo
// or ECQ comes back
$data = [
'display_schedule_choice' => $schedule_choice,
];
$res->setData($data);
return $res->getReturnResponse();
}
protected function checkCustomerPlateNumber($plate_number, $cust)
{
// strip spaces and make all caps
$plate_number = preg_replace('/\s+/', '', strtoupper($plate_number));
// if there's no customer linked to session
if ($cust != null)
{
// check all the customer vehicles
$cvs = $cust->getVehicles();
foreach ($cvs as $cv)
{
$cv_plate = preg_replace('/\s+/', '', strtoupper($cv->getPlateNumber()));
// did we find a match?
if ($cv_plate == $plate_number)
{
return true;
}
}
}
return false;
}
// TODO: put this in a service
protected function cleanSerial($serial)
{
// trim and make everything upper case
$clean_serial = trim(strtoupper($serial));
// remove QR prefix if it exists
$prefix = substr($clean_serial, 0, 2);
if ($prefix == 'QR')
$clean_serial = substr($clean_serial, 2);
return $clean_serial;
}
public function warrantyCheck($serial, EntityManagerInterface $em, Request $req)
{
// check required parameters and api key
$required_params = [];
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// check if warranty serial is there
$serial = $this->cleanSerial($serial);
$warr_serial = $em->getRepository(WarrantySerial::class)->find($serial);
$warr = $em->getRepository(Warranty::class)->findOneBy(['serial' => $serial]);
$batt = null;
$is_registered = false;
if ($warr_serial == null)
{
$res->setError(true)
->setErrorMessage('Invalid warranty serial code.');
return $res->getReturnResponse();
}
$today = new DateTime();
// if we have a warranty entry for the serial already
if ($warr != null)
{
$warr_plate = $warr->getPlateNumber();
$is_registered = true;
$is_customer_warranty = false;
// check if the warranty is registered to a car owned by the customer
$cust = $this->session->getCustomer();
$is_customer_warranty = $this->checkCustomerPlateNumber($warr_plate, $cust);
// null mobile number should be blank string instead
if ($warr->getMobileNumber() == null)
$mobile_num = '';
else
$mobile_num = $warr->getMobileNumber();
$can_edit = $is_customer_warranty;
// if customer plate number matches the one registered on the warranty
if ($is_customer_warranty)
{
// purchase date of customer
if ($warr->getDatePurchaseCustomer() != null)
$date_purchase_cust = $warr->getDatePurchaseCustomer()->format('Y-m-d');
else
$date_purchase_cust = $today->format('Y-m-d');
// invoice
if ($warr->getFileInvoice() != null)
$invoice_url = $req->getSchemeAndHttpHost() . '/warranty_uploads/' . $warr->getFileInvoice();
else
$invoice_url = '';
// warranty card
if ($warr->getFileWarrantyCard() != null)
$warr_card_url = $req->getSchemeAndHttpHost() . '/warranty_uploads/' . $warr->getFileWarrantyCard();
else
$warr_card_url = '';
$customer = [
'first_name' => $warr->getFirstName() ?? '',
'last_name' => $warr->getLastName() ?? '',
'mobile_number' => $mobile_num,
'plate_number' => $warr_plate,
'email' => $warr->getEmail() ?? '',
'contact_num' => $warr->getContactNumber() ?? '',
'address' => $warr->getCustomerAddress() ?? '',
];
$other_data = [
'odometer' => (int) $warr->getOdometer() ?? 0,
'date_purchase' => $date_purchase_cust,
'invoice' => $invoice_url,
'warr_card' => $warr_card_url,
'dealer_name' => $warr->getDealerName() ?? '',
'dealer_address' => $warr->getDealerAddress() ?? '',
'branch_code' => $warr->getDealerBranchCode() ?? '',
];
}
else
{
// hide customer information if customer is not the one registered
$customer = [
'first_name' => '',
'last_name' => '',
'mobile_number' => '',
'plate_number' => '',
'email' => '',
'contact_num' => '',
'address' => '',
];
$other_data = [
'odometer' => 0,
'date_purchase' => $today->format('Y-m-d'),
'invoice' => '',
'warr_card' => '',
'dealer_name' => '',
'dealer_address' => '',
'branch_code' => '',
];
}
}
else
{
$can_edit = true;
$customer = [
'first_name' => '',
'last_name' => '',
'mobile_number' => '',
'plate_number' => '',
'email' => '',
'contact_num' => '',
'address' => '',
];
$other_data = [
'odometer' => 0,
'date_purchase' => $today->format('Y-m-d'),
'invoice' => '',
'warr_card' => '',
'dealer_name' => '',
'dealer_address' => '',
'branch_code' => '',
];
}
$sku = $warr_serial->getSKU();
$batt = null;
$cat_name = '';
if ($sku != null)
$batt = $em->getRepository(SAPBattery::class)->find($sku);
else
{
// get the category name of the serial
$cat_name = $warr_serial->getMetaInfo('category_name');
}
// TODO: put this in a config file
$image_url = $req->getSchemeAndHttpHost() . '/battery/generic.png';
if ($batt != null)
{
$battery = [
'brand' => $batt->getBrand()->getName(),
'size' => $batt->getSize()->getName(),
'image_url' => $image_url,
];
}
else
{
$battery = [
'brand' => $cat_name,
'size' => '',
'image_url' => '',
];
}
// populate data
$data = [
'is_valid' => true,
'is_registered' => $is_registered,
'can_edit' => $can_edit,
'customer' => $customer,
'battery' => $battery,
'odometer' => $other_data['odometer'],
'invoice' => $other_data['invoice'],
'warr_card' => $other_data['warr_card'],
'date_purchase' => $other_data['date_purchase'],
'dealer_name' => $other_data['dealer_name'],
'dealer_address' => $other_data['dealer_address'],
'branch_code' => $other_data['branch_code'],
'message' => [
'register_error' => 'Warranty serial code has already been registered.',
'edit_error' => 'Sorry, warranty is registered under another vehicle not in your list of vehicles.',
],
];
$res->setData($data);
return $res->getReturnResponse();
}
protected function handlePictureUpload($file, $target_dir, $serial, $name)
{
error_log("handling $name upload");
// no file sent
if ($file == null)
{
error_log('no file');
return null;
}
// create target dir if it doesn't exist
if (!file_exists($target_dir))
{
if (!mkdir($target_dir, 0744, true))
{
error_log('failed to create folder for warranty pictures');
return null;
}
}
// move file
$filename = $name . '.' . $file->getClientOriginalExtension();
$file->move($target_dir . '/' . $serial, $filename);
error_log("filename - $filename");
error_log($target_dir . '/' . $serial . '/' . $filename);
return $serial . '/' . $filename;
}
public function warrantyRegister($serial, EntityManagerInterface $em, Request $req, KernelInterface $kernel, RisingTideGateway $rt,
TranslatorInterface $trans, WarrantyAPILogger $logger)
{
// check required parameters and api key
$required_params = [
'first_name',
'last_name',
'plate_number',
'date_purchase',
];
// handle file uploads
$invoice = $req->files->get('invoice');
$warr_card = $req->files->get('warr_card');
// normalize serial
$serial = $this->cleanSerial($serial);
// $serial = trim(strtoupper($serial));
// process picture uploads
$upload_dir = $kernel->getProjectDir() . '/public/warranty_uploads';
$inv_filename = $this->handlePictureUpload($invoice, $upload_dir, $serial, 'invoice');
$wcard_filename = $this->handlePictureUpload($warr_card, $upload_dir, $serial, 'wcard');
$user_id = $req->query->get('api_key');
$log_data = [
'plate_number' => $req->request->get('plate_num'),
'first_name' => $req->request->get('first_name'),
'last_name' => $req->request->get('last_name'),
'date_purchase' => $req->request->get('date_purchase'),
];
$action = 'create';
$source = WarrantySource::MOBILE;
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
{
$logger->logWarrantyInfo($log_data, $res->getErrorMessage(), $user_id, $action, $source);
return $res->getReturnResponse();
}
// update customer information
// $cust = $this->updateCustomerInfo($req, $em);
// update warranty
$res = $this->updateWarranty($res, $em, $rt, $trans, $req, $serial, $inv_filename, $wcard_filename,
$logger, $log_data, $user_id, $action, $source);
$em->flush();
return $res->getReturnResponse();
}
public function getAllOngoingJobOrders(EntityManagerInterface $em, Request $req, RiderTracker $rt)
{
$required_params = [];
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
$ongoing_jos = $this->getOngoingJobOrders($cust);
// initialize data
$jo_data = [];
foreach ($ongoing_jos as $jo)
{
$jo_data[] = $this->generateJobOrderData($req, $jo, $rt);
}
$data = [
'ongoing_job_orders' => $jo_data,
];
$res->setData($data);
return $res->getReturnResponse();
}
public function getOngoingJobOrderCount(EntityManagerInterface $em, Request $req)
{
$required_params = [];
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
$ongoing_jos = $this->getOngoingJobOrders($cust);
$data = [
'ongoing_job_order_count' => count($ongoing_jos),
];
$res->setData($data);
return $res->getReturnResponse();
}
public function addLocation(EntityManagerInterface $em, Request $req)
{
$required_params = [
'name',
'address',
'longitude',
'latitude',
'landmark',
];
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// get the information
$name = $req->request->get('name');
$address = $req->request->get('address');
$lng = $req->request->get('longitude');
$lat = $req->request->get('latitude');
$landmark = $req->request->get('landmark');
$loc_info = [
'address' => $address,
'longitude' => $lng,
'latitude' => $lat,
'landmark' => $landmark,
];
// check if customer already has existing metadata
$c_meta = $em->getRepository(CustomerMetadata::class)->findOneBy(['customer' => $cust]);
if ($c_meta == null)
{
// create new customer meta
$cust_meta = new CustomerMetadata();
$cust_meta->setCustomer($cust);
$cust_meta->addMetaInfo($name, $loc_info);
$em->persist($cust_meta);
}
else
{
// limit locations to 6. If more than 6, pop the first one out
// add location to existing customer meta
$meta_count = count($c_meta->getAllMetaInfo());
if ($meta_count >= 6)
$c_meta->popMetaInfo();
$c_meta->addMetaInfo($name, $loc_info);
}
$em->flush();
return $res->getReturnResponse();
}
public function getLocations(EntityManagerInterface $em, Request $req)
{
$required_params = [];
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// get the customer meta for customer
$locations = [];
$cust_meta = $em->getRepository(CustomerMetadata::class)->findOneBy(['customer' => $cust]);
if ($cust_meta != null)
{
$locations[] = $cust_meta->getAllMetaInfo();
}
$data = [
'locations' => $locations,
];
$res->setData($data);
return $res->getReturnResponse();
}
public function removeVehicle($id, EntityManagerInterface $em, Request $req)
{
$required_params = [];
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// find customer vehicle
$cv = $em->getRepository(CustomerVehicle::class)->find($id);
if ($cv == null)
{
$res->setError(true)
->setErrorMessage('Invalid customer vehicle id');
return $res->getReturnResponse();
}
// confirm that customer vehicle belongs to customer
if ($cv->getCustomer()->getID() != $cust->getID())
{
$res->setError(true)
->setErrorMessage('Vehicle does not belong to customer');
return $res->getReturnResponse();
}
// we cannot remove a vehicle from customer if customer vehicle has already has JOs for it.
// instead we set the customer vehicle's flag_active to false
$cv->setActive(false);
$em->flush();
// response
return $res->getReturnResponse();
}
public function getLatestJobOrder(EntityManagerInterface $em, Request $req, RiderTracker $rt)
{
// check required parameters and api key
$res = $this->checkParamsAndKey($req, $em, []);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// get the latest job order for customer
$latest_jo = $em->getRepository(JobOrder::class)->findOneBy(['customer' => $cust], ['id' => 'DESC']);
$jo_data = null;
if ($latest_jo != null)
{
// TODO: clean the response up to just return what is needed
$jo_data = $this->generateLatestJobOrderData($req, $latest_jo, $rt);
}
$data = [
'latest_job_order' => $jo_data,
];
$res->setData($data);
// response
return $res->getReturnResponse();
}
public function getCustomerHash(Request $req, EntityManagerInterface $em, HashGenerator $hash)
{
// check required parameters and api key
$res = $this->checkParamsAndKey($req, $em, []);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
// hash customer id
$hashed_id = $hash->getHash($cust->getID());
$data = [
'cust_hash' => $hashed_id,
];
$res->setData($data);
// response
return $res->getReturnResponse();
}
// commenting it out. Modify the getJOHistory instead to just get the fulfilled
// and cancelled job orders, since ongoing is not yet part of history
/*
public function getCompletedJobOrders(Request $req, EntityManagerInterface $em, RiderTracker $rt)
{
$required_params = [];
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get customer
$cust = $this->session->getCustomer();
if ($cust == null)
{
$res->setError(true)
->setErrorMessage('No customer information found');
return $res->getReturnResponse();
}
$completed_jos = $this->getCompletedJOs($cust, $em);
// initialize data
$jo_data = [];
foreach ($completed_jos as $jo)
{
$jo_data[] = $this->generateJobOrderData($req, $jo, $rt);
}
$data = [
'completed_job_orders' => $jo_data,
];
$res->setData($data);
return $res->getReturnResponse();
}
protected function getCompletedJOs($cust, EntityManagerInterface $em)
{
$completed_jos = $em->getRepository(JobOrder::class)->findBy([
'customer' => $cust,
'status' => [JOStatus::CANCELLED, JOStatus::FULFILLED],
], ['date_schedule' => 'desc']);
return $completed_jos;
}
*/
protected function updateWarranty($res, $em, $rt, $trans, $req, $serial, $inv_filename = null, $wcard_filename = null,
$logger, $log_data, $user_id, $action, $source)
{
// get serial
$warr_serial = $em->getRepository(WarrantySerial::class)->find($serial);
if ($warr_serial == null)
{
$res->setError(true)
->setErrorMessage('Invalid warranty serial code.');
$logger->logWarrantyInfo($log_data, $res->getErrorMessage(), $user_id, $action, $source);
return $res;
}
// check if warranty exists already
$warr = $em->getRepository(Warranty::class)->findOneBy(['serial' => $serial]);
// skip warranty if it already exists
if ($warr != null)
{
/*
// NOTE: we could not update in the old version
$res->setError(true)
->setErrorMessage('Warranty registration entry already exists.');
return $res;
*/
// check if warranty is registered to a serial owned by customer
$warr_plate = $warr->getPlateNumber();
$cust = $this->session->getCustomer();
$is_customer_warranty = $this->checkCustomerPlateNumber($warr_plate, $cust);
if (!$is_customer_warranty)
{
$res->setError(true)
->setErrorMessage('Warranty registered to a vehicle not in your list of vehicles.');
$logger->logWarrantyInfo($log_data, $res->getErrorMessage(), $user_id, $action, $source);
return $res;
}
$sms_msg = $trans->trans('warranty_update_confirm');
}
else
{
$warr = new Warranty();
$sms_msg = $trans->trans('warranty_register_confirm');
// set warranty source
$warr->setCreateSource($source);
}
// get sap battery
$sku = $warr_serial->getSKU();
$sap_bty = null;
if ($sku != null)
{
$sap_bty = $em->getRepository(SAPBattery::class)->find($sku);
if ($sap_bty == null)
{
$res->setError(true)
->setErrorMessage('Could not find battery entry for warranty.');
$logger->logWarrantyInfo($log_data, $res->getErrorMessage(), $user_id, $action, $source);
return $res;
}
}
// default date purchase to today
// NOTE: might need to change this later
$date_pur = new DateTime();
// get date purchase specified by customer
$date_pur_cust = DateTime::createFromFormat('Y-m-d', $req->request->get('date_purchase'));
if (!$date_pur_cust)
{
$res->setError(true)
->setErrorMessage('Invalid date format for date of purchase.');
$logger->logWarrantyInfo($log_data, $res->getErrorMessage(), $user_id, $action, $source);
return $res;
}
$customer = $this->session->getCustomer();
if ($customer != null)
{
$warr->setCustomer($customer);
// get customer vehicles
$vehicle = $this->findCustomerVehicle($em, $customer, $req->request->get('plate_number'));
if ($vehicle != null)
$warr->setVehicle($vehicle);
}
// create or update warranty entry
$warr->setSerial($serial)
->setFirstName($req->request->get('first_name'))
->setLastName($req->request->get('last_name'))
->setEmail($req->request->get('email'))
->setPlateNumber($req->request->get('plate_number'))
// TODO: figure out how to compute date of purchase
->setDatePurchase($date_pur)
// TODO: set status
// ->setStatus()
// TODO: set battery model and size id
// ->setBatterySize()
// ->setBatteryModel()
->setSAPBattery($sap_bty)
->setMobileNumber(substr($this->session->getPhoneNumber(), 2))
->setActivated(true)
// files
->setFileInvoice($inv_filename)
->setFileWarrantyCard($wcard_filename)
// new fields
->setOdometer($req->request->get('odometer', 0))
->setDatePurchaseCustomer($date_pur_cust)
->setContactNumber($req->request->get('contact_num'))
->setCustomerAddress($req->request->get('cust_address'))
->setDealerName($req->request->get('dealer_name'))
->setDealerAddress($req->request->get('dealer_address'))
->setDealerBranchCode($req->request->get('branch_code'))
->setValidated(false);
// TODO: check for date purchase and date expire
$em->persist($warr);
// TODO: check if we need to do anyting else
$data = [];
// set data to retrun to user
$res->setData($data);
$logger->logWarrantyInfo($log_data, '', $user_id, $action, $source);
// send sms
error_log('sending sms to - ' . $this->session->getPhoneNumber());
$rt->sendSMS($this->session->getPhoneNumber(), $trans->trans('message.battery_brand_allcaps'), $sms_msg);
return $res;
}
protected function findCustomerByNumber($number)
{
$em = $this->getDoctrine()->getManager();
$customers = $em->getRepository(Customer::class)->findBy(['phone_mobile' => $number]);
// find the customer with the most number of cars
$car_count = 0;
$cust = null;
foreach($customers as $customer)
{
$vehicles = $customer->getVehicles();
if (count($vehicles) > $car_count)
{
$car_count = count($vehicles);
// "save" customer object
$cust = $customer;
}
}
return $cust;
}
protected function findCustomerVehicle($em, $customer, $plate_number)
{
$clean_plate = Warranty::cleanPlateNumber($plate_number);
if ($clean_plate)
{
// find the customer vehicle and get the vehicle
$cv = $em->getRepository(CustomerVehicle::class)->findOneBy(['plate_number' => $clean_plate, 'customer' => $customer]);
if ($cv != null)
{
$vehicle = $cv->getVehicle();
return $vehicle;
}
}
return null;
}
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)
{
// get number of available riders
$count_riders = count($nhd['hub']->getAvailableRiders());
// get number of advance orders in the next 3 hours
$time_now = new DateTime();
$date_end = new DateTime();
$date_end->add(new DateInterval('PT2H'));
// NOTE: get advance orders via query
// get JOs assigned to hub that are advance orders and scheduled within X hours with
// for rider assignment status
$query = $em->createQuery('select count(jo) from App\Entity\JobOrder jo where jo.hub = :hub and jo.flag_advance = true and jo.date_schedule <= :date_end and jo.status = :status');
$count_advance_orders = $query->setParameters([
'hub' => $nhd['hub'],
'date_end' => $date_end,
'status' => JOStatus::RIDER_ASSIGN,
])
->setMaxResults(1)
->getSingleScalarResult();
error_log('HUB - ' . $nhd['hub']->getID());
error_log('RIDER COUNT - ' . $count_riders);
error_log('ADVANCE ORDER COUNT - ' . $count_advance_orders);
// if (count($nhd['hub']->getAvailableRiders()) > 0)
// if we have more riders than we have advance orders
if ($count_riders - $count_advance_orders > 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;
}
protected function findAdvanceNearestHubAndSlots(Point $coordinates, EntityManagerInterface $em, MapTools $map_tools, $hub=null)
{
$hub_data = [];
if ($hub != null)
{
// get the slots of hub
$hub_slots = $this->getHubRiderSlots($hub, $em);
$slots = $hub_slots['slot_data'];
$hub_data = [
'hub' => $hub,
'slots' => $slots,
];
return $hub_data;
}
// get the nearest 10 hubs
$nearest_hubs_with_distance = [];
$hubs = $map_tools->getClosestOpenHubs($coordinates, 10);
foreach ($hubs as $hub)
{
$nearest_hubs_with_distance[] = $hub;
// TODO: insert checking for branch code here when inventory manager is up
}
$nearest = null;
$hub_slots = [];
$slot_found = false;
// find the nearest hub
if (!empty($nearest_hubs_with_distance))
{
// get slots of nearest hub right after getting nearest hub.
// then check if hub has available slots. If not, get next nearest hub.
foreach ($nearest_hubs_with_distance as $nhd)
{
if (empty($nearest))
{
// get the slots for the hub to check if hub is available for assignment
$hub_slots = $this->getHubRiderSlots($nhd['hub'], $em);
$flag_hub_available = $hub_slots['flag_hub_available'];
if ($flag_hub_available == true)
{
$nearest = $nhd;
}
}
else
{
if ($nhd['distance'] < $nearest['distance'])
{
// get the slots for nearest which is nhd right now
$hub_slots = $this->getHubRiderSlots($nhd['hub'], $em);
$flag_hub_available = $hub_slots['flag_hub_available'];
// if hub is available, set hub to nearest
if ($flag_hub_available == true)
{
$nearest = $nhd;
}
}
}
}
}
// set hub data to what is in nearest
$hub_data = [
'hub' => $nearest['hub'],
'slots' => $hub_slots['slot_data'],
];
return $hub_data;
}
protected function getHubRiderSlots(Hub $hub, EntityManagerInterface $em)
{
// check hub's advance orders for the day
/*
// get number of advance orders for the next day if request came in before midnight
// or for current day if request came in after midnight
// check request_time
$request_time = time();
$midnight = strtotime('00:00');
*/
$start_date = new DateTime();
$end_date = new DateTime();
// to keep things simple, just start on next day regardless of midnight timer
$start_date->add(new DateInterval('P1D'));
$end_date->add(new DateInterval('P3D'));
/*
if ($request_time < $midnight)
{
// add +1 to start date to get the next day
// add +3 to date to end date to get the advance orders for the next three days
$start_date->add(new DateInterval('P1D'));
$end_date->add(new DateInterval('P1D'));
}
$end_date->add(new DateInterval('P2D'));
*/
// set time bounds for the start and end date
$start_date->setTime(0, 1);
$end_date->setTime(23, 59);
// NOTE: get advance orders via query
// get JOs assigned to hub that are advance orders and scheduled for the next three days with
// for hub assignment status
$query = $em->createQuery('select jo from App\Entity\JobOrder jo where jo.hub = :hub and jo.flag_advance = true and
jo.date_schedule >= :date_start and jo.date_schedule <= :date_end and jo.status != :status_cancelled
and jo.status != :status_fulfilled');
$jos_advance_orders = $query->setParameters([
'hub' => $hub,
'date_start' => $start_date,
'date_end' => $end_date,
'status_cancelled' => JOStatus::CANCELLED,
'status_fulfilled' => JOStatus::FULFILLED,
])
->getResult();
// check request_time
// define slots
$slots = [
'08_09' => '8:00 AM',
'09_10' => '9:00 AM',
'10_11' => '10:00 AM',
'11_12' => '11:00 AM',
'12_13' => '12:00 PM',
'13_14' => '1:00 PM',
'14_15' => '2:00 PM',
'15_16' => '3:00 PM',
'16_17' => '4:00 PM',
];
// get the dates for the next three days
$first_date = $start_date->format('Y-m-d');
$second_date = $start_date->add(new DateInterval('P1D'));
$sec_date = $second_date->format('Y-m-d');
$third_date = $end_date->format('Y-m-d');
// define days
$days = [
$first_date => $first_date,
$sec_date => $sec_date,
$third_date => $third_date,
];
// initialize hub rider slots
$hub_rider_slots = [];
foreach ($days as $day)
{
foreach ($slots as $slot_key => $slot)
{
$hub_rider_slots[$day][$slot_key] = $hub->getRiderSlots();
}
}
// check each JO's date_schedule, decrement rider_slots if date schedule falls in that slot
foreach ($jos_advance_orders as $jo)
{
// get date key
$date_sched = $jo->getDateSchedule();
$date_string = $date_sched->format('Y-m-d');
$hour = $date_sched->format('H');
$slot_id = sprintf('%02d_%02d', $hour, $hour + 1);
error_log("SLOT - $date_string - $slot_id");
// decrement rider slot
if (isset($hub_rider_slots[$date_string][$slot_id]))
$hub_rider_slots[$date_string][$slot_id]--;
// check if it goes through next slot (10 min allowance)
$mins = $date_sched->format('i');
if ($mins > 10)
{
$next_slot_id = sprintf('%02d_%02d', $hour + 1, $hour + 2);
error_log("NEXT SLOT - $date_string - $next_slot_id");
// decrement rider slot
if (isset($hub_rider_slots[$date_string][$next_slot_id]))
$hub_rider_slots[$date_string][$next_slot_id]--;
}
}
error_log(print_r($hub_rider_slots, true));
$hub_slots = $this->generateHubSlots($hub_rider_slots, $slots);
error_log(print_r($hub_slots, true));
return $hub_slots;
}
protected function generateHubSlots($rider_slots, $slots)
{
$data = [];
$total_rslots = 0;
$total_unavailable_rslots = 0;
foreach ($rider_slots as $day_id => $rslot)
{
$data[$day_id] = [];
foreach ($rslot as $slot_id => $avail_slots)
{
// increment total rider slots
$total_rslots++;
$slot_data = [
'id' => $slot_id,
'label' => $slots[$slot_id],
'available' => true,
];
// mark unavailable ones
if ($avail_slots <= 0)
{ // increment total number of unavailable slots
$total_unavailable_rslots++;
$slot_data['available'] = false;
}
// add to day data
$data[$day_id][] = $slot_data;
}
}
// check if hub has available slots
$hub_available = true;
error_log('total rider slots ' . $total_rslots);
error_log('total unavailable slots ' . $total_unavailable_slots);
if ($total_rslots == $total_unavailable_rslots)
{
error_log('hub has no available slots');
$hub_available = false;
}
$hs_data = [
'flag_hub_available' => $hub_unavailable,
'slot_data' => $data,
];
return $hs_data;
}
protected function getTimeFromSlot($slot_id)
{
$time_selected = '';
switch($slot_id) {
case '08_09':
$time_selected = AdvanceOrderSlot::_08_09;
break;
case '09_10':
$time_selected = AdvanceOrderSlot::_09_10;
break;
case '10_11':
$time_selected = AdvanceOrderSlot::_10_11;
break;
case '11_12':
$time_selected = AdvanceOrderSlot::_11_12;
break;
case '12_13':
$time_selected = AdvanceOrderSlot::_12_13;
break;
case '13_14':
$time_selected = AdvanceOrderSlot::_13_14;
break;
case '14_15':
$time_selected = AdvanceOrderSlot::_14_15;
break;
case '15_16':
$time_selected = AdvanceOrderSlot::_15_16;
break;
case '16_17':
$time_selected = AdvanceOrderSlot::_16_17;
break;
default:
error_log('Invalid slot id ' . $slot_id);
}
return $time_selected;
}
protected function generateLatestJobOrderData($req, $jo, $rt)
{
$status = $jo->getStatus();
$dest = $jo->getCoordinates();
$jo_data = [
'id' => $jo->getID(),
'date_create' => $jo->getDateCreate()->format('M d, Y'),
'service_type' => $jo->getServiceType(),
'destination' => [
'long' => $dest->getLongitude(),
'lat' => $dest->getLatitude(),
],
'delivery_address' => $jo->getDeliveryAddress(),
'delivery_instructions' => $jo->getDeliveryInstructions(),
'jo_status' => $status,
'status' => $this->generateAPIRiderStatus($status),
'landmark' => $jo->getLandmark(),
];
// customer vehicle and warranty
$cv = $jo->getCustomerVehicle();
// get latest warranty using plate number
$warranty = $this->findWarranty($cv->getPlateNumber());
$jo_data['customer_vehicle'] = [
'id' => $cv->getID(),
'plate_number' => $cv->getPlateNumber(),
'warranty' => $warranty,
];
// customer information
$customer = $jo->getCustomer();
$jo_data['customer'] = [
'first_name' => $customer->getFirstName(),
'last_name' => $customer->getLastName(),
'mobile_number' => $customer->getPhoneMobile(),
];
// rider
$rider = $jo->getRider();
if ($rider != null)
{
// default image url
$url_prefix = $req->getSchemeAndHttpHost();
$image_url = $url_prefix . '/assets/images/user.gif';
if ($rider->getImageFile() != null)
$image_url = $url_prefix . '/uploads/' . $rider->getImageFile();
$coord = $rt->getRiderLocation($rider->getID());
$jo_data['rider'] = [
'id' => $rider->getID(),
'name' => $rider->getFullName(),
'plate_num' => $rider->getPlateNumber(),
'contact_num' => $rider->getContactNumber(),
'image_url' => $image_url,
'location' => [
'long' => $coord->getLongitude(),
'lat' => $coord->getLatitude()
]
];
}
else
{
$jo_data['rider'] = null;
}
// invoice items
$items = [];
$jo_items = $jo->getInvoice()->getItems();
foreach ($jo_items as $item)
{
$items[] = [
'id' => $item->getID(),
'title' => $item->getTitle(),
'qty' => $item->getQuantity(),
'price' => $item->getPrice(),
];
}
$jo_data['items'] = $items;
// dates depending on status
switch ($status)
{
case JOStatus::FULFILLED:
if ($jo->getDateFulfill() == null)
$jo_data['date_fulfilled'] = '';
else
$jo_data['date_fulfilled'] = $jo->getDateFulfill()->format('M d, Y');
break;
case JOStatus::CANCELLED:
$date_cancel = $jo->getDateCancel();
if ($date_cancel == null)
$date_cancel = new DateTime();
$jo_data['date_cancelled'] = $date_cancel->format('M d, Y');
break;
}
return $jo_data;
}
}