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) { // send sms to number $message = "Your Resq confirmation code is $code."; $rt->sendSMS($phone_number, 'MOTOLITE', $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 == '639991112233') { $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(); $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(); 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(); } public function requestJobOrder(Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo, MapTools $map_tools, InventoryManager $im, MQTTClient $mclient, RiderAssignmentHandlerInterface $rah) { // 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); // assign hub and rider if (($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) || ($jo->getServicetype() == ServiceType::BATTERY_REPLACEMENT_WARRANTY)) { // get nearest hub // $nearest_hub = $this->findNearestHubWithInventory($jo, $batt, $em, $map_tools, $im); $nearest_hub = $this->findNearestHub($jo, $em, $map_tools); } else { $nearest_hub = $this->findNearestHub($jo, $em, $map_tools); } if (!empty($nearest_hub)) { //error_log('found nearest hub ' . $nearest_hub->getID()); // assign rider $available_riders = $nearest_hub->getAvailableRiders(); if (count($available_riders) > 0) { $assigned_rider = null; if (count($available_riders) > 1) { // 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); } else $assigned_rider = $available_riders[0]; //error_log('found rider ' . $assigned_rider->getID()); $jo->setHub($nearest_hub); $jo->setRider($assigned_rider); $jo->setStatus(JOStatus::ASSIGNED); $assigned_rider->setAvailable(false); } } $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' => $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 ]; // 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 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], ]); 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(); } 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(); foreach ($jos as $jo) { $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(); if ($rider != null) { $jo_data['rider'] = $rider->getFullName(); } // 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'); // geofence $is_covered = $geo->isCovered($long, $lat); $data = [ 'longitude' => $long, 'latitude' => $lat, 'supported' => $is_covered, ]; $res->setData($data); 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) { $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'], ]; } $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')); $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) { // 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', ''); // 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(); } $hub = null; $hub_id = $req->request->get('hub_id'); 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'); $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); // 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) { // find nearest hub if (($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) || ($jo->getServicetype() == ServiceType::BATTERY_REPLACEMENT_WARRANTY)) { // get nearest hub // $nearest_hub = $this->findNearestHubWithInventory($jo, $batt, $em, $map_tools, $im); $nearest_hub = $this->findNearestHub($jo, $em, $map_tools); } else { $nearest_hub = $this->findNearestHub($jo, $em, $map_tools); } if (!empty($nearest_hub)) { //error_log('found nearest hub ' . $nearest_hub->getID()); // assign rider $available_riders = $nearest_hub->getAvailableRiders(); if (count($available_riders) > 0) { $assigned_rider = null; if (count($available_riders) > 1) { // 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); } else $assigned_rider = $available_riders[0]; //error_log('found rider ' . $assigned_rider->getID()); $jo->setHub($nearest_hub); $jo->setRider($assigned_rider); $jo->setStatus(JOStatus::ASSIGNED); $jo->setStatusAutoAssign(AutoAssignStatus::HUB_AND_RIDER_ASSIGNED); $assigned_rider->setAvailable(false); } } } else { $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')); } } $jo->setHub($hub); $jo->setStatus(JOStatus::RIDER_ASSIGN); $jo->setStatusAutoAssign(AutoAssignStatus::HUB_ASSIGNED); if ($date_schedule != null) $jo->setDateSchedule($date_schedule); } $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' => $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 ]; // 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'); $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; // TODO: 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'); if (($hour < 8) || ($hour > 16)) $schedule_choice = false; $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; } 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 $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() ?? '', ]; } 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' => '', ]; } } 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' => '', ]; } $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'], '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 = 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(); } 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 registred 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'); } // 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; } // 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')) ->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(), 'MOTOLITE', $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 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) { // get the nearest 10 hubs $hub_data = []; $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; $slot_found = false; // find the nearest hub if (!empty($nearest_hubs_with_distance)) { foreach ($nearest_hubs_with_distance as $nhd) { if (empty($nearest)) $nearest = $nhd; else { if ($nhd['distance'] < $nearest['distance']) $nearest = $nhd; } } // get slots of nearest hub if ($nearest != null) { $hub_slots = $this->getHubRiderSlots($nearest['hub'], $em); $hub_data = [ 'hub' => $nearest['hub'], 'slots' => $hub_slots, ]; } } 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); return $hub_slots; } protected function generateHubSlots($rider_slots, $slots) { $data = []; foreach ($rider_slots as $day_id => $rslot) { $data[$day_id] = []; foreach ($rslot as $slot_id => $avail_slots) { $slot_data = [ 'id' => $slot_id, 'label' => $slots[$slot_id], 'available' => true, ]; // mark unavailable ones if ($avail_slots <= 0) $slot_data['available'] = false; // add to day data $data[$day_id][] = $slot_data; } } return $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; } }