resq/src/Controller/CustomerAppAPI/VehicleController.php

564 lines
19 KiB
PHP

<?php
namespace App\Controller\CustomerAppAPI;
use Symfony\Component\HttpFoundation\Request;
use Catalyst\ApiBundle\Component\Response as ApiResponse;
use App\Entity\CustomerVehicle;
use App\Entity\JobOrder;
use App\Entity\VehicleManufacturer;
use App\Entity\Vehicle;
use App\Ramcar\JOStatus;
use App\Ramcar\ServiceType;
use App\Ramcar\TradeInType;
use App\Ramcar\InsuranceApplicationStatus;
use App\Service\PayMongoConnector;
use DateTime;
class VehicleController extends ApiController
{
public function listVehicleManufacturers(Request $req)
{
// validate params
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// get manufacturer list
$mfgs = $this->em->getRepository(VehicleManufacturer::class)->findBy(['flag_mobile' => true], ['name' => 'asc']);
$mfg_list = [];
foreach ($mfgs as $mfg) {
$mfg_list[] = [
'id' => $mfg->getID(),
'name' => $mfg->getName(),
];
}
return new ApiResponse(true, '', [
'manufacturers' => $mfg_list,
]);
}
public function listVehicleMakes(Request $req, $mfg_id)
{
// validate params
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// get manufacturer
$mfg = $this->em->getRepository(VehicleManufacturer::class)->find($mfg_id);
if ($mfg == null) {
// response
return new ApiResponse(false, 'Invalid vehicle manufacturer id.');
}
// get makes
$vehicles = $this->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(),
];
}
// response
return new ApiResponse(true, '', [
'manufacturer' => [
'id' => $mfg->getID(),
'name' => $mfg->getName(),
],
'makes' => $vlist,
]);
}
public function addVehicle(Request $req)
{
// check requirements
$validity = $this->checkVehicleRequirements($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// customer vehicle
$cv = new CustomerVehicle();
// set object
$res = $this->setCustomerVehicleObject($req, $cv);
if (!$res['success']) {
return new ApiResponse(false, $res['error']);
}
// response
return new ApiResponse(true, '', [
'cv_id' => $res['cv_id'],
]);
}
public function getVehicle(Request $req, $id, PayMongoConnector $paymongo)
{
// check requirements
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// get customer vehicle
$cv = $this->em->getRepository(CustomerVehicle::class)->find($id);
// check if it exists
if ($cv == null) {
return new ApiResponse(false, 'Vehicle does not exist.');
}
// check if it's owned by customer
if ($cv->getCustomer()->getID() != $this->session->getCustomer()->getID()) {
return new ApiResponse(false, 'Invalid vehicle.');
}
// response
return new ApiResponse(true, '', [
'vehicle' => $this->generateVehicleInfo($cv, true, $paymongo),
]);
}
public function updateVehicle(Request $req, $id)
{
// check requirements
$validity = $this->checkVehicleRequirements($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// get customer vehicle
$cv = $this->em->getRepository(CustomerVehicle::class)->find($id);
// check if it exists
if ($cv == null) {
return new ApiResponse(false, 'Vehicle does not exist.');
}
// check if it's owned by customer
if ($cv->getCustomer()->getID() != $this->session->getCustomer()->getID()) {
return new ApiResponse(false, 'Invalid vehicle.');
}
// set object
$res = $this->setCustomerVehicleObject($req, $cv);
if (!$res['success']) {
return new ApiResponse(false, $res['error']);
}
// response
return new ApiResponse(true, '', [
'cv_id' => $res['cv_id'],
]);
}
public function getTradeInEstimate(Request $req, $id)
{
// check requirements
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// get customer vehicle
$cv = $this->em->getRepository(CustomerVehicle::class)->find($id);
// check if it exists
if ($cv == null) {
return new ApiResponse(false, 'Vehicle does not exist.');
}
// check if it's owned by customer
if ($cv->getCustomer()->getID() != $this->session->getCustomer()->getID()) {
return new ApiResponse(false, 'Invalid vehicle.');
}
// check trade in value
$result = $this->getTIEstimateByCV($cv);
// response
return new ApiResponse(true, '', [
'trade_in_batt' => $result['trade_in_batt'],
'trade_in_type' => $result['trade_in_type'],
'trade_in_value' => $result['trade_in_value'],
]);
}
public function listVehicles(Request $req, PayMongoConnector $paymongo)
{
// validate params
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// customer
$cust = $this->session->getCustomer();
if ($cust == null) {
return new ApiResponse(false, 'No customer information found.');
}
// vehicles
$cv_list = [];
// $cvs = $cust->getVehicles();
// only get the customer's vehicles whose flag_active is true
$cvs = $this->em->getRepository(CustomerVehicle::class)->findBy(['flag_active' => true, 'customer' => $cust]);
foreach ($cvs as $cv) {
$cv_list[] = $this->generateVehicleInfo($cv, true, $paymongo);
}
// response
return new ApiResponse(true, '', [
'vehicles' => $cv_list,
]);
}
public function getCompatibleBatteries(Request $req, $vid)
{
// validate params
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// get vehicle
$vehicle = $this->em->getRepository(Vehicle::class)->find($vid);
if ($vehicle == null) {
return new ApiResponse(false, 'Invalid vehicle.');
}
// batteries
$batt_list = [];
$batts = $vehicle->getActiveBatteries();
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),
];
}
// response
return new ApiResponse(true, '', [
'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,
]);
}
public function removeVehicle($id, Request $req)
{
// validate params
$validity = $this->validateRequest($req);
if (!$validity['is_valid']) {
return new ApiResponse(false, $validity['error']);
}
// get customer
$cust = $this->session->getCustomer();
if ($cust == null) {
return new ApiResponse(false, 'No customer information found.');
}
// find customer vehicle
$cv = $this->em->getRepository(CustomerVehicle::class)->find($id);
if ($cv == null) {
return new ApiResponse(false, 'Invalid customer vehicle id.');
}
// confirm that customer vehicle belongs to customer
if ($cv->getCustomer()->getID() != $cust->getID()) {
return new ApiResponse(false, 'Vehicle does not belong to customer.');
}
// 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);
$this->em->flush();
// response
return new ApiResponse();
}
protected function getTIEstimateByCV($cv)
{
// compute for trade in value
$trade_in_batt = null;
$trade_in_value = 0;
$trade_in_type = TradeInType::OTHER;
$previous_jo_found = false;
// check for last battery replacement JO
$last_jo = $this->em->getRepository(JobOrder::class)->findOneBy([
'service_type' => [
ServiceType::BATTERY_REPLACEMENT_NEW, ServiceType::BATTERY_REPLACEMENT_WARRANTY
],
'status' => JOStatus::FULFILLED,
'cus_vehicle' => $cv,
], ['date_create' => 'desc']);
if (!empty($last_jo)) {
$items = $last_jo->getInvoice()->getItems();
foreach ($items as $item) {
// find the first battery item and get its trade-in value
$item_battery = $item->getBattery();
if (!empty($item_battery)) {
$trade_in_type = TradeInType::MOTOLITE;
$trade_in_batt = $item_battery->getID();
$previous_jo_found = true;
$size = $item_battery->getSize();
$trade_in_value = $size->getTIPriceMotolite();
break;
}
}
}
// TODO: possibly refactor this bit
// if no valid previous JO is found, base the trade-in value on recommended batteries
if (!$previous_jo_found) {
$comp_batteries = $cv->getVehicle()->getBatteries();
// get the lowest trade-in value from the list of batteries
if (!empty($comp_batteries)) {
foreach ($comp_batteries as $battery) {
$size = $battery->getSize();
$size_ti = $size->getTIPriceOther();
// get the lowest value or set if not set yet
if ($size_ti < $trade_in_value || $trade_in_value == 0) {
$trade_in_value = $size_ti;
$trade_in_batt = $battery->getID();
}
}
}
}
return [
'trade_in_batt' => $trade_in_batt,
'trade_in_type' => $trade_in_type,
'trade_in_value' => $trade_in_value,
];
}
protected function generateVehicleInfo(CustomerVehicle $cv, $include_insurance = false, PayMongoConnector $paymongo)
{
$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();
$row = [
'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,
];
// get latest insurance row
if ($include_insurance) {
$insurance = null;
$iobj = $cv->getLatestInsuranceApplication();
if (!empty($iobj)) {
$gt = $iobj->getGatewayTransaction();
$date_complete = $iobj->getDateComplete();
$date_expire = $iobj->getDateExpire();
$status = $iobj->getStatus();
error_log("\r\nTHIS IS THE CURRENT STATUS: " . $status . "\r\n");
// handle the very transient state between a payment being made and receiving the paymongo webhook
// TODO: maybe handle this more elegantly. issue is not sure it is a good idea to update the db for this very transient status as the webhook listener also updates this status right away
switch ($status) {
case InsuranceApplicationStatus::CREATED:
// get latest status on this checkout from paymongo
$checkout = $paymongo->getCheckout($gt->getExtTransactionId());
if ($checkout['success']) {
$payment_intent = $checkout['response']['data']['attributes']['payment_intent'] ?? null;
if (!empty($payment_intent)) {
$intent_status = $payment_intent['attributes']['status'] ?? null;
// TODO: define these paymongo payment intent statuses elsewhere
if ($intent_status === 'processing' || $intent_status === 'succeeded') {
$status = InsuranceApplicationStatus::PAID;
}
}
}
break;
default:
break;
}
$insurance = [
'id' => $iobj->getID(),
'ext_transaction_id' => $iobj->getExtTransactionId(),
'status' => $status,
'coc_url' => $iobj->getCOC(),
'checkout_url' => $gt->getMetadata()['checkout_url'],
'transaction_status' => $gt->getStatus(),
'premium_amount' => (string)bcdiv($gt->getAmount(), 100), // NOTE: hard expressing as string so it's consistent
'date_submit' => $iobj->getDateSubmit()->format('Y-m-d H:i:s'),
'date_complete' => $date_complete ? $date_complete->format('Y-m-d H:i:s') : null,
'date_expire' => $date_expire ? $date_expire->format('Y-m-d H:i:s') : null,
];
// get information changelog
$insurance['changelog'] = $iobj->getMetadata()['changes'] ?? [];
}
$row['latest_insurance'] = $insurance;
}
return $row;
}
protected function checkVehicleRequirements(Request $req)
{
// validate params
return $this->validateRequest($req, [
'make_id',
'name',
'plate_num',
'model_year',
'color',
'condition',
'fuel_type',
]);
// 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
}
protected function setCustomerVehicleObject(Request $req, CustomerVehicle $cv)
{
// check customer
$cust = $this->session->getCustomer();
if ($cust == null) {
return [
'success' => false,
'error' => 'No customer information found.',
];
}
// get vehicle
$vehicle = $this->em->getRepository(Vehicle::class)->find($req->request->get('make_id'));
if ($vehicle == null) {
return [
'success' => false,
'error' => 'Invalid vehicle make id.',
];
}
$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($this->normalizeString($req->request->get('fuel_type')))
->setStatusCondition($this->normalizeString($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
$this->em->persist($cv);
$this->em->flush();
// response
return [
'success' => true,
'cv_id' => $cv->getID(),
];
}
protected function normalizeString($string)
{
return trim(strtolower($string));
}
}