resq/src/Controller/TAPI/JobOrderController.php

1657 lines
56 KiB
PHP

<?php
namespace App\Controller\TAPI;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\HttpFoundation\Request;
use Doctrine\ORM\Query;
use Doctrine\ORM\EntityManagerInterface;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use Catalyst\APIBundle\Controller\APIController;
use Catalyst\APIBundle\Response\APIResponse;
use App\Ramcar\WarrantyClass;
use App\Ramcar\JOStatus;
use App\Ramcar\AdvanceOrderSlot;
use App\Ramcar\AutoAssignStatus;
use App\Ramcar\InvoiceCriteria;
use App\Ramcar\ServiceType;
use App\Ramcar\TransactionOrigin;
use App\Ramcar\TradeInType;
use App\Ramcar\JOEventType;
use App\Ramcar\HubCriteria;
use App\Ramcar\ModeOfPayment;
use App\Ramcar\APIRiderStatus;
use App\Service\InvoiceGeneratorInterface;
use App\Service\RisingTideGateway;
use App\Service\MQTTClient;
use App\Service\GeofenceTracker;
use App\Service\InventoryManager;
use App\Service\RiderAssignmentHandlerInterface;
use App\Service\HubSelector;
use App\Service\HubDistributor;
use App\Service\HubFilterLogger;
use App\Service\HubFilteringGeoChecker;
use App\Service\RiderTracker;
use App\Service\PromoLogger;
use App\Service\MapTools;
use App\Entity\JobOrder;
use App\Entity\CustomerVehicle;
use App\Entity\Promo;
use App\Entity\Battery;
use App\Entity\JOEvent;
use App\Entity\Customer;
use App\Entity\Hub;
use App\Entity\Invoice;
use DateTime;
use DateInterval;
use Catalyst\APIBundle\Access\Generator as ACLGenerator;
class JobOrderController extends APIController
{
protected $acl_gen;
public function __construct(ACLGenerator $acl_gen)
{
$this->acl_gen = $acl_gen;
}
// TODO: break this monolithic method down
public function requestJobOrder(Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo,
InventoryManager $im, MQTTClient $mclient,
RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger,
HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger,
HubFilteringGeoChecker $hub_geofence, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('tapi_jo.request', null, 'No access.');
// check required parameters and api key
$required_params = [
'service_type',
'trade_in_type',
'longitude',
'latitude',
'mode_of_payment',
'first_name',
'last_name',
'mobile_number',
'vehicle_manufacturer',
'vehicle_model',
'plate_number'
];
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// get data from request
$data = $this->getJobOrderRequestInfo($req);
// process customer and vehicle information
$this->processCustomerAndVehicleInformation();
// 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
// TODO: need to modify how to get 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);
$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;
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)
{
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, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('tapi_jo.get.estimate', null, 'No access.');
// check required parameters and api key
$required_params = [
'service_type',
'cv_id',
// 'batt_id',
'trade_in',
];
$res = $this->checkParamsAndKey($req, $em, $required_params);
if ($res->isError())
return $res->getReturnResponse();
// customer
// TODO: needs to be modified to get 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;
$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;
// set data
$res->setData($data);
return $res->getReturnResponse();
}
public function getJOInvoice(Request $req, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('tapi_jo.get.invoice', null, 'No access.');
$required_params = [
'jo_id',
];
$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
// TODO: modify to 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);
return $res->getReturnResponse();
}
public function cancelJobOrder(Request $req, MQTTClient $mclient, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('tapi_jo.cancel', null, 'No access.');
$required_params = [
'jo_id',
'reason'
];
$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
// TODO: modify to 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();
// TODO: do we need this?
// send mobile app event
$payload = [
'event' => 'cancelled',
'reason' => $cancel_reason,
'jo_id' => $jo->getID(),
];
$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)
{
$this->denyAccessUnlessGranted('tapi_jo.get.info', null, 'No access.');
// check required parameters and api key
$res = $this->checkParamsAndKey($req, $em, []);
if ($res->isError())
return $res->getReturnResponse();
// get customer
// TODO: modify to 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();
}
public function getJOHistory(Request $req, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('tapi_jo.get.history', null, 'No access.');
$res = $this->checkParamsAndKey($req, $em, []);
if ($res->isError())
return $res->getReturnResponse();
// get customer
// TODO: modify to find 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 = [];
// 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(), $em);
$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 locationSupport(Request $req, GeofenceTracker $geo, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('tapi_jo.location.support', null, 'No access.');
$required_params = [
'longitude',
'latitude',
];
$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
// TODO: modify to find customer if we still need this
$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);
}
$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();
}
// TODO: should we change to the HubSelector?
public function getNearestHubAndSlots(Request $req, EntityManagerInterface $em,
MapTools $map_tools)
{
$this->denyAccessUnlessGranted('tapi_jo.nearest_hub.get', null, 'No access.');
$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
// TODO: modify how we get customer
$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
// TODO: do we still need this?
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. Please expect a call from us 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. Please expect a call from us 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 scheduleOptionStatus(Request $req, EntityManagerInterface $em)
{
$this->denyAccessUnlessGranted('tapi_jo.schedule_option.status', null, 'No access.');
// check required parameters and api key
$required_params = [];
$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
// TODO: modify how we get customer
$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 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(), $em);
$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;
}
protected function findWarranty($plate_number, EntityManagerInterface $em)
{
// 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;
}
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;
}
}
}
}
}
if ($nearest != null)
{
// 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 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 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 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;
}
protected function getJobOrderRequestInfo(Request $req)
{
return $data;
}
// TODO: add function to clean plate number
// TODO: add function to normalize strings (lowercase them since the types are lowercase)
}