diff --git a/catalyst/api-bundle/Entity/User.php b/catalyst/api-bundle/Entity/User.php index a55c88c6..ff6a7ba8 100644 --- a/catalyst/api-bundle/Entity/User.php +++ b/catalyst/api-bundle/Entity/User.php @@ -53,6 +53,18 @@ class User extends BaseUser */ protected $roles; + // rider linked to user + // NOTE: we're directly linking this only because we don't have to care about other apps using this library + /** + * @ORM\OneToOne(targetEntity="App\Entity\Rider", mappedBy="api_user") + */ + protected $rider; + + /** + * @ORM\Column(type="json") + */ + protected $metadata; + public function __construct() { parent::__construct(); @@ -63,6 +75,7 @@ class User extends BaseUser // set date created $this->date_create = new DateTime(); + $this->metadata = []; } public function getID() @@ -140,9 +153,34 @@ class User extends BaseUser return $this->generateKey('secret'); } + public function setMetadata($meta) + { + $this->metadata = $meta; + return $this; + } + + public function getMetadata() + { + if ($this->metadata == null) + return []; + + return $this->metadata; + } + protected function generateKey($prefix = '') { return md5(uniqid($prefix, true)); } + + public function setRider($rider) + { + $this->rider = $rider; + return $this; + } + + public function getRider() + { + return $this->rider; + } } diff --git a/config/packages/security.yaml b/config/packages/security.yaml index 4339eba4..2eecd1e8 100644 --- a/config/packages/security.yaml +++ b/config/packages/security.yaml @@ -16,6 +16,11 @@ security: pattern: ^/(_(profiler|wdt)|css|images|js)/ security: false + new_rider_api_login: + pattern: ^\/rider_api\/login$ + methods: [POST] + security: false + login: pattern: ^\/login$ methods: [GET] @@ -46,6 +51,14 @@ security: provider: api_key_user_provider user_checker: Catalyst\AuthBundle\Service\UserChecker + new_rider_api: + pattern: ^\/rider_api\/ + stateless: true + simple_preauth: + authenticator: Catalyst\APIBundle\Security\APIKeyAuthenticator + provider: api_key_user_provider + user_checker: Catalyst\AuthBundle\Service\UserChecker + main: provider: user_provider form_login: diff --git a/config/routes/capi_rider.yaml b/config/routes/capi_rider.yaml new file mode 100644 index 00000000..746cd609 --- /dev/null +++ b/config/routes/capi_rider.yaml @@ -0,0 +1,97 @@ +# rider app api + +capi_rider_register: + path: /rider_api/register + controller: App\Controller\CAPI\RiderAppController::register + methods: [POST] + +capi_rider_login: + path: /rider_api/login + controller: App\Controller\CAPI\RiderAppController::login + methods: [POST] + +capi_rider_logout: + path: /rider_api/logout + controller: App\Controller\CAPI\RiderAppController::logout + methods: [POST] + +capi_rider_jo_get: + path: /rider_api/joborder + controller: App\Controller\CAPI\RiderAppController::getJobOrder + methods: [GET] + +capi_rider_jo_accept: + path: /rider_api/accept + controller: App\Controller\CAPI\RiderAppController::acceptJobOrder + methods: [POST] + +capi_rider_jo_cancel: + path: /rider_api/cancel + controller: App\Controller\CAPI\RiderAppController::cancelJobOrder + methods: [POST] + +capi_rider_arrive: + path: /rider_api/arrive + controller: App\Controller\CAPI\RiderAppController::arrive + methods: [POST] + +capi_rider_payment: + path: /rider_api/payment + controller: App\Controller\CAPI\RiderAppController::payment + methods: [POST] + +capi_rider_hub_arrive: + path: /rider_api/hub_arrive + controller: App\Controller\CAPI\RiderAppController::hubArrive + methods: [POST] + +capi_rider_promos: + path: /rider_api/promos + controller: App\Controller\CAPI\RiderAppController::getPromos + methods: [GET] + +capi_rider_batteries: + path: /rider_api/batteries + controller: App\Controller\CAPI\RiderAppController::getBatteries + methods: [GET] + +capi_rider_change_service: + path: /rider_api/service + controller: App\Controller\CAPI\RiderAppController::changeService + methods: [POST] + +capi_rider_available: + path: /rider_api/available + controller: App\Controller\CAPI\RiderAppController::available + methods: [POST] + +capi_rider_hub_depart: + path: /rider_api/hub_depart + controller: App\Controller\CAPI\RiderAppController::hubDepart + methods: [POST] + +capi_rider_pre_hub_depart: + path: /rider_api/pre_hub_depart + controller: App\Controller\CAPI\RiderAppController::preHubDepart + methods: [POST] + +capi_rider_pre_hub_arrive: + path: /rider_api/pre_hub_arrive + controller: App\Controller\CAPI\RiderAppController::preHubArrive + methods: [POST] + +capi_rider_post_hub_depart: + path: /rider_api/post_hub_depart + controller: App\Controller\CAPI\RiderAppController::postHubDepart + methods: [POST] + +capi_rider_post_hub_arrive: + path: /rider_api/post_hub_arrive + controller: App\Controller\CAPI\RiderAppController::postHubArrive + methods: [POST] + +capi_rider_jo_start: + path: /rider_api/start + controller: App\Controller\CAPI\RiderAppController::startJobOrder + methods: [POST] + diff --git a/config/routes/rider_api.yaml b/config/routes/rider_api.yaml index bd38c492..b38cd6ae 100644 --- a/config/routes/rider_api.yaml +++ b/config/routes/rider_api.yaml @@ -64,3 +64,34 @@ rapi_available: path: /rapi/available controller: App\Controller\RAPIController::available methods: [POST] + +rapi_hub_depart: + path: /rapi/hub_depart + controller: App\Controller\RAPIController::hubDepart + methods: [POST] + +rapi_pre_hub_depart: + path: /rapi/pre_hub_depart + controller: App\Controller\RAPIController::preHubDepart + methods: [POST] + +rapi_pre_hub_arrive: + path: /rapi/pre_hub_arrive + controller: App\Controller\RAPIController::preHubArrive + methods: [POST] + +rapi_post_hub_depart: + path: /rapi/post_hub_depart + controller: App\Controller\RAPIController::postHubDepart + methods: [POST] + +rapi_post_hub_arrive: + path: /rapi/post_hub_arrive + controller: App\Controller\RAPIController::postHubArrive + methods: [POST] + +rapi_jo_start: + path: /rapi/start + controller: App\Controller\RAPIController::startJobOrder + methods: [POST] + diff --git a/src/Command/CreateRiderAPIUserCommand.php b/src/Command/CreateRiderAPIUserCommand.php new file mode 100644 index 00000000..96508fd8 --- /dev/null +++ b/src/Command/CreateRiderAPIUserCommand.php @@ -0,0 +1,92 @@ +em = $em; + + parent::__construct(); + } + + protected function configure() + { + $this->setName('api:user-create-for-rider') + ->setDescription('Create API users for existing riders.') + ->setHelp('Creates API users for existing riders.') + ->addArgument('role_id', InputArgument::REQUIRED, 'Role ID for api_user.'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + error_log('Creating api users...'); + // get all existing riders + $riders = $this->em->getRepository(Rider::class)->findAll(); + + // get role for rider api user + $role_id = $input->getArgument('role_id'); + $role = $this->em->getRepository(APIRole::class)->find($role_id); + if ($role == null) + { + error_log('Cannot find role with id ' . $role_id); + return 0; + } + + foreach ($riders as $rider) + { + // skip riders who already have users + if ($rider->getAPIUser() != null) + continue; + + // create api user for each rider + // no need to generate the keys. + // Secret and API keys are generated in constructor + $api_user = new APIUser(); + + // set enabled to true + $api_user->setEnabled(true); + + // set name to rider's last name + first name + $rider_name = $rider->getFirstName() . ' ' . $rider->getLastName(); + $api_user->setName($rider_name); + + // set rider to api_user + $api_user->setRider($rider); + + // set meta + $meta = ['rider_id' => $rider->getID()]; + $api_user->setMetaData($meta); + + // set role + $api_user->addRole($role); + + // set rider's api user + $rider->setAPIUser($api_user); + + $this->em->persist($api_user); + } + + $this->em->flush(); + + return 0; + } +} diff --git a/src/Controller/APIController.php b/src/Controller/APIController.php index 6d286ead..caea00c3 100644 --- a/src/Controller/APIController.php +++ b/src/Controller/APIController.php @@ -31,6 +31,7 @@ use App\Ramcar\AutoAssignStatus; use App\Ramcar\WarrantySource; use App\Ramcar\HubCriteria; use App\Ramcar\CustomerSource; +use App\Ramcar\DeliveryStatus; use App\Service\InvoiceGeneratorInterface; use App\Service\RisingTideGateway; @@ -1061,6 +1062,7 @@ class APIController extends Controller implements LoggedController // find nearest hubs $nearest_hubs = $hub_select->find($hub_criteria); + $assigned_rider = null; if (!empty($nearest_hubs)) { // go through the hub list, find the nearest hub @@ -1071,7 +1073,6 @@ class APIController extends Controller implements LoggedController $available_riders = $nearest_hub['hub']->getAvailableRiders(); if (count($available_riders) >= 1) { - $assigned_rider = null; if (count($available_riders) == 1) { $assigned_rider = $available_riders[0]; @@ -1097,6 +1098,9 @@ class APIController extends Controller implements LoggedController $assigned_rider->setAvailable(false); + // set rider's current job order + $assigned_rider->setCurrentJobOrder($jo); + // update redis hub_jo_count for hub $hub_dist->incrementJoCountForHub($nearest_hub['hub']); @@ -2947,9 +2951,13 @@ class APIController extends Controller implements LoggedController $jo->setRider($assigned_rider); $jo->setStatus(JOStatus::ASSIGNED); $jo->setStatusAutoAssign(AutoAssignStatus::HUB_AND_RIDER_ASSIGNED); + $jo->setDeliveryStatus(DeliveryStatus::RIDER_ASSIGN); $assigned_rider->setAvailable(false); + // set rider's current job order + $assigned_rider->setCurrentJobOrder($jo); + // update redis hub_jo_count for hub $hub_dist->incrementJoCountForHub($nearest_hub['hub']); diff --git a/src/Controller/APIRoleController.php b/src/Controller/APIRoleController.php index d2dbd888..f347d890 100644 --- a/src/Controller/APIRoleController.php +++ b/src/Controller/APIRoleController.php @@ -242,7 +242,7 @@ class APIRoleController extends Controller if (!$row->isSuperAdmin()) { // clear first - $row->clearACLAttributes(); + $row->clearACLAccess(); // then add $acl_attribs = $req->request->get('acl'); diff --git a/src/Controller/APIUserController.php b/src/Controller/APIUserController.php index 8dd7e2f1..06f128e9 100644 --- a/src/Controller/APIUserController.php +++ b/src/Controller/APIUserController.php @@ -14,6 +14,8 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller; use Catalyst\MenuBundle\Annotation\Menu; +use App\Entity\Rider; + class APIUserController extends Controller { /** @@ -135,6 +137,7 @@ class APIUserController extends Controller // get roles $em = $this->getDoctrine()->getManager(); $params['roles'] = $em->getRepository(APIRole::class)->findAll(); + $params['riders'] = $em->getRepository(Rider::class)->findBy([], ['first_name' => 'asc']); // response return $this->render('api-user/form.html.twig', $params); @@ -149,9 +152,21 @@ class APIUserController extends Controller $em = $this->getDoctrine()->getManager(); $obj = new APIUser(); + // metadata + $rider_id = $req->request->get('rider_id'); + $rider = $em->getRepository(Rider::class)->find($rider_id); + // TODO: check for null rider + + $meta = ['rider_id' => $rider_id]; + + // set api user in rider + $rider->setAPIUser($obj); + // set and save values $obj->setName($req->request->get('name')) ->setEnabled($req->request->get('enabled') ? true : false) + ->setMetadata($meta) + ->setRider($rider) ->clearRoles(); // set roles @@ -221,6 +236,7 @@ class APIUserController extends Controller // get roles $params['roles'] = $em->getRepository(APIRole::class)->findAll(); + $params['riders'] = $em->getRepository(Rider::class)->findBy([], ['first_name' => 'asc']); $params['obj'] = $obj; @@ -241,8 +257,21 @@ class APIUserController extends Controller throw $this->createNotFoundException('The item does not exist'); // set and save values + // metadata + $rider_id = $req->request->get('rider_id'); + $rider = $em->getRepository(Rider::class)->find($rider_id); + // TODO: check for null rider + + $meta = $obj->getMetadata(); + $meta['rider_id'] = $rider_id; + + // set api user in rider + $rider->setAPIUser($obj); + $obj->setName($req->request->get('name')) ->setEnabled($req->request->get('enabled') ? true : false) + ->setMetadata($meta) + ->setRider($rider) ->clearRoles(); // set roles diff --git a/src/Controller/CAPI/RiderAppController.php b/src/Controller/CAPI/RiderAppController.php new file mode 100644 index 00000000..20719535 --- /dev/null +++ b/src/Controller/CAPI/RiderAppController.php @@ -0,0 +1,1311 @@ +checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user to link to rider api user + $capi_user_id = $this->getUser()->getID(); + + // check if capi user already has a rider api session + $rapi_session = $em->getRepository(RiderAPISession::class)->findOneBy(['capi_user_id' => $capi_user_id]); + + if ($rapi_session != null) + return new APIResponse(false, 'User already registered'); + + // retry until we get a unique id + while (true) + { + try + { + // instantiate session + $sess = new RiderAPISession(); + $sess->setPhoneNumber($req->request->get('phone_number')) + ->setDevicePushID($req->request->get('device_push_id')) + ->setCapiUserId($capi_user_id); + + // reopen in case we get an exception + if (!$em->isOpen()) + { + $em = $em->create( + $em->getConnection(), + $em->getConfiguration() + ); + } + + // save + $em->persist($sess); + $em->flush(); + + // create redis entry for the session + $redis_client = $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 new APIResponse(true, 'Rider API user created.', $data); + } */ + + public function login(Request $req, EntityManagerInterface $em, EncoderFactoryInterface $ef, + RiderCache $rcache, RiderTracker $rider_tracker, MQTTClient $mclient, + RedisClientProvider $redis) + { + $required_params = [ + 'user', + 'pass', + ]; + + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // NOTE: we retain the username and password in rider for backward compatibility + // look for rider with username + $rider = $em->getRepository(Rider::class)->findOneBy(['username' => $req->request->get('user')]); + if ($rider == null) + return new APIResponse(false, 'Invalid username or password.'); + + // check if rider password is correct + // NOTE: we use + $encoder = $ef->getEncoder(new User()); + if (!$encoder->isPasswordValid($rider->getPassword(), $req->request->get('pass'), '')) + return new APIResponse(false, 'Invalid username or password.'); + + // user will be the one linked to the rider + $user = $rider->getAPIUser(); + // no linked user, cannot login + if ($user == null) + return new APIResponse(false, 'Rider cannot login, no associated user.'); + + // set rider to available + $rider->setAvailable(true); + + $rider_id = $rider->getID(); + // cache rider location (default to hub) + // TODO: figure out longitude / latitude default + $rcache->addActiveRider($rider_id, 0, 0); + + // send mqtt event to put rider on map + // get rider coordinates from redis + $coord = $rider_tracker->getRiderLocation($rider->getID()); + + $lng = $coord->getLongitude(); + $lat = $coord->getLatitude();; + $channel = 'rider/' . $rider->getID() . '/availability'; + $payload = [ + 'status' => 'rider_online', + 'longitude' => $lng, + 'latitude' => $lat, + ]; + $mclient->publish($channel, json_encode($payload)); + + // TODO: log rider logging in + + $em->flush(); + + // NOTE; commenting this out since this doesn't seem to be needed. + // this is being set in utils/mqtt_rider_convert/mqtt_rider_convert.py + // update redis rider.id. with the rider id + //$redis_client = $redis->getRedisClient(); + //$redis_key = 'rider.id.' . $rapi_session->getID(); + //$rider_id = $rider->getID(); + + //$redis_client->set($redis_key, $rider_id); + + $rider_id = $rider->getID(); + + $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, + 'api_key' => $user->getAPIKey(), + 'secret_key' => $user->getSecretKey(), + ]; + + return new APIResponse(true, 'Rider logged in.', $data); + } + + public function logout(Request $req, EntityManagerInterface $em, RiderCache $rcache, MQTTClient $mclient) + { + $required_params = []; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // make rider unavailable + $rider->setAvailable(false); + + // remove from cache + $rcache->removeActiveRider($rider->getID()); + + // TODO: log rider logging out + + $em->flush(); + + // send mqtt event to remove rider from map + $channel = 'rider/' . $rider->getID() . '/availability'; + $payload = [ + 'status' => 'rider_offline' + ]; + $mclient->publish($channel, json_encode($payload)); + + $data = []; + return new APIResponse(true, 'Rider logged out', $data); + } + + public function getJobOrder(Request $req, EntityManagerInterface $em) + { + // get the job order of the rider assigned to this session + $required_params = []; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // do we have a job order? + // $jo = $rider->getActiveJobOrder(); + $jo = $rider->getCurrentJobOrder(); + 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->getParameter('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(), + 'delivery_status' => $jo->getDeliveryStatus(), + ] + ]; + } + + return new APIResponse(true, 'Job order found.', $data); + + } + + public function acceptJobOrder(Request $req, EntityManagerInterface $em) + { + $required_params = ['jo_id']; + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + $msg = $this->checkJO($req, $required_params, $jo, $rider); + if (!empty($msg)) + return new APIResponse(false, $msg); + + // 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); + + // set delivery status + $jo->setDeliveryStatus(DeliveryStatus::RIDER_ACCEPT); + + // TODO: send mqtt event (?) + + // set rider's current job order + $rider->setCurrentJobOrder($jo); + + // set rider to unavailable + $rider->setAvailable(false); + + // add event log + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ACCEPT) + ->setJobOrder($jo) + ->setRider($rider); + $em->persist($event); + + $em->flush(); + + $data = []; + return new APIResponse(true, 'Job order accepted.', $data); + + } + + public function cancelJobOrder(Request $req, EntityManagerInterface $em, MQTTClient $mclient) + { + $required_params = ['jo_id']; + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + $msg = $this->checkJO($req, $required_params, $jo, $rider); + if (!empty($msg)) + return new APIResponse(false, $msg); + + // requeue it, instead of cancelling it + $jo->requeue(); + + // set delivery status to requeued + $jo->setDeliveryStatus(DeliveryStatus::REQUEUE); + + // set rider's current job order to null + $rider->setCurrentJobOrder(); + + // add event log + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::REQUEUE) + ->setJobOrder($jo) + ->setRider($rider); + $em->persist($event); + + $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(), + ]; + $mclient->sendEvent($jo, $payload); + + $data = []; + return new APIResponse(true, 'Job order requeued.', $data); + } + + public function hubDepart(Request $req, EntityManagerInterface $em) + { + $required_params = []; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // set delivery status + $jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB); + + // create time stamp event for JO event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_DEPART_HUB) + ->setJobOrder($jo) + ->setRider($rider); + + $em->persist($event); + $em->flush(); + + $data = []; + return new APIResponse(true, 'Rider leave hub.', $data); + } + + public function preHubArrive(Request $req, EntityManagerInterface $em) + { + $required_params = []; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // set delivery status + $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB_PRE_JO); + + // create time stamp event for JO event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ARRIVE_HUB_PRE_JO) + ->setJobOrder($jo) + ->setRider($rider); + + $em->persist($event); + $em->flush(); + + $data = []; + return new APIResponse(true, 'Rider arrive at hub pre jo.', $data); + } + + public function preHubDepart(Request $req, EntityManagerInterface $em) + { + $required_params = []; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // set delivery status + $jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB_PRE_JO); + + // create time stamp event for JO event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_DEPART_HUB_PRE_JO) + ->setJobOrder($jo) + ->setRider($rider); + + $em->persist($event); + $em->flush(); + + $data = []; + return new APIResponse(true, 'Rider depart from hub pre jo.', $data); + } + + public function startJobOrder(Request $req, EntityManagerInterface $em) + { + $required_params = []; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // set delivery status + $jo->setDeliveryStatus(DeliveryStatus::RIDER_START); + + // create time stamp event for JO event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_START) + ->setJobOrder($jo) + ->setRider($rider); + + $em->persist($event); + $em->flush(); + + $data = []; + return new APIResponse(true, 'Rider start job order.', $data); + + } + + public function arrive(Request $req, EntityManagerInterface $em, MQTTClient $mclient) + { + $required_params = ['jo_id']; + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + $msg = $this->checkJO($req, $required_params, $jo, $rider); + if (!empty($msg)) + return new APIResponse(false, $msg); + + // 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); + + // set delivery status + $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE); + + // add event log + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ARRIVE) + ->setJobOrder($jo) + ->setRider($rider); + $em->persist($event); + + $em->flush(); + + // send mqtt event + $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(), + ]; + $mclient->sendEvent($jo, $payload); + + $data = []; + return new APIResponse(true, 'Rider arrived at customer location.', $data); + } + + public function hubArrive(Request $req, EntityManagerInterface $em) + { + $required_params = []; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // set delivery status + $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB); + + $timestamp_event = new JOEvent(); + $timestamp_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ARRIVE_HUB) + ->setJobOrder($jo) + ->setRider($rider); + + $em->persist($timestamp_event); + + // tag rider as available + $rider->setAvailable(true); + + // set rider's current job order to null + $rider->setCurrentJobOrder(); + + $em->flush(); + + $data = []; + return new APIResponse(true, 'Rider arrive at hub.', $data); + } + + public function payment(Request $req, EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler, + RisingTideGateway $rt, WarrantyHandler $wh, MQTTClient $mclient) + { + $required_params = ['jo_id']; + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + $msg = $this->checkJO($req, $required_params, $jo, $rider); + if (!empty($msg)) + return new APIResponse(false, $msg); + + // set invoice to paid + $jo->getInvoice()->setStatus(InvoiceStatus::PAID); + + /* + // set jo status to fulfilled + $jo->setStatus(JOStatus::FULFILLED); + */ + $jo->fulfill(); + + // set delivery status + $jo->setDeliveryStatus(DeliveryStatus::RIDER_END); + + // add event log + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::FULFILL) + ->setJobOrder($jo) + ->setRider($rider); + + $timestamp_event = new JOEvent(); + $timestamp_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_END) + ->setJobOrder($jo) + ->setRider($rider); + + $em->persist($event); + $em->persist($timestamp_event); + + // NOTE: fix for the rider being assigned to other JO + // while on another JO. + // TODO: comment this out. Rider needs to be set to unavailable + // when rider accepts the JO + // tag rider as unavailable + $rider->setAvailable(false); + + // save to customer vehicle battery record + $jo_handler->updateVehicleBattery($jo); + + // send SMS to customer + $phone_number = $jo->getCustomer()->getPhoneMobile(); + if (!empty($phone_number)) + { + // TODO: put this in config file or somewhere + $message = "Your Resq job order has been completed."; + $rt->sendSMS($phone_number, 'MOTOLITE', $message); + } + + $em->flush(); + + // create warranty + if($jo_handler->checkIfNewBattery($jo)) + { + $serial = null; + $warranty_class = $jo->getWarrantyClass(); + $first_name = $jo->getCustomer()->getFirstName(); + $last_name = $jo->getCustomer()->getLastName(); + $mobile_number = $jo->getCustomer()->getPhoneMobile(); + + // check if date fulfilled is null + //if ($jo->getDateFulfill() == null) + // $date_purchase = $jo->getDateCreate(); + //else + // $date_purchase = $jo->getDateFulfill(); + + // use date_schedule for warranty expiration computation + $date_purchase = $jo->getDateSchedule(); + + $plate_number = $wh->cleanPlateNumber($jo->getCustomerVehicle()->getPlateNumber()); + + $batt_list = array(); + $invoice = $jo->getInvoice(); + if (!empty($invoice)) + { + // get battery + $invoice_items = $invoice->getItems(); + foreach ($invoice_items as $item) + { + $battery = $item->getBattery(); + if ($battery != null) + { + $batt_list[] = $item->getBattery(); + } + } + } + + // for riders, use rider id + $user_id = $rider->getID(); + $source = WarrantySource::RAPI; + $wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class, $user_id, $source, $jo->getCustomer(), $jo->getCustomerVehicle()->getVehicle()); + } + + // send mqtt event (fulfilled) + $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' => 'fulfilled', + 'jo_id' => $jo->getID(), + 'driver_image' => $image_url, + 'driver_name' => $rider->getFullName(), + 'driver_id' => $rider->getID(), + ]; + $mclient->sendEvent($jo, $payload); + + $data = []; + return new APIResponse(true, 'Job order paid and fulfilled.', $data); + } + + public function postHubArrive(Request $req, EntityManagerInterface $em) + { + $required_params = []; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // set delivery status + $jo->setDeliveryStatus(DeliveryStatus::RIDER_ARRIVE_HUB_POST_JO); + + // create time stamp event for JO event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ARRIVE_HUB_POST_JO) + ->setJobOrder($jo) + ->setRider($rider); + + $em->persist($event); + $em->flush(); + + $data = []; + return new APIResponse(true, 'Rider arrive hub post jo.', $data); + + } + + public function postHubDepart(Request $req, EntityManagerInterface $em) + { + $required_params = []; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // set delivery status + $jo->setDeliveryStatus(DeliveryStatus::RIDER_DEPART_HUB_POST_JO); + + // create time stamp event for JO event + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_DEPART_HUB_POST_JO) + ->setJobOrder($jo) + ->setRider($rider); + + $em->persist($event); + $em->flush(); + + $data = []; + return new APIResponse(true, 'Rider depart hub post jo.', $data); + } + + public function available(Request $req, EntityManagerInterface $em) + { + $required_params = []; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + $rider->setAvailable(true); + + $em->flush(); + + $data = []; + return new APIResponse(true, 'Rider available.', $data); + } + + public function getPromos(Request $req, EntityManagerInterface $em) + { + $required_params = []; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + $promos = $em->getRepository(Promo::class)->findAll(); + + $promo_data = []; + foreach ($promos as $promo) + { + $promo_data[] = [ + 'id' => $promo->getID(), + 'name' => $promo->getName(), + 'code' => $promo->getCode(), + ]; + } + + $data = [ + 'promos' => $promo_data, + ]; + + return new APIResponse(true, 'Promos found.', $data); + } + + public function getBatteries(Request $req, EntityManagerInterface $em) + { + $required_params = []; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + return new APIResponse(false, 'Missing parameter(s): ' . $params); + } + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + $batts = $em->getRepository(Battery::class)->findAll(); + $models = $em->getRepository(BatteryModel::class)->findAll(); + $sizes = $em->getRepository(BatterySize::class)->findAll(); + + $batt_data = []; + foreach ($batts as $batt) + { + $batt_data[] = [ + 'id' => $batt->getID(), + 'model_id' => $batt->getModel()->getID(), + 'size_id' => $batt->getSize()->getID(), + 'sell_price' => $batt->getSellingPrice(), + ]; + } + + $model_data = []; + foreach ($models as $model) + { + $model_data[] = [ + 'id' => $model->getID(), + 'name' => $model->getName(), + ]; + } + + $size_data = []; + foreach ($sizes as $size) + { + $size_data[] = [ + 'id' => $size->getID(), + 'name' => $size->getName(), + ]; + } + + $data = [ + 'batteries' => $batt_data, + 'models' => $model_data, + 'sizes' => $size_data, + ]; + + return new APIResponse(true, 'Batteries found.', $data); + } + + public function changeService(Request $req, EntityManagerInterface $em, InvoiceGeneratorInterface $ic) + { + $this->debugRequest($req); + + // allow rider to change service, promo, battery and trade-in options + $required_params = ['jo_id', 'stype_id', 'promo_id']; + + // get capi user + $capi_user = $this->getUser(); + if ($capi_user == null) + return new APIResponse(false, 'User not found.'); + + // get rider id from capi user metadata + $rider = $this->getRiderFromCAPI($capi_user, $em); + if ($rider == null) + return new APIResponse(false, 'No rider found.'); + + $msg = $this->checkJO($req, $required_params, $jo, $rider); + if (!empty($msg)) + return new APIResponse(false, $msg); + + // check service type + $stype_id = $req->request->get('stype_id'); + if (!ServiceType::validate($stype_id)) + { + $data = [ + 'error' => 'Invalid service type - ' . $stype_id + ]; + return $data; + } + + // check promo id + $promo_id = $req->request->get('promo_id'); + // no promo + if ($promo_id == 0) + $promo = null; + else + { + $promo = $em->getRepository(Promo::class)->find($promo_id); + if ($promo == null) + { + $data = [ + 'error' => 'Invalid promo id - ' . $promo_id + ]; + return $data; + } + } + + // check or number + $or_num = $req->request->get('or_num'); + if ($or_num != null) + $jo->setORNum($or_num); + + // coolant + $flag_coolant = $req->request->get('flag_coolant', 'false'); + if ($flag_coolant == 'true') + $jo->setHasCoolant(true); + else + $jo->setHasCoolant(false); + + // has motolite battery + $cv = $jo->getCustomerVehicle(); + $has_motolite = $req->request->get('has_motolite', 'false'); + if ($has_motolite == 'true') + $cv->setHasMotoliteBattery(true); + else + $cv->setHasMotoliteBattery(false); + $em->persist($cv); + + // check battery id + $batt_id = $req->request->get('batt_id', null); + // no battery + if ($batt_id == 0 || $batt_id == null) + $battery = null; + else + { + $battery = $em->getRepository(Battery::class)->find($batt_id); + if ($battery == null) + { + $data = [ + 'error' => 'Invalid battery id - ' . $batt_id + ]; + return $data; + } + } + + // check trade in + $trade_in = $req->request->get('trade_in'); + if (!TradeInType::validate($trade_in)) + $trade_in = null; + + // check mode of payment + $mode = $req->request->get('mode_of_payment'); + if (!ModeOfPayment::validate($mode)) + $mode = ModeOfPayment::CASH; + $jo->setModeOfPayment($mode); + + // generate new invoice + $crit = new InvoiceCriteria(); + $crit->setServiceType($stype_id); + $crit->setCustomerVehicle($cv); + $crit->setHasCoolant($jo->hasCoolant()); + + if ($promo != null) + $crit->addPromo($promo); + + if ($battery != null) + { + $crit->addEntry($battery, $trade_in, 1); + error_log('adding entry for battery - ' . $battery->getID()); + } + + $invoice = $ic->generateInvoice($crit); + + // remove previous invoice + $old_invoice = $jo->getInvoice(); + $em->remove($old_invoice); + $em->flush(); + + // save job order + $jo->setServiceType($stype_id); + + // save invoice + $jo->setInvoice($invoice); + $em->persist($invoice); + + // add event log + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_EDIT) + ->setJobOrder($jo) + ->setRider($rider); + $em->persist($event); + + $em->flush(); + // TODO: send mqtt event (?) + + $data = []; + return new APIResponse(true, 'Job order service changed.', $data); + } + + protected function getCAPIUser($id, EntityManagerInterface $em) + { + $capi_user = $em->getRepository(APIUser::class)->find($id); + return $capi_user; + } + + protected function getRiderFromCAPI($capi_user, $em) + { + /* + //get rider id from metadata + $metadata = $capi_user->getMetadata(); + $rider_id = $metadata['rider_id']; + + // get rider + $rider = $em->getRepository(Rider::class)->find($rider_id); + + return $rider; + */ + + return $capi_user->getRider(); + } + + 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 checkJO(Request $req, $required_params, &$jo = null, $rider) + { + // set jo status to in transit + $msg = ''; + $missing = $this->checkMissingParameters($req, $required_params); + if (count($missing) > 0) + { + $params = implode(', ', $missing); + $msg = 'Missing parameter(s): ' . $params; + return $msg; + } + + // check if we have an active JO + // $jo = $rider->getActiveJobOrder(); + $jo = $rider->getCurrentJobOrder(); + if ($jo == null) + { + $msg = 'No active job order.'; + return $msg; + } + + // check if the jo_id sent is the same as our active jo + if ($req->request->get('jo_id') != $jo->getID()) + { + $msg = 'Job order selected is not active job order.'; + return $msg; + } + + return $msg; + } + + protected function debugRequest(Request $req) + { + $all = $req->request->all(); + error_log(print_r($all, true)); + } +} diff --git a/src/Controller/RAPIController.php b/src/Controller/RAPIController.php index 3b8bdab1..a818e9a7 100644 --- a/src/Controller/RAPIController.php +++ b/src/Controller/RAPIController.php @@ -300,4 +300,136 @@ class RAPIController extends Controller // response return $res->getReturnResponse(); } + + public function hubDepart(Request $req, RiderAPIHandlerInterface $rapi_handler) + { + $res = new APIResult(); + + $data = $rapi_handler->hubDepart($req); + + if (isset($data['error'])) + { + $message = $data['error']; + + $res->setError(true) + ->setErrorMessage($message); + } + else + { + $res->setData($data); + } + + // response + return $res->getReturnResponse(); + } + + public function preHubArrive(Request $req, RiderAPIHandlerInterface $rapi_handler) + { + $res = new APIResult(); + + $data = $rapi_handler->preHubArrive($req); + + if (isset($data['error'])) + { + $message = $data['error']; + + $res->setError(true) + ->setErrorMessage($message); + } + else + { + $res->setData($data); + } + + // response + return $res->getReturnResponse(); + } + + public function preHubDepart(Request $req, RiderAPIHandlerInterface $rapi_handler) + { + $res = new APIResult(); + + $data = $rapi_handler->preHubDepart($req); + + if (isset($data['error'])) + { + $message = $data['error']; + + $res->setError(true) + ->setErrorMessage($message); + } + else + { + $res->setData($data); + } + + // response + return $res->getReturnResponse(); + } + + public function postHubArrive(Request $req, RiderAPIHandlerInterface $rapi_handler) + { + $res = new APIResult(); + + $data = $rapi_handler->postHubArrive($req); + + if (isset($data['error'])) + { + $message = $data['error']; + + $res->setError(true) + ->setErrorMessage($message); + } + else + { + $res->setData($data); + } + + // response + return $res->getReturnResponse(); + } + + public function postHubDepart(Request $req, RiderAPIHandlerInterface $rapi_handler) + { + $res = new APIResult(); + + $data = $rapi_handler->postHubDepart($req); + + if (isset($data['error'])) + { + $message = $data['error']; + + $res->setError(true) + ->setErrorMessage($message); + } + else + { + $res->setData($data); + } + + // response + return $res->getReturnResponse(); + } + + public function startJobOrder(Request $req, RiderAPIHandlerInterface $rapi_handler) + { + $res = new APIResult(); + + $data = $rapi_handler->startJobOrder($req); + + if (isset($data['error'])) + { + $message = $data['error']; + + $res->setError(true) + ->setErrorMessage($message); + } + else + { + $res->setData($data); + } + + // response + return $res->getReturnResponse(); + } } diff --git a/src/Entity/JobOrder.php b/src/Entity/JobOrder.php index 9fdc2424..fcbe5ddc 100644 --- a/src/Entity/JobOrder.php +++ b/src/Entity/JobOrder.php @@ -359,6 +359,12 @@ class JobOrder */ protected $no_trade_in_reason; + // delivery status of the job order + /** + * @ORM\Column(type="string", length=30, nullable=true) + */ + protected $delivery_status; + public function __construct() { $this->date_create = new DateTime(); @@ -1032,4 +1038,16 @@ class JobOrder { return $this->no_trade_in_reason; } + + public function setDeliveryStatus($delivery_status) + { + $this->delivery_status = $delivery_status; + return $this; + } + + public function getDeliveryStatus() + { + return $this->delivery_status; + } + } diff --git a/src/Entity/Rider.php b/src/Entity/Rider.php index 502d93bc..34f49455 100644 --- a/src/Entity/Rider.php +++ b/src/Entity/Rider.php @@ -9,6 +9,7 @@ use Doctrine\Common\Collections\ArrayCollection; use Doctrine\Common\Collections\Criteria; use App\Ramcar\JOStatus; +use Catalyst\APIBundle\Entity\User as APIUser; /** * @ORM\Entity @@ -120,6 +121,21 @@ class Rider */ protected $sessions; + // current job order + // NOTE: for forgotten reasons, we don't set active_job_order. + // but we now need rider's current JO for the time stamp events + /** + * @ORM\OneToOne(targetEntity="JobOrder") + * @ORM\JoinColumn(name="current_jo_id", referencedColumnName="id") + */ + protected $current_job_order; + + /** + * @ORM\OneToOne(targetEntity="Catalyst\APIBundle\Entity\User", inversedBy="rider") + * @ORM\JoinColumn(name="api_user_id", referencedColumnName="id", nullable=true) + */ + protected $api_user; + public function __construct() { $this->job_orders = new ArrayCollection(); @@ -132,6 +148,8 @@ class Rider $this->password = ''; $this->active_job_order = null; + $this->current_job_order = null; + $this->api_user = null; } public function getID() @@ -371,4 +389,26 @@ class Rider $map_label = $this->first_name .' ' . $this->last_name; return $map_label; } + + public function setCurrentJobOrder(JobOrder $jo = null) + { + $this->current_job_order = $jo; + return $this; + } + + public function getCurrentJobOrder() + { + return $this->current_job_order; + } + + public function setAPIUser($user) + { + $this->api_user = $user; + return $this; + } + + public function getAPIUser() + { + return $this->api_user; + } } diff --git a/src/Entity/RiderAPISession.php b/src/Entity/RiderAPISession.php new file mode 100644 index 00000000..98228268 --- /dev/null +++ b/src/Entity/RiderAPISession.php @@ -0,0 +1,134 @@ +id = $this->generateKeyID(); + $this->rider = null; + $this->is_active = true; + $this->capi_user_id = 0; + } + + public function generateKeyID() + { + // use uniqid for now, since primary key dupes will trigger exceptions + return uniqid(); + } + + public function getID() + { + return $this->id; + } + + public function setDevicePushID($id) + { + $this->device_push_id = $id; + return $this; + } + + public function getDevicePushID() + { + return $this->device_push_id; + } + + public function setRider(Rider $rider = null) + { + $this->rider = $rider; + return $this; + } + + public function getRider() + { + return $this->rider; + } + + public function setPhoneNumber($num) + { + $this->phone_number = $num; + return $this; + } + + public function getPhoneNumber() + { + return $this->phone_number; + } + + public function setActive($flag = true) + { + $this->is_active = $flag; + return $this; + } + + public function isActive() + { + return $this->is_active; + } + + public function hasRider() + { + if ($this->rider == null) + return false; + + return true; + } + + public function setCapiUserId($capi_user_id) + { + $this->capi_user_id = $capi_user_id; + } + + public function getCapiUserId() + { + return $this->capi_user_id; + } +} + diff --git a/src/Ramcar/DeliveryStatus.php b/src/Ramcar/DeliveryStatus.php new file mode 100644 index 00000000..b4d06871 --- /dev/null +++ b/src/Ramcar/DeliveryStatus.php @@ -0,0 +1,41 @@ + 'Assigned Rider', + 'requeue' => 'Requeue', + 'accept' => 'Rider Accept', + 'arrive' => 'Rider Arrive', + 'rider_edit' => 'Rider Edit', + 'rider_depart_hub' => 'Rider Depart Hub', + 'rider_arrive_hub_pre_jo' => 'Rider Arrive Hub Pre JO', + 'rider_depart_hub_pre_jo' => 'Rider Depart Hub Pre JO', + 'rider_start' => 'Rider Start', + 'rider_end' => 'Rider End', + 'rider_arrive_hub_post_jo' => 'Rider Arrive Hub Post JO', + 'rider_depart_hub_post_jo' => 'Rider Depart Hub Post JO', + 'rider_arrive_hub' => 'Rider Arrive Hub', + 'cancelled' => 'Cancelled', + 'fulfilled' => 'Fulfilled', + ]; +} + diff --git a/src/Ramcar/JOEventType.php b/src/Ramcar/JOEventType.php index 3929c24e..3413f660 100644 --- a/src/Ramcar/JOEventType.php +++ b/src/Ramcar/JOEventType.php @@ -4,16 +4,24 @@ namespace App\Ramcar; class JOEventType extends NameValue { - const CREATE = 'create'; - const HUB_ASSIGN = 'hub_assign'; - const RIDER_ASSIGN = 'rider_assign'; - const CANCEL = 'cancel'; - const FULFILL = 'fulfill'; - const OPEN_EDIT = 'open_edit'; - const REQUEUE = 'requeue'; - const RIDER_ACCEPT = 'accept'; - const RIDER_ARRIVE = 'arrive'; - const RIDER_EDIT = 'rider_edit'; + const CREATE = 'create'; + const HUB_ASSIGN = 'hub_assign'; + const RIDER_ASSIGN = 'rider_assign'; + const CANCEL = 'cancel'; + const FULFILL = 'fulfill'; + const OPEN_EDIT = 'open_edit'; + const REQUEUE = 'requeue'; + const RIDER_ACCEPT = 'accept'; + const RIDER_ARRIVE = 'arrive'; + const RIDER_EDIT = 'rider_edit'; + const RIDER_DEPART_HUB = 'rider_depart_hub'; + const RIDER_ARRIVE_HUB_PRE_JO = 'rider_arrive_hub_pre_jo'; + const RIDER_DEPART_HUB_PRE_JO = 'rider_depart_hub_pre_jo'; + const RIDER_START = 'rider_start'; + const RIDER_END = 'rider_end'; + const RIDER_ARRIVE_HUB_POST_JO = 'rider_arrive_hub_post_jo'; + const RIDER_DEPART_HUB_POST_JO = 'rider_depart_hub_post_jo'; + const RIDER_ARRIVE_HUB = 'rider_arrive_hub'; const COLLECTION = [ 'create' => 'Created', @@ -26,5 +34,13 @@ class JOEventType extends NameValue 'accept' => 'Rider Accept', 'arrive' => 'Rider Arrive', 'rider_edit' => 'Rider Edit', + 'rider_depart_hub' => 'Rider Depart Hub', + 'rider_arrive_hub_pre_jo' => 'Rider Arrive Hub Pre JO', + 'rider_depart_hub_pre_jo' => 'Rider Depart Hub Pre JO', + 'rider_start' => 'Rider Start', + 'rider_end' => 'Rider End', + 'rider_arrive_hub_post_jo' => 'Rider Arrive Hub Post JO', + 'rider_depart_hub_post_jo' => 'Rider Depart Hub Post JO', + 'rider_arrive_hub' => 'Rider Arrive Hub', ]; } diff --git a/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php b/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php index 47d8728e..d3e3e4cc 100644 --- a/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php +++ b/src/Service/InvoiceGenerator/ResqInvoiceGenerator.php @@ -123,7 +123,9 @@ class ResqInvoiceGenerator implements InvoiceGeneratorInterface // get current user $user = $this->security->getUser(); - if ($user != null) + // check if user is User or APIUser + //if ($user != null) + if ($user instanceof User) { $invoice->setCreatedBy($user); } diff --git a/src/Service/JobOrderHandler/ResqJobOrderHandler.php b/src/Service/JobOrderHandler/ResqJobOrderHandler.php index 82261c4d..4a98b490 100644 --- a/src/Service/JobOrderHandler/ResqJobOrderHandler.php +++ b/src/Service/JobOrderHandler/ResqJobOrderHandler.php @@ -43,6 +43,7 @@ use App\Ramcar\NoTradeInReason; use App\Ramcar\WillingToWaitContent; use App\Ramcar\WarrantySource; use App\Ramcar\HubCriteria; +use App\Ramcar\DeliveryStatus; use App\Service\InvoiceGeneratorInterface; use App\Service\JobOrderHandlerInterface; @@ -932,7 +933,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ->setLandmark($req->request->get('landmark')) ->setWillWait($req->request->get('flag_willing_to_wait')) ->setReasonNotWait($reason) - ->setNotWaitingNotes($more_reason); + ->setNotWaitingNotes($more_reason) + ->setDeliveryStatus(DeliveryStatus::RIDER_ASSIGN); if ($user != null) { @@ -951,7 +953,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface if (empty($error_array)) { // set rider unavailable - $rider->setAvailable(false); + // $rider->setAvailable(false); + + // set rider's current job order + $rider->setCurrentJobOrder($obj); // the event $event = new JOEvent(); @@ -1028,7 +1033,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ->setLandmark($req->request->get('landmark')) ->setWillWait($req->request->get('flag_willing_to_wait')) ->setReasonNotWait($reason) - ->setNotWaitingNotes($more_reason); + ->setNotWaitingNotes($more_reason) + ->setDeliveryStatus(DeliveryStatus::FULFILLED); // validate $errors = $this->validator->validate($obj); @@ -1142,7 +1148,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface throw new NotFoundHttpException('The item does not exist'); $cancel_reason = $req->request->get('cancel_reason'); - error_log($cancel_reason); + //error_log('cancel_reason ' . $cancel_reason); + + $obj->setDeliveryStatus(DeliveryStatus::CANCELLED); + $obj->cancel($cancel_reason); // the event @@ -1464,6 +1473,13 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ]; $mclient->sendRiderEvent($obj, $rider_payload); + // need to unset first rider's current job order + $old_rider = $obj->getRider(); + $old_rider->setCurrentJobOrder(); + + // set available flag for first rider + $old_rider->setAvailable(true); + // coordinates $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); @@ -1484,7 +1500,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ->setLandmark($req->request->get('landmark')) ->setWillWait($req->request->get('flag_willing_to_wait')) ->setReasonNotWait($reason) - ->setNotWaitingNotes($more_reason); + ->setNotWaitingNotes($more_reason) + ->setDeliveryStatus(DeliveryStatus::RIDER_ASSIGN); if ($user != null) { @@ -1505,6 +1522,12 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface // check if any errors were found if (empty($error_array)) { + // set new rider's current job order + $rider->setCurrentJobOrder($obj); + + // set new rider's availability to false + $rider->setAvailable(false); + // add event $event = new JOEvent(); $event->setDateHappen(new DateTime()) diff --git a/src/Service/MQTTClient.php b/src/Service/MQTTClient.php index c9c7a4f2..13c884a0 100644 --- a/src/Service/MQTTClient.php +++ b/src/Service/MQTTClient.php @@ -72,6 +72,8 @@ class MQTTClient if ($rider == null) return; + /* + // NOTE: this is for the old rider app // check if rider has sessions $sessions = $rider->getSessions(); if (count($sessions) == 0) @@ -84,5 +86,9 @@ class MQTTClient $channel = self::RIDER_PREFIX . $sess_id; $this->publish($channel, json_encode($payload)); } + */ + + // NOTE: this is for the new rider app + $this->publish('rider/' . $rider->getID(), json_encode($payload)); } } diff --git a/src/Service/RiderAPIHandler/ResqRiderAPIHandler.php b/src/Service/RiderAPIHandler/ResqRiderAPIHandler.php index 10678139..db83076b 100644 --- a/src/Service/RiderAPIHandler/ResqRiderAPIHandler.php +++ b/src/Service/RiderAPIHandler/ResqRiderAPIHandler.php @@ -33,6 +33,7 @@ use App\Entity\Promo; use App\Entity\Battery; use App\Entity\BatteryModel; use App\Entity\BatterySize; +use App\Entity\JobOrder; use DateTime; @@ -408,8 +409,15 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface // TODO: send mqtt event (?) - // add event log $rider = $this->session->getRider(); + + // set rider's current job order + $rider->setCurrentJobOrder($jo); + + // set rider to unavailable + $rider->setAvailable(false); + + // add event log $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::RIDER_ACCEPT) @@ -433,8 +441,11 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface // requeue it, instead of cancelling it $jo->requeue(); - // add event log $rider = $this->session->getRider(); + // set rider's current job order to null + $rider->setCurrentJobOrder(); + + // add event log $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::REQUEUE) @@ -512,10 +523,26 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface return $data; } + $rider = $this->session->getRider(); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + $timestamp_event = new JOEvent(); + $timestamp_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ARRIVE_HUB) + ->setJobOrder($jo) + ->setRider($rider); + + $this->em->persist($timestamp_event); + // tag rider as available $rider = $this->session->getRider(); $rider->setAvailable(true); + // set rider's current job order to null + $rider->setCurrentJobOrder(); + $this->em->flush(); return $data; @@ -544,8 +571,20 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface ->setTypeID(JOEventType::FULFILL) ->setJobOrder($jo) ->setRider($rider); - $this->em->persist($event); + $timestamp_event = new JOEvent(); + $timestamp_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_END) + ->setJobOrder($jo) + ->setRider($rider); + + $this->em->persist($event); + $this->em->persist($timestamp_event); + + // NOTE: fix for the rider being assigned to other JO + // while on another JO. + // TODO: comment this out. Rider needs to be set to unavailable + // when rider accepts the JO // tag rider as unavailable $rider->setAvailable(false); @@ -845,6 +884,162 @@ class ResqRiderAPIHandler implements RiderAPIHandlerInterface return $data; } + public function hubDepart(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + $rider = $this->session->getRider(); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // create time stamp event for JO event + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_DEPART_HUB) + ->setJobOrder($jo) + ->setRider($rider); + + $this->em->persist($event); + $this->em->flush(); + + return $data; + } + + public function preHubArrive(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + $rider = $this->session->getRider(); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // create time stamp event for JO event + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ARRIVE_HUB_PRE_JO) + ->setJobOrder($jo) + ->setRider($rider); + + $this->em->persist($event); + $this->em->flush(); + + return $data; + } + + public function preHubDepart(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + $rider = $this->session->getRider(); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // create time stamp event for JO event + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_DEPART_HUB_PRE_JO) + ->setJobOrder($jo) + ->setRider($rider); + + $this->em->persist($event); + $this->em->flush(); + + return $data; + } + + public function startJobOrder(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + $rider = $this->session->getRider(); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // create time stamp event for JO event + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_START) + ->setJobOrder($jo) + ->setRider($rider); + + $this->em->persist($event); + $this->em->flush(); + + return $data; + } + + public function postHubArrive(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + $rider = $this->session->getRider(); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // create time stamp event for JO event + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ARRIVE_HUB_POST_JO) + ->setJobOrder($jo) + ->setRider($rider); + + $this->em->persist($event); + $this->em->flush(); + + return $data; + } + + public function postHubDepart(Request $req) + { + $required_params = []; + $data = $this->checkParamsAndKey($req, $required_params); + if (isset($data['error'])) + return $data; + + $rider = $this->session->getRider(); + + // get rider's current job order + $jo = $rider->getCurrentJobOrder(); + + // create time stamp event for JO event + $rider = $this->session->getRider(); + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_DEPART_HUB_POST_JO) + ->setJobOrder($jo) + ->setRider($rider); + + $this->em->persist($event); + $this->em->flush(); + + return $data; + } + protected function checkMissingParameters(Request $req, $params = []) { $missing = []; diff --git a/templates/api-user/form.html.twig b/templates/api-user/form.html.twig index 57c3c313..436f5f7b 100644 --- a/templates/api-user/form.html.twig +++ b/templates/api-user/form.html.twig @@ -45,6 +45,20 @@ +
+
+ + + +
+