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