em = $em; $this->redis = $redis; $this->ef = $ef; $this->rcache = $rcache; $this->country_code = $country_code; $this->mclient = $mclient; $this->wh = $wh; $this->jo_handler = $jo_handler; $this->ic = $ic; $this->upload_dir = $upload_dir; // 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 = [ 'title' => 'Failed Registration', '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'])) { $data['title'] = 'Failed Login'; return $data; } // check if session has a rider already if ($this->session->hasRider()) { $data = [ 'title' => 'Failed Login', '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 = [ 'title' => 'Failed Login', '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 = [ 'title' => 'Failed Login', 'error' => 'Invalid username or password.' ]; return $data; } // assign rider to session $this->session->setRider($rider); //$rider->setAvailable(true); $rider->setActive(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'])) { $data['title'] = 'Failed Logout'; return $data; } // make rider unavailable $rider = $this->session->getRider(); $rider->setAvailable(false); $rider->setActive(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 goOnline(Request $req) { $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) { $data['title'] = 'Failed Go Online'; return $data; } // set rider to available to take on JOs $rider = $this->session->getRider(); $rider->setAvailable(true); $this->em->flush(); return $data; } public function goOffline(Request $req) { $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) { $data['title'] = 'Failed Go Offline'; return $data; } // set rider to unavailable to take on JOs $rider = $this->session->getRider(); $rider->setAvailable(false); $this->em->flush(); return $data; } public function getJobOrderHistory(Request $req, $period) { $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) { $data['title'] = 'Failed Get Job Order History'; return $data; } // are we logged in? if (!$this->session->hasRider()) { $data = [ 'title' => 'Failed Get Job Order History', 'error' => 'No logged in rider.' ]; return $data; } $rider = $this->session->getRider(); // get JOs assigned to rider for the month given // setup start date and end dates // get current year $current_date = new DateTime(); if ($period == 'lastmonth') { $date_interval = new DateInterval('P1M'); $period_date = $current_date->sub($date_interval); } else $period_date = $current_date; $period_year = $period_date->format('Y'); $period_month = $period_date->format('m'); // get number of days in month requested $last_day = cal_days_in_month(CAL_GREGORIAN, $period_month, $period_year); $s_date = $period_year . '-' . $period_month . '-01 00:00:00'; $e_date = $period_year . '-' . $period_month . '-' . $last_day . ' 23:59:59'; $qb = $this->em->getRepository(JobOrder::class)->createQueryBuilder('j'); $query = $qb->innerJoin('j.rider', 'r') ->where('j.date_schedule >= :start') ->andWhere('j.date_schedule <= :end') ->andWhere('r.id = :rider_id') ->setParameter('start', $s_date) ->setParameter('end', $e_date) ->setParameter('rider_id', $rider->getID()) ->getQuery(); $jo_results = $query->getResult(); $jo_data = []; if (!(empty($jo_results))) { foreach ($jo_results as $jo) { // TODO: refactor this to call formatJobOrderData $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) { // if more than 1, split it into one of each of the same item $item_qty = $item->getQuantity(); for ($i = 0; $i < $item_qty; $i++) { $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' => 1, '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'; $jo_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(), 'phone_landline' => $this->country_code . $cust->getPhoneLandline(), ], '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(), 'mode_of_payment_display' => CMBModeOfPayment::getName($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(), ] ]; } } $data = [ 'jo_history' => $jo_data, ]; return $data; } public function getAssignedJobOrders(Request $req) { $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) { $data['title'] = 'Failed Get Assigned Job Orders'; return $data; } // are we logged in? if (!$this->session->hasRider()) { $data = [ 'title' => 'Failed Get Assigned Job Orders', 'error' => 'No logged in rider.' ]; return $data; } $rider = $this->session->getRider(); $qb = $this->em->getRepository(JobOrder::class)->createQueryBuilder('j'); $query = $qb->innerJoin('j.rider', 'r') ->andWhere('r.id = :rider_id') ->andWhere('j.status = :status') ->setParameter('rider_id', $rider->getID()) ->setParameter('status', JOStatus::ASSIGNED) ->getQuery(); $jo_results = $query->getResult(); $jo_data = []; if (!(empty($jo_results))) { foreach ($jo_results as $jo) { // TODO: refactor this to call formatJobOrderData $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) { // if more than 1, split it into one of each of the same item $item_qty = $item->getQuantity(); for ($i = 0; $i < $item_qty; $i++) { $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' => 1, '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'; $jo_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(), 'phone_landline' => $this->country_code . $cust->getPhoneLandline(), ], '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(), 'mode_of_payment_display' => CMBModeOfPayment::getName($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(), ] ]; } } $data = [ 'assigned_jos' => $jo_data, ]; return $data; } protected function formatJobOrderData($req, $jo) { $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) { // if more than 1, split it into one of each of the same item $item_qty = $item->getQuantity(); for ($i = 0; $i < $item_qty; $i++) { $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' => 1, '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'; // get date of status last change if ($jo->getDateStatusChange() == null) $date_status_change = null; else $date_status_change = $jo->getDateStatusChange()->format('Ymd H:i:s'); // get odometer reading $odo = $jo->getMeta('odometer'); if ($odo <= 0) $odo = 0; $after_images = [ 'speedometer' => null, 'plate_number' => null, 'battery' => null, 'others' => [], ]; $jo_extra = $jo->getJOExtra(); if ($jo_extra != null) { // after images $after_images['speedometer'] = $this->getURLExtraImage($req, $jo_extra->getAfterSpeedImageFilename()); $after_images['plate_number'] = $this->getURLExtraImage($req, $jo_extra->getAfterPlateNumImageFilename()); $after_images['battery'] = $this->getURLExtraImage($req, $jo_extra->getAfterBattImageFilename()); // other images $other_images = []; foreach ($jo_extra->getAfterOtherImages() as $others) { $other_images[] = $this->getURLExtraImage($req, $others); } $after_images['others'] = $other_images; } // customer email if ($jo->getMeta('customer_email') == null) $cust_email = ''; else $cust_email = $jo->getMeta('customer_email'); $data = [ '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(), 'date_status_change' => $date_status_change, 'customer' => [ 'title' => $cust->getTitle(), 'first_name' => $cust->getFirstName(), 'last_name' => $cust->getLastName(), 'phone_mobile' => $this->country_code . $cust->getPhoneMobile(), 'phone_landline' => $this->country_code . $cust->getPhoneLandline(), ], '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(), 'mode_of_payment_display' => CMBModeOfPayment::getName($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(), // BEGIN: cmb specific details // odometer 'odometer' => $odo, // images 'finish_photos' => $after_images, // customer email 'customer_email' => $cust_email, // END: cmb speicifc details ]; return $data; } public function getJobOrder(Request $req) { $required_params = [ 'jo_id' ]; $data = $this->checkJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Get Job Order'; return $data; } $jo_data = $this->formatJobOrderData($req, $jo); $data = [ 'job_order' => $jo_data ]; return $data; } public function acceptJobOrder(Request $req) { $required_params = [ 'jo_id' ]; $data = $this->checkJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Accept Job Order'; return $data; } // TODO: put JO in job queue // TODO: refactor this into a jo handler class, so we don't have to repeat for control center // TODO: send mqtt event (?) // add event log $rider = $this->session->getRider(); $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(CMBJOEventType::RIDER_ACCEPT) ->setJobOrder($jo) ->setRider($rider); $this->em->persist($event); $this->em->flush(); return $data; } public function setJobOrderInTransit(Request $req) { $required_params = ['jo_id']; $data = $this->checkActiveJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Set Job Order in Transit'; return $data; } // set jo status to in transit $jo->setStatus(JOStatus::IN_TRANSIT); // set rider's active JO // TODO: send mqtt event (?) // add event log $rider = $this->session->getRider(); $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(CMBJOEventType::RIDER_IN_TRANSIT) ->setJobOrder($jo) ->setRider($rider); $this->em->persist($event); $this->em->flush(); return $data; } public function cancelJobOrder(Request $req) { $required_params = [ 'jo_id', 'cancel_reason' ]; $data = $this->checkJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Job Order Cancellation'; return $data; } $cancel_reason = $req->request->get('cancel_reason'); $jo->cancel($cancel_reason); // add event log $rider = $this->session->getRider(); $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(CMBJOEventType::REQUEUE) ->setJobOrder($jo) ->setRider($rider); $this->em->persist($event); $this->em->flush(); return $data; } public function performJobOrder(Request $req) { $required_params = [ 'jo_id', ]; $data = $this->checkJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Job Order Perform'; return $data; } $jo->perform(); // add event log $rider = $this->session->getRider(); $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(CMBJOEventType::PERFORM) ->setJobOrder($jo) ->setRider($rider); $this->em->persist($event); $this->em->flush(); return $data; } public function rejectJobOrder(Request $req) { $required_params = [ 'jo_id' ]; $data = $this->checkJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Job Order Rejection'; return $data; } $jo->requeue(); // TODO: do we leave JO as assigned to rider? // add event log $rider = $this->session->getRider(); $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(CMBJOEventType::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->checkActiveJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Arrive'; 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(CMBJOEventType::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; } // tag rider as available $rider = $this->session->getRider(); $rider->setAvailable(true); $this->em->flush(); return $data; } public function payment(Request $req) { $required_params = [ 'jo_id', 'mode_of_payment', ]; $data = $this->checkActiveJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Payment'; return $data; } // add mode of payment $mode = $req->request->get('mode_of_payment'); $jo->setModeOfPayment($mode); // set status to paid $jo->setStatus(JOStatus::PAID); // set invoice to paid $jo->getInvoice()->setStatus(InvoiceStatus::PAID); // add event log $rider = $this->session->getRider(); $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(CMBJOEventType::PAID) ->setJobOrder($jo) ->setRider($rider); $this->em->persist($event); $this->em->flush(); return $data; } public function available(Request $req) { $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) { $data['title'] = 'Failed Available Rider'; return $data; } // make rider available $this->session->getRider()->setAvailable(true); // TODO: log rider available $this->em->flush(); return $data; } public function getPromos(Request $req) { $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) { $data['title'] = 'Failed Get Promos'; return $data; } $promos = $this->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 $data; } public function getBatteries(Request $req) { // get batteries, models, and sizes $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) { $data['title'] = 'Failed Get Batteries'; return $data; } $batts = $this->em->getRepository(Battery::class)->findAll(); $models = $this->em->getRepository(BatteryModel::class)->findAll(); $sizes = $this->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 $data; } public function changeService(Request $req) { $this->debugRequest($req); // allow rider to change service, promo, battery and trade-in options $required_params = ['jo_id', 'service_type', 'promo_id']; $data = $this->checkJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Service Change'; return $data; } // check service type $service_type = $req->request->get('service_type'); if (!CMBServiceType::validate($service_type)) { $data = [ 'title' => 'Failed Service Change', 'error' => 'Invalid service type - ' . $service_type ]; return $data; } // check promo id $promo_id = $req->request->get('promo_id'); // no promo if ($promo_id == 0) $promo = null; else { $promo = $this->em->getRepository(Promo::class)->find($promo_id); if ($promo == null) { $data = [ 'title' => 'Failed Service Change', '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); $this->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 = $this->em->getRepository(Battery::class)->find($batt_id); if ($battery == null) { $data = [ 'title' => 'Failed Service Change', '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 // TODO: defaulting to credit_card for now if mode of payment is invalid $mode = $req->request->get('mode_of_payment'); if (!CMBModeOfPayment::validate($mode)) $mode = CMBModeOfPayment::CASH; $jo->setModeOfPayment($mode); // generate new invoice $crit = new InvoiceCriteria(); $crit->setServiceType($service_type); $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 = $this->ic->generateInvoice($crit); // remove previous invoice $old_invoice = $jo->getInvoice(); $this->em->remove($old_invoice); $this->em->flush(); // save job order $jo->setServiceType($service_type); // save invoice $jo->setInvoice($invoice); $this->em->persist($invoice); // add event log $rider = $this->session->getRider(); $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(CMBJOEventType::RIDER_EDIT) ->setJobOrder($jo) ->setRider($rider); $this->em->persist($event); $this->em->flush(); // TODO: send mqtt event (?) return $data; } public function generateInvoice(Request $req) { $required_params = ['jo_id']; $data = $this->checkActiveJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Invoice Generation'; return $data; } $inv = $jo->getInvoice(); $promo = $inv->getPromo(); // invoice items $inv_items = []; foreach ($inv->getItems() as $item) { // if more than 1, split it into one of each of the same item $item_qty = $item->getQuantity(); for ($i = 0; $i < $item_qty; $i++) { $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' => 1, '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 = [ 'invoice' => [ 'id' => $inv->getID(), 'discount' => $inv->getDiscount(), 'trade_in' => $inv->getTradeIn(), 'total_price' => $inv->getTotalPrice(), 'vat' => $inv->getVat(), 'items' => $inv_items, 'trade_in_type' => $trade_in_type, 'promo' => $promo_data, ] ]; return $data; } public function startJobOrder(Request $req) { $required_params = ['jo_id']; $data = $this->checkActiveJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Job Order Start'; return $data; } // 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(CMBJOEventType::RIDER_START) ->setJobOrder($jo) ->setRider($rider); $this->em->persist($event); $this->em->flush(); return $data; } public function completeJobOrder(Request $req) { $required_params = ['jo_id']; $data = $this->checkActiveJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Job Order Completion'; return $data; } // set customer signature $jo_extra = $jo->getJOExtra(); $sig_file = $this->handleFileUpload($req, 'signature'); if ($sig_file) $jo_extra->setCustomerSignature($sig_file); // set email $cust_email = $req->request->get('customer_email', ''); $jo->addMeta('customer_email', $cust_email); /* // set jo status to fulfilled $jo->setStatus(JOStatus::FULFILLED); */ //$jo->fulfill(); $jo->setStatus(JOStatus::FULFILLED) ->setDateFulfill(new DateTime()); // add event log $rider = $this->session->getRider(); $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(CMBJOEventType::FULFILL) ->setJobOrder($jo) ->setRider($rider); $this->em->persist($event); // save to customer vehicle battery record $this->jo_handler->updateVehicleBattery($jo); $this->em->flush(); // create warranty if($this->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 = $this->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(); } } } $this->wh->createWarranty($serial, $plate_number, $first_name, $last_name, $mobile_number, $batt_list, $date_purchase, $warranty_class); } // TODO: Need to verify if needed. // 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(), ]; $this->mclient->sendEvent($jo, $payload); return $data; } public function setActiveJobOrder(Request $req) { $required_params = [ 'jo_id' ]; $data = $this->checkJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Set Active Job Order'; return $data; } $rider = $this->session->getRider(); $rider->setActiveJobOrder($jo); $this->em->persist($rider); $this->em->flush(); return $data; } public function setOdometer(Request $req) { $required_params = [ 'jo_id', 'odometer' ]; $data = $this->checkActiveJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Set Odometer'; return $data; } $odometer_reading = $req->request->get('odometer'); if ($odometer_reading > 999999) { $data = [ 'title' => 'Failed Set Odometer', 'error' => 'Odometer cannot be more than 6 figures.', ]; return $data; } $jo->addMeta('odometer', $odometer_reading); $this->em->flush(); return $data; } protected function handleFileUpload($req, $name) { $file = $req->files->get($name); if (empty($file)) return false; $orig_filename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); // generate prefix to assure uniqueness $prefix = uniqid() . '_'; $new_filename = $prefix . $orig_filename . '.' . $file->guessClientExtension(); // move to our upload dir $dest = $this->upload_dir; try { $file->move($dest, $new_filename); } catch (FileException $e) { return false; } return $new_filename; } public function uploadFinishPhotos(Request $req) { $required_params = [ 'jo_id', ]; $data = $this->checkActiveJO($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Upload Finish Photos'; return $data; } $dest = $this->upload_dir; $speed_img_file = $req->files->get('speedometer_img'); $batt_img_file = $req->files->get('battery_img'); $plate_num_img_file = $req->files->get('plate_number_img'); $other_img_files[]= $req->files->get('other_images'); if ((empty($speed_img_file)) && (empty($batt_img_file)) && (empty($plate_num_img_file))) { $data = [ 'title' => 'Failed Upload Arrive Photos', 'error' => 'No image files received.' ]; return $data; } else { $new_speed_filename = ''; $new_batt_filename = ''; $new_plate_num_filename = ''; $other_filenames = []; if (!empty($speed_img_file)) { // save speedometer file $orig_speed_filename = pathinfo($speed_img_file->getClientOriginalName(), PATHINFO_FILENAME); $new_speed_filename = uniqid() . '-'. $orig_speed_filename . '.' . $speed_img_file->guessClientExtension(); try { $speed_img_file->move($dest, $new_speed_filename); } catch (FileException $e) { $data = [ 'error' => 'Error saving image files.' ]; return $data; } } if (!empty($batt_img_file)) { // save battery file $orig_batt_filename = pathinfo($batt_img_file->getClientOriginalName(), PATHINFO_FILENAME); $new_batt_filename = uniqid() . '-' . $orig_batt_filename . '.' . $batt_img_file->guessClientExtension(); try { $batt_img_file->move($dest, $new_batt_filename); } catch (FileException $e) { $data = [ 'error' => 'Error saving image files.' ]; return $data; } } if (!empty($plate_num_img_file)) { // save plate number file $orig_plate_num_filename = pathinfo($plate_num_img_file->getClientOriginalName(), PATHINFO_FILENAME); $new_plate_num_filename = uniqid() . '-' . $orig_plate_num_filename . '.' . $plate_num_img_file->guessClientExtension(); try { $plate_num_img_file->move($dest, $new_plate_num_filename); } catch (FileException $e) { $data = [ 'error' => 'Error saving image files.' ]; return $data; } } foreach ($other_img_files as $other_img_file) { if (!(empty($other_img_file))) { foreach($other_img_file as $other_img) { $orig_other_filename = pathinfo($other_img->getClientOriginalName(), PATHINFO_FILENAME); $new_other_filename = uniqid() . '-'. $orig_other_filename . '.' . $other_img->guessClientExtension(); $other_filenames[] = $new_other_filename; try { $other_img->move($dest, $new_other_filename); } catch (FileException $e) { $data = [ 'error' => 'Error saving image files.' ]; return $data; } } } } $jo_extra = $jo->getJOExtra(); if ($jo_extra == null) { // create JOExtra entity $jo_extra = new JOExtra(); $jo_extra->setAfterSpeedImageFilename($new_speed_filename); $jo_extra->setAfterBattImageFilename($new_batt_filename); $jo_extra->setAfterPlateNumImageFilename($new_plate_num_filename); if (empty($other_filenames)) { $jo_extra->clearAfterOtherImages(); } else { $jo_extra->setAfterOtherImages($other_filenames); } $jo->setJOExtra($jo_extra); $this->em->persist($jo_extra); } else { $jo_extra->setAfterSpeedImageFilename($new_speed_filename); $jo_extra->setAfterBattImageFilename($new_batt_filename); $jo_extra->setAfterPlateNumImageFilename($new_plate_num_filename); if (empty($other_filenames)) { $jo_extra->clearAfterOtherImages(); } else { $jo_extra->setAfterOtherImages($other_filenames); } } $this->em->flush(); } return $data; } public function getStatus(Request $req) { $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) { $data['title'] = 'Failed Get Status'; return $data; } $rider = $this->session->getRider(); $rider_status = $rider->isAvailable(); $status = 'Offline'; if ($rider_status) $status = 'Online'; $data = [ 'status' => $status, ]; return $data; } public function getOngoingJobOrder(Request $req) { $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) { $data['title'] = 'Failed Get Ongoing Job Order'; $data['job_order'] = null; return $data; } // are we logged in? if (!$this->session->hasRider()) { $data = [ 'title' => 'Failed Get Ongoing Job Order', 'error' => 'No logged in rider.', 'job_order' => null, ]; return $data; } $rider = $this->session->getRider(); // check if we have an active JO $jo = $rider->getRiderActiveJobOrder(); if ($jo == null) { $data = [ 'title' => 'Failed Get Ongoing Job Order', 'error' => 'No active job order.', 'job_order' => null, ]; return $data; } $jo_data = []; // check if JO status is in_progress, in_transit, performed, paid switch($jo->getStatus()) { case JOStatus::IN_TRANSIT: case JOStatus::IN_PROGRESS: case JOStatus::PERFORMED: case JOStatus::PAID: $jo_data = $this->formatJobOrderData($req, $jo); break; } $data = [ 'job_order' => $jo_data ]; return $data; } public function getPaymentMethods(Request $req) { $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) { $data['title'] = 'Failed Get Payment Methods'; return $data; } $data = [ 'payment_methods' => CMBModeOfPayment::getCollection(), ]; return $data; } public function getCancelReasons(Request $req) { $required_params = []; $data = $this->checkParamsAndKey($req, $required_params); if (isset($data['error'])) { $data['title'] = 'Failed Get Cancel Reasons'; return $data; } $data = [ 'cancel_reasons' => CMBCancelReason::getCollection(), ]; return $data; } public function verifyJobOrder(Request $req) { $required_params = [ 'jo_id' ]; $data = $this->checkJOForVerify($req, $required_params, $jo); if (isset($data['error'])) { $data['title'] = 'Failed Verify Job Order'; return $data; } $rider = $this->session->getRider(); // check if rider is assigned to JO if ($jo->getRider() != null) { if ($rider->getID() != $jo->getRider()->getID()) { $data = [ 'assigned' => false, 'available' => false, ]; return $data; } } // check if JO status is not fulfilled and not cancelled if (($jo->getStatus() == JOStatus::FULFILLED) || ($jo->getStatus() == JOStatus::CANCELLED)) { $data = [ 'assigned' => true, 'available' => false ]; return $data; } $data = [ 'assigned' => true, 'available' => true, ]; 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 checkActiveJO(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->getRiderActiveJobOrder(); if ($jo == null) { $data = [ 'error' => 'No active job order.' ]; return $data; } $jo_id = ''; if ($req->getMethod() == 'GET') $jo_id = $req->query->get('jo_id'); else $jo_id = $req->request->get('jo_id'); // check if the jo_id sent is the same as our active jo if ($jo_id != $jo->getID()) { $data = [ 'error' => 'Job order selected is not active job order.' ]; return $data; } return $data; } 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(); // get jo $jo_id = ''; if ($req->getMethod() == 'GET') $jo_id = $req->query->get('jo_id'); else $jo_id = $req->request->get('jo_id'); $jo = $this->em->getRepository(JobOrder::class)->find($jo_id); if ($jo == null) { $data = [ 'error' => 'No job order found.' ]; return $data; } // check if rider assigned to jo is our rider if ($jo->getRider() == null) { $data = [ 'error' => 'Job order selected has no rider assigned.' ]; return $data; } // check if rider is assigned to JO if ($rider->getID() != $jo->getRider()->getID()) { $data = [ 'error' => 'Job order selected is not assigned to rider' ]; return $data; } return $data; } protected function checkJOForVerify(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(); // get jo $jo_id = ''; if ($req->getMethod() == 'GET') $jo_id = $req->query->get('jo_id'); else $jo_id = $req->request->get('jo_id'); $jo = $this->em->getRepository(JobOrder::class)->find($jo_id); if ($jo == null) { $data = [ 'error' => 'No job order found.' ]; return $data; } // check if rider assigned to jo is our rider if ($jo->getRider() == null) { $data = [ 'error' => 'Job order selected has no rider assigned.' ]; return $data; } /* // check if rider is assigned to JO if ($rider->getID() != $jo->getRider()->getID()) { $data = [ 'error' => 'Job order selected is not assigned to rider' ]; return $data; } */ return $data; } protected function debugRequest(Request $req) { $all = $req->request->all(); error_log(print_r($all, true)); } protected function getURLExtraImage(Request $req, $filename) { // return null if blank filename if (strlen(trim($filename)) <= 0) return null; return $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/jo_extra/' . $filename; } }