em = $em; $this->redis = $redis; $this->ef = $ef; $this->rcache = $rcache; $this->country_code = $country_code; $this->mclient = $mclient; // one device = one session, since we have control over the devices // when a rider logs in, we just change the rider assigned to the device // when a rider logs out, we remove the rider assigned to the device $this->session = null; } public function register(Request $req) { // confirm parameters $required_params = [ 'phone_number', 'device_push_id' ]; $missing = $this->checkMissingParameters($req, $required_params); if (count($missing) > 0) { $params = implode(', ', $missing); $data = [ 'error' => 'Missing parameter(s): ' . $params ]; return $data; } // retry until we get a unique id while (true) { try { // instantiate session $sess = new RiderSession(); $sess->setPhoneNumber($req->request->get('phone_number')) ->setDevicePushID($req->request->get('device_push_id')); // reopen in case we get an exception if (!$this->em->isOpen()) { $this->em = $this->em->create( $this->em->getConnection(), $this->em->getConfiguration() ); } // save $this->em->persist($sess); $this->em->flush(); // create redis entry for the session $redis_client = $this->redis->getRedisClient(); $redis_key = 'rider.id.' . $sess->getID(); error_log('redis_key: ' . $redis_key); $redis_client->set($redis_key, ''); } catch (DBALException $e) { error_log($e->getMessage()); // delay one second and try again sleep(1); continue; } break; } // return data $data = [ 'session_id' => $sess->getID() ]; return $data; } public function login(Request $req) { $required_params = [ 'user', 'pass', ]; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) return $data; // check if session has a rider already if ($this->session->hasRider()) { $data = [ 'error' => 'Another rider is already logged in. Please logout first.' ]; return $data; } // look for rider with username $rider = $this->em->getRepository(Rider::class)->findOneBy(['username' => $req->request->get('user')]); if ($rider == null) { $data = [ 'error' => 'Invalid username or password.' ]; return $data; } // check if rider password is correct $encoder = $this->ef->getEncoder(new User()); if (!$encoder->isPasswordValid($rider->getPassword(), $req->request->get('pass'), '')) { $data = [ 'error' => 'Invalid username or password.' ]; return $data; } // assign rider to session $this->session->setRider($rider); $rider->setAvailable(true); $rider_id = $rider->getID(); // cache rider location (default to hub) // TODO: figure out longitude / latitude default $this->rcache->addActiveRider($rider_id, 0, 0); // TODO: log rider logging in $this->em->flush(); // update redis rider.id. with the rider id $redis_client = $this->redis->getRedisClient(); $redis_key = 'rider.id.' . $this->session->getID(); $rider_id = $rider->getID(); $redis_client->set($redis_key, $rider_id); $hub = $rider->getHub(); if ($hub == null) $hub_data = null; else { $coord = $hub->getCoordinates(); $hub_data = [ 'id' => $hub->getID(), 'name' => $hub->getName(), 'branch' => $hub->getBranch(), 'longitude' => $coord->getLongitude(), 'latitude' => $coord->getLatitude(), 'contact_nums' => $hub->getContactNumbers(), ]; } // data $data = [ 'hub' => $hub_data, 'rider_id' => $rider_id, ]; return $data; } public function logout(Request $req) { $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) return $data; // make rider unavailable $rider = $this->session->getRider(); $rider->setAvailable(false); // remove from cache $this->rcache->removeActiveRider($rider->getID()); // remove rider from session $this->session->setRider(null); // TODO: log rider logging out $this->em->flush(); return $data; } public function getJobOrder(Request $req) { // get the job order of the rider assigned to this session $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) return $data; // are we logged in? if (!$this->session->hasRider()) { $data = [ 'error' => 'No logged in rider.' ]; return $data; } $rider = $this->session->getRider(); // do we have a job order? $jo = $rider->getActiveJobOrder(); if ($jo == null) { $data = [ 'job_order' => null ]; } else { $coord = $jo->getCoordinates(); $cust = $jo->getCustomer(); $cv = $jo->getCustomerVehicle(); $v = $cv->getVehicle(); $inv = $jo->getInvoice(); $promo = $inv->getPromo(); // 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, ]; } // promo if ($promo != null) { $promo_data = [ 'id' => $promo->getID(), 'name' => $promo->getName(), 'code' => $promo->getCode(), 'discount_rate' => $promo->getDiscountRate(), 'discount_apply' => $promo->getDiscountApply(), ]; } else { $promo_data = null; } $trade_in_type = $jo->getTradeInType(); if (empty($trade_in_type)) $trade_in_type = 'none'; $data = [ 'job_order' => [ 'id' => $jo->getID(), 'service_type' => $jo->getServiceType(), 'date_schedule' => $jo->getDateSchedule()->format('Ymd H:i:s'), 'longitude' => $coord->getLongitude(), 'latitude' => $coord->getLatitude(), 'status' => $jo->getStatus(), 'customer' => [ 'title' => $cust->getTitle(), 'first_name' => $cust->getFirstName(), 'last_name' => $cust->getLastName(), 'phone_mobile' => $this->country_code . $cust->getPhoneMobile(), ], 'vehicle' => [ 'manufacturer' => $v->getManufacturer()->getName(), 'make' => $v->getMake(), 'model' => $cv->getModelYear(), 'plate_number' => $cv->getPlateNumber(), 'color' => $cv->getColor(), ], 'or_num' => $jo->getORNum(), 'or_name' => $jo->getORName(), 'delivery_instructions' => $jo->getDeliveryInstructions(), 'delivery_address' => $jo->getDeliveryAddress(), 'landmark' => $jo->getLandmark(), 'invoice' => [ 'discount' => $inv->getDiscount(), 'trade_in' => $inv->getTradeIn(), 'total_price' => $inv->getTotalPrice(), 'vat' => $inv->getVat(), 'items' => $inv_items, ], 'mode_of_payment' => $jo->getModeOfPayment(), 'trade_in_type' => $trade_in_type, 'promo' => $promo_data, // TODO: load the actual 'has_warranty_doc' => false, 'flag_coolant' => $jo->hasCoolant(), 'has_motolite' => $cv->hasMotoliteBattery(), ] ]; } return $data; } public function acceptJobOrder(Request $req) { $required_params = ['jo_id']; $data = $this->checkJO($req, $required_params, $jo); if (isset($data['error'])) return $data; // TODO: refactor this into a jo handler class, so we don't have to repeat for control center // set jo status to in transit $jo->setStatus(JOStatus::IN_TRANSIT); // TODO: send mqtt event (?) // add event log $rider = $this->session->getRider(); $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::RIDER_ACCEPT) ->setJobOrder($jo) ->setRider($rider); $this->em->persist($event); $this->em->flush(); return $data; } public function cancelJobOrder(Request $req) { $required_params = ['jo_id']; $data = $this->checkJO($req, $required_params, $jo); if (isset($data['error'])) return $data; // $jo->cancel("rider cancelled"); // requeue it, instead of cancelling it $jo->requeue(); // add event log $rider = $this->session->getRider(); $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::REQUEUE) ->setJobOrder($jo) ->setRider($rider); $this->em->persist($event); $this->em->flush(); // send mqtt event // send outlet assign since order should go back to hub and await reassignment to another rider $payload = [ 'event' => 'outlet_assign', 'jo_id' => $jo->getID(), ]; $this->mclient->sendEvent($jo, $payload); return $data; } public function arrive(Request $req) { $required_params = ['jo_id']; $data = $this->checkJO($req, $required_params, $jo); if (isset($data['error'])) return $data; // TODO: refactor this into a jo handler class, so we don't have to repeat for control center // set jo status to in progress $jo->setStatus(JOStatus::IN_PROGRESS); // add event log $rider = $this->session->getRider(); $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::RIDER_ARRIVE) ->setJobOrder($jo) ->setRider($rider); $this->em->persist($event); $this->em->flush(); // send mqtt event $rider = $this->session->getRider(); $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; if ($rider->getImageFile() != null) $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); $payload = [ 'event' => 'driver_arrived', 'jo_id' => $jo->getID(), 'driver_image' => $image_url, 'driver_name' => $rider->getFullName(), 'driver_id' => $rider->getID(), ]; $this->mclient->sendEvent($jo, $payload); return $data; } public function hubArrive(Request $req) { $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) return $data; // are we logged in? if (!$this->session->hasRider()) { $data = [ 'error' => 'No logged in rider.' ]; return $data; } // TODO: tag rider as available $this->em->flush(); return $data; } 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 ($check == null) $missing[] = $param; } else if ($req->getMethod() == 'POST') { $check = $req->request->get($param); if ($check == null) $missing[] = $param; } else return $params; } return $missing; } protected function checkParamsAndKey(Request $req, $params) { $data = []; // check for api_key in query string $api_key = $req->query->get('api_key'); if (empty($api_key)) { $data = [ 'error' => 'Missing API key' ]; return $data; } // check missing parameters $missing = $this->checkMissingParameters($req, $params); if (count($missing) > 0) { $miss_string = implode(', ', $missing); $data = [ 'error' => 'Missing parameter(s): ' . $miss_string ]; return $data; } // check api key $sess = $this->checkAPIKey($req->query->get('api_key')); if ($sess == null) { $data = [ 'error' => 'Invalid API Key' ]; return $data; } // store session $this->session = $sess; return $data; } protected function checkAPIKey($api_key) { // find the api key (session id) $session = $this->em->getRepository(RiderSession::class)->find($api_key); if ($session == null) return null; return $session; } protected function checkJO(Request $req, $required_params, &$jo = null) { // set jo status to in transit $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) return $data; // are we logged in? if (!$this->session->hasRider()) { $data = [ 'error' => 'No logged in rider.' ]; return $data; } $rider = $this->session->getRider(); // check if we have an active JO $jo = $rider->getActiveJobOrder(); if ($jo == null) { $data = [ 'error' => 'No active job order.' ]; return $data; } // check if the jo_id sent is the same as our active jo if ($req->request->get('jo_id') != $jo->getID()) { $data = [ 'error' => 'Job order selected is not active job order.' ]; return $data; } return $data; } }