diff --git a/src/Controller/CustomerAppAPI/ApiController.php b/src/Controller/CustomerAppAPI/ApiController.php new file mode 100644 index 00000000..cc8f88d6 --- /dev/null +++ b/src/Controller/CustomerAppAPI/ApiController.php @@ -0,0 +1,157 @@ +session = new MobileSession; // NOTE: original was null + $this->em = $em; + + // load env file + $dotenv = new Dotenv(); + $dotenv->loadEnv($kernel->getProjectDir() . '.env'); + } + + protected function debugRequest(Request $req) + { + $all = $req->request->all(); + error_log(print_r($all, true)); + } + + protected function validateParams(Request $req, $params = []) + { + $missing = $this->checkRequiredParameters($req, $params); + if ($missing) { + return new ApiResponse(false, $missing, []); + } + } + + protected function validateSession($api_key) + { + // check if the session exists + $session = $this->em->getRepository(MobileSession::class)->find($api_key); + if ($session === null) { + return new ApiResponse(false, 'Invalid API Key.'); + } + + $this->session = $session; + } + + protected function validateRequest(Request $req, $params = []) + { + $this->validateParams($req, $params); + $this->validateSession($req->query->get('api_key')); + } + + protected function findWarranty($plate_number) + { + // NOTE: Modify the search for the latest warranty. This seems hacky. + // get latest warranty using plate number + $warranty_results = $this->em->getRepository(Warranty::class)->findBy( + ['plate_number' => $plate_number], + ['date_create' => 'desc'] + ); + + $warr = []; + + // check if warranty_results is empty + if (empty($warranty_results)) { + /* + $res->setError(true) + ->setErrorMessage('No warranty found for plate number'); + return $res->getReturnResponse(); + */ + + return $warr; + } + + // get first entry + $warranty = current($warranty_results); + + // check for null values for battery and date claim and date expire + $batt_model = ''; + $batt_size = ''; + $sap_batt = ''; + $claim_date = ''; + $expiry_date = ''; + + if (!(is_null($warranty->getBatteryModel()))) { + $batt_model = $warranty->getBatteryModel()->getName(); + } + if (!(is_null($warranty->getBatterySize()))) { + $batt_size = $warranty->getBatterySize()->getName(); + } + if (!(is_null($warranty->getSAPBattery()))) { + $sap_batt = $warranty->getSAPBattery()->getID(); + } + if (!(is_null($warranty->getDateClaim()))) { + $claim_date = $warranty->getDateClaim()->format("d M Y"); + } + if (!(is_null($warranty->getDateExpire()))) { + $expiry_date = $warranty->getDateExpire()->format("d M Y"); + } + + $warr[] = [ + 'id' => $warranty->getID(), + 'serial' => $warranty->getSerial(), + 'warranty_class' => $warranty->getWarrantyClass(), + 'plate_number' => $warranty->getPlateNumber(), + 'first_name' => $warranty->getFirstName(), + 'last_name' => $warranty->getLastName(), + 'mobile_number' => $warranty->getMobileNumber(), + 'battery_model' => $batt_model, + 'battery_size' => $batt_size, + 'sap_battery' => $sap_batt, + 'status' => $warranty->getStatus(), + 'date_create' => $warranty->getDateCreate()->format("d M Y g:i A"), + 'date_purchase' => $warranty->getDatePurchase()->format("d M Y"), + 'date_expire' => $expiry_date, + 'date_claim' => $claim_date, + 'claim_from' => $warranty->getClaimedFrom(), + 'is_activated' => $warranty->isActivated() ? 1 : 0, + ]; + + return $warr; + } + + protected function getBatteryImageURL($req, $batt) + { + // TODO: workaround for now, we get static image of battery based on model name + $filename = trim(strtolower($batt->getModel()->getName())) . '_mobile.jpg'; + $file_path = $req->getSchemeAndHttpHost() . $this->generateUrl('static_battery_image') . '/' . $filename; + + return $file_path; + } + + protected function getOngoingJobOrders($cust) + { + $ongoing_jos = $this->em->getRepository(JobOrder::class)->findBy([ + 'customer' => $cust, + 'status' => [JOStatus::PENDING, JOStatus::RIDER_ASSIGN, JOStatus::IN_TRANSIT, JOStatus::ASSIGNED, JOStatus::IN_PROGRESS], + ], ['date_schedule' => 'desc']); + + return $ongoing_jos; + } + + protected function getGeoErrorMessage() + { + return 'Oops! Our service is limited to some areas in Metro Manila, Laguna, Cavite, Pampanga and Baguio only. We will update you as soon as we are able to cover your area'; + } +} diff --git a/src/Controller/CustomerAppAPI/AppController.php b/src/Controller/CustomerAppAPI/AppController.php new file mode 100644 index 00000000..21af64bf --- /dev/null +++ b/src/Controller/CustomerAppAPI/AppController.php @@ -0,0 +1,45 @@ +validateParams($req, [ + 'version', + ]); + + $need_update = false; + $msg = 'Version is up to date.'; + + $api_version = $this->getParameter('api_version'); + + $app_version = $req->query->get('version'); + // putting this in for the future, in case we have diverging versions + $os = $req->query->get('os'); + $platform = $req->query->get('platform'); + + $api_v = explode('.', $api_version); + $app_v = explode('.', $app_version); + + if ($api_v[0] < $app_v[0]) { + return new ApiResponse(false, 'Invalid application version: ' . $app_version); + } + + if ($api_v[0] > $app_v[0]) { + $need_update = true; + $msg = 'Your version is outdated and needs an update to use the latest features RES-Q has to offer.'; + } + + // response + return new ApiResponse(true, '', [ + 'need_update' => $need_update, + 'message' => $msg, + ]); + } +} diff --git a/src/Controller/CustomerAppAPI/AuthController.php b/src/Controller/CustomerAppAPI/AuthController.php new file mode 100644 index 00000000..fd98a038 --- /dev/null +++ b/src/Controller/CustomerAppAPI/AuthController.php @@ -0,0 +1,229 @@ +validateParams($req, [ + 'phone_model', + 'os_type', + 'os_version', + 'phone_id', + ]); + + // retry until we get a unique id + while (true) { + try { + // instantiate session + $sess = new MobileSession(); + $sess->setPhoneModel($req->request->get('phone_model')) + ->setOSType($req->request->get('os_type')) + ->setOSVersion($req->request->get('os_version')) + ->setPhoneID($req->request->get('phone_id')); + + // reopen in case we get an exception + if (!$this->em->isOpen()) { + $this->em = $this->em->create( + $this->em->getConnection(), + $this->em->getConfiguration() + ); + } + + // save + $this->em->persist($sess); + $this->em->flush(); + } catch (DBALException $e) { + error_log($e->getMessage()); + // delay one second and try again + sleep(1); + continue; + } + + break; + } + + // return data + return new ApiResponse(true, '', [ + 'session_id' => $sess->getID(), + ]); + } + + public function confirmNumber(RisingTideGateway $rt, Request $req, TranslatorInterface $translator) + { + // validate request + $this->validateRequest($req, [ + 'phone_number' + ]); + + // phone number + $phone_number = $req->request->get('phone_number'); + + // get otp_mode from .env + $otp_mode = $_ENV['OTP_MODE']; + + // check for hardcoded phone number for app store testing + if ($phone_number == '639221111111') { + $code = '123456'; + $this->session->setConfirmCode($code) + ->setPhoneNumber($phone_number); + $this->em->flush(); + + return new ApiResponse(); + } + + // check if otp_mode is test + if ($otp_mode == 'test') { + $code = '123456'; + $this->session->setConfirmCode($code) + ->setPhoneNumber($phone_number); + $this->em->flush(); + + return new ApiResponse(); + } + + // TODO: spam protection + + // TODO: validate phone number + + // generate code and save + $code = $this->generateConfirmCode(); + $this->session->setConfirmCode($code) + ->setPhoneNumber($phone_number); + $this->em->flush(); + + if ($otp_mode != 'test') { + // send sms to number + $this->sendConfirmationCode($rt, $phone_number, $code, $translator); + } + + // response + return new ApiResponse(); + } + + public function validateCode(Request $req) + { + // validate request + $this->validateRequest($req, [ + 'code' + ]); + + // code is wrong + $code = $req->request->get('code'); + if ($this->session->getConfirmCode() != $code) { + return new ApiResponse(false, 'Wrong confirm code.'); + } + + // set confirm date + $date = new DateTime(); + $this->session->setDateConfirmed($date) + ->setConfirmed(); + + + // TODO: check if we have the number registered before and merge + $dupe_sess = $this->findNumberSession($this->session->getPhoneNumber()); + if ($dupe_sess != null) { + $dupe_cust = $dupe_sess->getCustomer(); + $this->session->setCustomer($dupe_cust); + } + + // TODO: check if mobile matches mobile of customer + $customer = $this->findCustomerByNumber($this->session->getPhoneNumber()); + if ($customer != null) { + // TODO: if there is a dupe_sess, do we need to check if + // dupe_cust is the same as the customer we found? + $this->session->setCustomer($customer); + } + + $this->em->flush(); + + // response + return new ApiResponse(); + } + + public function resendCode(Request $req, RisingTideGateway $rt, TranslatorInterface $translator) + { + // validate request + $this->validateRequest($req); + + // already confirmed + if ($this->session->isConfirmed()) { + return new ApiResponse(false, 'User is already confirmed.'); + } + + // have sent code before + if ($this->session->getDateCodeSent() != null) { + return new ApiResponse(false, 'Can only send confirm code every 5 mins.'); + } + + // TODO: send via sms + $phone_number = $this->session->getPhoneNumber(); + $code = $this->session->getConfirmCode(); + $this->sendConfirmationCode($rt, $phone_number, $code, $translator); + + // response + return new ApiResponse(); + } + + protected function generateConfirmCode() + { + return sprintf("%06d", mt_rand(100000, 999999)); + } + + protected function sendConfirmationCode(RisingTideGateway $rt, $phone_number, $code, TranslatorInterface $translator) + { + // send sms to number + $message = $translator->trans('message.confirmation_code') . ' ' . $code; + $rt->sendSMS($phone_number, $translator->trans('message.battery_brand_allcaps'), $message); + } + + // TODO: find session customer by phone number + protected function findNumberSession($number) + { + $query = $this->em->getRepository(MobileSession::class)->createQueryBuilder('s') + ->where('s.phone_number = :number') + ->andWhere('s.customer is not null') + ->andWhere('s.confirm_flag = 1') + ->setParameter('number', $number) + ->setMaxResults(1) + ->getQuery(); + + // we just need one + $res = $query->getOneOrNullResult(); + + return $res; + } + + protected function findCustomerByNumber($number) + { + $customers = $this->em->getRepository(Customer::class)->findBy(['phone_mobile' => $number]); + + // find the customer with the most number of cars + $car_count = 0; + $cust = null; + + foreach ($customers as $customer) { + $vehicles = $customer->getVehicles(); + if (count($vehicles) > $car_count) { + $car_count = count($vehicles); + + // "save" customer object + $cust = $customer; + } + } + + return $cust; + } +} diff --git a/src/Controller/CustomerAppAPI/CustomerController.php b/src/Controller/CustomerAppAPI/CustomerController.php new file mode 100644 index 00000000..cfaf4833 --- /dev/null +++ b/src/Controller/CustomerAppAPI/CustomerController.php @@ -0,0 +1,140 @@ +validateRequest($req); + + // if no customer found + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(true, '', [ + 'first_name' => '', + 'last_name' => '', + 'priv_third_party' => (bool) false, + 'priv_promo' => (bool) false, + ]); + } + + // send back customer details + return new ApiResponse(true, '', [ + 'first_name' => $cust->getFirstName(), + 'last_name' => $cust->getLastName(), + 'priv_third_party' => (bool) $cust->getPrivacyThirdParty(), + 'priv_promo' => (bool) $cust->getPrivacyPromo(), + ]); + } + + public function updateInfo(Request $req) + { + // validate params + $this->validateRequest($req, [ + 'first_name', + 'last_name', + ]); + + $cust = $this->updateCustomerInfo($req); + + $policy_mobile_id = $_ENV['POLICY_MOBILE']; + $mobile_policy = $this->em->getRepository(PrivacyPolicy::class)->find($policy_mobile_id); + + // set policy id + if ($mobile_policy != null) { + $cust->setPrivacyPolicyMobile($mobile_policy); + } + + $this->em->flush(); + + // response + return new ApiResponse(); + } + + public function getStatus(Request $req) + { + // validate params + $this->validateRequest($req); + + // set data + $data = []; + if ($this->session->isConfirmed()) { + $data['status'] = 'confirmed'; + } else { + $data['status'] = 'unconfirmed'; + } + + return new ApiResponse(true, '', $data); + } + + public function updateDeviceID(Request $req) + { + // validate params + $this->validateRequest($req, [ + 'device_id', + ]); + + $device_id = $req->request->get('device_id'); + $this->session->setDevicePushID($device_id); + + $this->em->flush(); + + // response + return new ApiResponse(); + } + + protected function updateCustomerInfo(Request $req) + { + // create new customer if it's not there + $cust = $this->session->getCustomer(); + if ($cust == null) { + $cust = new Customer(); + + // set customer source + $cust->setCreateSource(CustomerSource::MOBILE); + $this->em->persist($cust); + + $this->session->setCustomer($cust); + } + + $cust->setFirstName($req->request->get('first_name')) + ->setLastName($req->request->get('last_name')) + ->setEmail($req->request->get('email', '')) + ->setConfirmed($this->session->isConfirmed()); + + // update mobile phone of customer + $cust->setPhoneMobile(substr($this->session->getPhoneNumber(), 2)); + + return $cust; + } + + public function getCustomerHash(Request $req, HashGenerator $hash) + { + // validate params + $this->validateRequest($req); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // hash customer id + $hashed_id = $hash->getHash($cust->getID()); + + // response + return new ApiResponse(true, '', [ + 'cust_hash' => $hashed_id, + ]); + } +} diff --git a/src/Controller/CustomerAppAPI/JobOrderController.php b/src/Controller/CustomerAppAPI/JobOrderController.php new file mode 100644 index 00000000..28c71a89 --- /dev/null +++ b/src/Controller/CustomerAppAPI/JobOrderController.php @@ -0,0 +1,1374 @@ +debugRequest($req); + + // validate params + $this->validateRequest($req, [ + 'service_type', + 'cv_id', + // 'batt_id', + 'trade_in', + ]); + + // customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // make invoice criteria + $icrit = new InvoiceCriteria(); + $icrit->setServiceType($req->request->get('service_type')); + + // check promo + $promo_id = $req->request->get('promo_id'); + if (!empty($promo_id)) { + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + if ($promo == null) { + return new ApiResponse(false, 'Invalid promo id.'); + } + + // put in criteria + $icrit->addPromo($promo); + } + + // check customer vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id')); + if ($cv == null) { + return new ApiResponse(false, 'Invalid customer vehicle id.'); + } + $icrit->setCustomerVehicle($cv); + + // check if customer owns vehicle + if ($cust->getID() != $cv->getCustomer()->getID()) { + return new ApiResponse(false, 'Customer does not own vehicle.'); + } + + // check battery + $batt_id = $req->request->get('batt_id'); + if ($batt_id != null) { + $batt = $this->em->getRepository(Battery::class)->find($batt_id); + if ($batt == null) { + return new ApiResponse(false, 'Invalid battery id.'); + } + } else + $batt = null; + + /* + // put battery in criteria + $icrit->addBattery($batt); + */ + + // check trade-in + // only allow motolite, other, none + $trade_in = $req->request->get('trade_in'); + switch ($trade_in) { + case TradeInType::MOTOLITE: + case TradeInType::OTHER: + break; + + default: + $trade_in = ''; + break; + } + + $icrit->addEntry($batt, $trade_in, 1); + + // send to invoice generator + $invoice = $ic->generateInvoice($icrit); + + // make invoice json data + $data = [ + 'total_price' => (float) $invoice->getTotalPrice(), + 'vat_ex_price' => (float) $invoice->getVATExclusivePrice(), + 'vat' => (float) $invoice->getVAT(), + 'discount' => (float) $invoice->getDiscount(), + 'trade_in' => (float) $invoice->getTradeIn(), + ]; + $items = $invoice->getItems(); + $items_data = []; + foreach ($items as $item) { + $my_data = [ + 'title' => $item->getTitle(), + 'qty' => (int) $item->getQuantity() + 0, + 'price' => (float) $item->getPrice() + 0.0, + ]; + + $item_batt = $item->getBattery(); + if ($item_batt != null) { + $my_data['image_url'] = $this->getBatteryImageURL($req, $item_batt); + } + + $items_data[] = $my_data; + } + + $data['items'] = $items_data; + + // error_log(print_r($data, true)); + + // response + return new ApiResponse(true, '', $data); + } + + public function getOngoing(Request $req) + { + // validate params + $this->validateRequest($req); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + /* + // check if we have an ongoing job order + $ongoing_jos = $em->getRepository(JobOrder::class)->findBy([ + 'customer' => $cust, + 'status' => [JOStatus::PENDING, JOStatus::RIDER_ASSIGN, JOStatus::IN_TRANSIT, JOStatus::ASSIGNED, JOStatus::IN_PROGRESS], + ]); + */ + $ongoing_jos = $this->getOngoingJobOrders($cust); + + // initialize data + $data = []; + + // no ongoing + if (count($ongoing_jos) <= 0) { + $data = [ + 'has_ongoing' => false, + ]; + } else { + $data = [ + 'has_ongoing' => true, + ]; + } + + // response + return new ApiResponse(true, '', $data); + } + + public function getJOInvoice(Request $req) + { + // validate params + $this->validateRequest($req, [ + 'jo_id', + ]); + + // get job order + $jo_id = $req->query->get('jo_id'); + $jo = $this->em->getRepository(JobOrder::class)->find($jo_id); + if ($jo == null) { + return new ApiResponse(false, 'No job order found.'); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // check that the customer owns the job order + $jo_cust = $jo->getCustomer(); + if ($jo_cust->getID() != $cust->getID()) { + return new ApiResponse(false, 'Job order was not initiated by customer.'); + } + + $invoice = $jo->getInvoice(); + + // make invoice json data + $data = [ + 'total_price' => (float) $invoice->getTotalPrice(), + 'vat_ex_price' => (float) $invoice->getVATExclusivePrice(), + 'vat' => (float) $invoice->getVAT(), + 'discount' => (float) $invoice->getDiscount(), + 'trade_in' => (float) $invoice->getTradeIn(), + ]; + $items = $invoice->getItems(); + $items_data = []; + foreach ($items as $item) { + $my_data = [ + 'title' => $item->getTitle(), + 'qty' => (int) $item->getQuantity() + 0, + 'price' => (float) $item->getPrice() + 0.0, + ]; + + $item_batt = $item->getBattery(); + if ($item_batt != null) { + $my_data['image_url'] = $this->getBatteryImageURL($req, $item_batt); + } + + $items_data[] = $my_data; + } + + $data['items'] = $items_data; + + /* + // invoice items + $inv_items = []; + foreach ($inv->getItems() as $item) + { + $item_batt = $item->getBattery(); + if ($item_batt == null) + $batt_id = null; + else + $batt_id = $item_batt->getID(); + + $inv_items[] = [ + 'id' => $item->getID(), + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity(), + 'price' => $item->getPrice(), + 'batt_id' => $batt_id, + ]; + } + + $data = [ + 'invoice' => [ + 'discount' => $inv->getDiscount(), + 'trade_in' => $inv->getTradeIn(), + 'total_price' => $inv->getTotalPrice(), + 'vat' => $inv->getVat(), + 'items' => $inv_items, + ], + ]; + + */ + + // response + return new ApiResponse(true, '', $data); + } + + public function cancelJobOrder(Request $req, MQTTClient $mclient) + { + // validate params + $this->validateRequest($req, [ + 'jo_id', + 'reason', + ]); + + // get job order + $jo_id = $req->request->get('jo_id'); + $jo = $this->em->getRepository(JobOrder::class)->find($jo_id); + if ($jo == null) { + return new ApiResponse(false, 'No job order found.'); + } + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // check that the customer owns the job order + $jo_cust = $jo->getCustomer(); + if ($jo_cust->getID() != $cust->getID()) { + return new ApiResponse(false, 'Job order was not initiated by customer.'); + } + + // TODO: check job order status, if it's cancellable + $cancel_reason = $req->request->get('reason'); + + $jo->cancel($cancel_reason); + + // add event log + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CANCEL) + ->setJobOrder($jo); + $this->em->persist($event); + + $this->em->flush(); + + // send mobile app event + $payload = [ + 'event' => 'cancelled', + 'reason' => $cancel_reason, + 'jo_id' => $jo->getID(), + ]; + // $mclient->sendEvent($jo, $payload); + $mclient->sendRiderEvent($jo, $payload); + + // response + return new ApiResponse(); + } + + // we can't use param converter for now because we want to output the proper 404 + public function getJobOrderInfo($id, Request $req, RiderTracker $rt) + { + // validate params + $this->validateRequest($req); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get job order data + $jo = $this->em->getRepository(JobOrder::class)->find($id); + if ($jo == null) { + return new ApiResponse(false, 'No job order information found.'); + } + + // check if job order belongs to customer / user + if ($cust->getID() != $jo->getCustomer()->getID()) { + return new ApiResponse(false, 'No job order information found.'); + } + + // put into job order data array + $jo_data = $this->generateJobOrderData($req, $jo, $rt); + + // response + return new ApiResponse(true, '', [ + 'job_order' => $jo_data, + ]); + } + + public function getJOHistory(Request $req) + { + // validate params + $this->validateRequest($req); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get job orders + $all_jo_data = []; + // $jos = $cust->getJobOrders(); + // get the fulfilled and cancelled job orders, since ongoing jos are not yet part of history + $jos = $this->em->getRepository(JobOrder::class)->findBy([ + 'customer' => $cust, + 'status' => [JOStatus::CANCELLED, JOStatus::FULFILLED] + ], ['date_schedule' => 'DESC']); + foreach ($jos as $jo) { + // NOTE: use generateJobOrderData method, maybe? + $status = $jo->getStatus(); + + $jo_data = [ + 'id' => $jo->getID(), + 'date_create' => $jo->getDateCreate()->format('M d, Y'), + 'service_type' => $jo->getServiceType(), + 'status' => $status, + ]; + + // customer vehicle and warranty + $cv = $jo->getCustomerVehicle(); + + // get latest warranty using plate number + $warranty = $this->findWarranty($cv->getPlateNumber()); + + $jo_data['customer_vehicle'] = [ + 'id' => $cv->getID(), + 'plate_number' => $cv->getPlateNumber(), + 'warranty' => $warranty, + ]; + + // rider + $rider = $jo->getRider(); + + // check if jo has rider rating set to true + $has_rider_rating = $jo->hasRiderRating(); + $rating = 0; + $comment = ''; + if ($rider != null) { + $jo_data['rider'] = $rider->getFullName(); + + // find the rider rating if any + if ($has_rider_rating) { + $jo_rating = $jo->getRating(); + if ($jo_rating != null) { + $rating = $jo_rating->getRating(); + + // get comment + $comment = $jo_rating->getComment(); + } + } + } + + // rider rating for jo + $jo_data['has_rider_rating'] = $has_rider_rating; + $jo_data['rider_rating'] = $rating; + $jo_data['comment'] = $comment; + + // invoice items + $items = []; + $jo_items = $jo->getInvoice()->getItems(); + foreach ($jo_items as $item) { + $items[] = [ + 'id' => $item->getID(), + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity(), + 'price' => $item->getPrice(), + ]; + } + + $jo_data['items'] = $items; + + // dates depending on status + switch ($status) { + case JOStatus::FULFILLED: + if ($jo->getDateFulfill() == null) + $jo_data['date_fulfilled'] = ''; + else + $jo_data['date_fulfilled'] = $jo->getDateFulfill()->format('M d, Y'); + break; + case JOStatus::CANCELLED: + $date_cancel = $jo->getDateCancel(); + if ($date_cancel == null) + $date_cancel = new DateTime(); + $jo_data['date_cancelled'] = $date_cancel->format('M d, Y'); + break; + } + + $all_jo_data[] = $jo_data; + } + + // response + return new ApiResponse(true, '', [ + 'job_orders' => $all_jo_data, + ]); + } + + public function getLatestJobOrder(Request $req, RiderTracker $rt) + { + // validate params + $this->validateRequest($req); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get the latest job order for customer + $latest_jo = $this->em->getRepository(JobOrder::class)->findOneBy(['customer' => $cust], ['id' => 'DESC']); + + $jo_data = null; + if ($latest_jo != null) { + // TODO: clean the response up to just return what is needed + $jo_data = $this->generateLatestJobOrderData($req, $latest_jo, $rt); + } + + // response + return new ApiResponse(true, '', [ + 'latest_job_order' => $jo_data, + ]); + } + + public function getAllOngoingJobOrders(Request $req, RiderTracker $rt) + { + // validate params + $this->validateRequest($req); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + $ongoing_jos = $this->getOngoingJobOrders($cust); + + // initialize data + $jo_data = []; + foreach ($ongoing_jos as $jo) { + $jo_data[] = $this->generateJobOrderData($req, $jo, $rt); + } + + // response + return new ApiResponse(true, '', [ + 'ongoing_job_orders' => $jo_data, + ]); + } + + public function getOngoingJobOrderCount(Request $req) + { + // validate params + $this->validateRequest($req); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + $ongoing_jos = $this->getOngoingJobOrders($cust); + + // response + return new ApiResponse(true, '', [ + 'ongoing_job_order_count' => count($ongoing_jos), + ]); + } + + public function newRequestJobOrder( + Request $req, + InvoiceGeneratorInterface $ic, + GeofenceTracker $geo, + MapTools $map_tools, + InventoryManager $im, + MQTTClient $mclient, + RiderAssignmentHandlerInterface $rah, + PromoLogger $promo_logger, + HubSelector $hub_select, + HubDistributor $hub_dist, + HubFilterLogger $hub_filter_logger, + HubFilteringGeoChecker $hub_geofence + ) { + // validate params + $this->validateRequest($req, [ + 'service_type', + 'cv_id', + 'trade_in', + 'long', + 'lat', + 'warranty', + 'mode_of_payment', + ]); + + // trade in type + $trade_in = $req->request->get('trade_in'); + + // address + $address = $req->request->get('delivery_address', 'Set by mobile application'); + + // instructions + $instructions = $req->request->get('delivery_instructions', ''); + + // landmark + $landmark = $req->request->get('landmark', ' '); + + // longitude and latitude + $long = $req->request->get('long'); + $lat = $req->request->get('lat'); + + // NOTE: had to move this up so we can check for promo before geofence + // customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + $is_covered = false; + // check if customer still has promo + if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) || + ($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')) + ) { + // if has customer tag, customer has not availed of promo + $is_covered = true; + } else { + // geofence + $is_covered = $geo->isCovered($long, $lat); + } + + if (!$is_covered) { + // TODO: put geofence error message in config file somewhere + return new ApiResponse(false, $this->getGeoErrorMessage()); + } + + $hub = null; + $hub_id = $req->request->get('hub_id'); + // check if hub_id is -1 which means user clicked Book Now before 5 PM + // but confirmed the order after 5 PM + if ($hub_id == -1) { + return new ApiResponse(false, 'Book Now no longer available.'); + } + if (strlen($hub_id) > 0) + $hub = $this->em->getRepository(Hub::class)->find($hub_id); + + $schedule_date = $req->request->get('date_schedule'); + $slot_id = $req->request->get('slot_id'); + + // process the jo date schedule + $date_schedule = null; + if ((strlen($schedule_date) > 0) && (strlen($slot_id) > 0)) { + $time_schedule = $this->getTimeFromSlot($slot_id); + if (!empty($time_schedule)) { + $s_date = $schedule_date . ' ' . $time_schedule; + $date_schedule = DateTime::createFromFormat('Y-m-d H:i', $s_date); + // error_log($date_schedule->format('Y-m-d H:i')); + } + } + + $advance_order = $req->request->get('flag_advance_order'); + // check for 'false' text + if ($advance_order === false || $advance_order === 0 || $advance_order === '0' || $advance_order == 'false') + $flag_advance_order = false; + else + $flag_advance_order = true; + // $flag_advance_order = $advance_order ? true : false; + + $jo = new JobOrder(); + $jo->setSource(TransactionOrigin::MOBILE_APP) + ->setStatus(JOStatus::PENDING) + ->setDeliveryInstructions('') + ->setTier1Notes('') + ->setTier2Notes('') + ->setDeliveryAddress($address) + ->setTradeInType($trade_in) + ->setDeliveryInstructions($instructions) + // TODO: error check for valid mode of payment + ->setModeOfPayment($req->request->get('mode_of_payment')) + ->setAdvanceOrder($flag_advance_order) + ->setStatusAutoAssign(AutoAssignStatus::NOT_ASSIGNED) + ->setLandmark($landmark); + + // customer + // $cust = $this->session->getCustomer(); + // if ($cust == null) + // { + // $res->setError(true) + // ->setErrorMessage('No customer information found'); + // return $res->getReturnResponse(); + // } + $jo->setCustomer($cust); + + // validate service type + $stype = $req->request->get('service_type'); + if (!ServiceType::validate($stype)) { + return new ApiResponse(false, 'Invalid service type.'); + } + $jo->setServiceType($stype); + + // validate warranty + $warr = $req->request->get('warranty'); + if (!WarrantyClass::validate($warr)) { + return new ApiResponse(false, 'Invalid warranty class.'); + } + $jo->setWarrantyClass($warr); + + // set coordinates + $point = new Point($long, $lat); + $jo->setCoordinates($point); + + // make invoice criteria + $icrit = new InvoiceCriteria(); + $icrit->setServiceType($stype); + + // check promo + $promo_id = $req->request->get('promo_id'); + if (!empty($promo_id)) { + $promo = $this->em->getRepository(Promo::class)->find($promo_id); + if ($promo == null) { + return new ApiResponse(false, 'Invalid promo id.'); + } + + // put in criteria + $icrit->addPromo($promo); + } + + // check customer vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id')); + if ($cv == null) { + return new ApiResponse(false, 'Invalid customer vehicle id.'); + } + $icrit->setCustomerVehicle($cv); + $jo->setCustomerVehicle($cv); + + // check if customer owns vehicle + if ($cust->getID() != $cv->getCustomer()->getID()) { + return new ApiResponse(false, 'Customer does not own vehicle.'); + } + + // check battery + $batt_id = $req->request->get('batt_id'); + if ($batt_id != null) { + $batt = $this->em->getRepository(Battery::class)->find($batt_id); + if ($batt == null) { + return new ApiResponse(false, 'Invalid battery id.'); + } + } else + $batt = null; + + /* + // put battery in criteria + $icrit->addBattery($batt); + */ + + // check trade-in + // only allow motolite, other, none + switch ($trade_in) { + case TradeInType::MOTOLITE: + case TradeInType::OTHER: + break; + + default: + $trade_in = ''; + break; + } + + $icrit->addEntry($batt, $trade_in, 1); + + // send to invoice generator + $invoice = $ic->generateInvoice($icrit); + $jo->setInvoice($invoice); + + // assign hub and rider + // check if hub is null + if ($hub == null) { + // TODO: need to factor out the setting of HubCriteria fields + $hub_criteria = new HubCriteria(); + $hub_criteria->setPoint($jo->getCoordinates()); + + // get distance limit for mobile from env + // get value of hub_filter_enable from env + $limit_distance = $_ENV['CUST_DISTANCE_LIMIT']; + $hub_filter_enabled = $_ENV['HUB_FILTER_ENABLE']; + + // set distance limit + $hub_criteria->setLimitDistance($limit_distance); + + // check if hub filter is enabled. If not, use default values + // for the rest of the HubCriteria fields + if ($hub_filter_enabled == 'true') { + // error_log('hub filter is enabled'); + // check if customer location is in hub filter area + if ($hub_geofence->isCovered($long, $lat)) { + // if true, set other values for HubCriteria + // TODO: set this properly, since the other flags + // are on default values + // error_log('Area is covered by hub filtering'); + $hub_criteria->setJoType($jo->getServiceType()) + ->setPaymentMethod($jo->getModeOfPayment()) + ->setRoundRobin(true); + } + } + + // check if batt is null + if ($batt != null) { + // add battery to items + $sku = $batt->getSAPCode(); + if (!empty($sku)) + $hub_criteria->addItem($batt->getSAPCode(), 1); + } + + // get customer id. No JO id at this point + $customer_id = $cust->getID(); + + $hub_criteria->setCustomerId($customer_id); + + // find nearest hubs + $nearest_hubs = $hub_select->find($hub_criteria); + + if (!empty($nearest_hubs)) { + // go through the hub list, find the nearest hub + // with an available rider + // error_log('found nearest hub ' . $nearest_hub->getID()); + foreach ($nearest_hubs as $nearest_hub) { + // check if hub can be auto assigned + // if not, move on to the next hub in the list + if (($nearest_hub['hub']->isHubAutoAssign())) { + // check if hub has riders that can be auto assigned + // if not, move on to the next hub + if (($nearest_hub['hub']->isRiderAutoAssign())) { + $available_riders = $nearest_hub['hub']->getAvailableRiders(); + if (count($available_riders) >= 1) { + $assigned_rider = null; + if (count($available_riders) == 1) { + $assigned_rider = $available_riders[0]; + } else { + // TODO: the setting of riders into an array + // will no longer be necessary when the contents + // of randomizeRider changes + $riders = []; + foreach ($available_riders as $rider) { + $riders[] = $rider; + } + + $assigned_rider = $this->randomizeRider($riders); + } + + $jo->setHub($nearest_hub['hub']); + $jo->setRider($assigned_rider); + $jo->setStatus(JOStatus::ASSIGNED); + $jo->setStatusAutoAssign(AutoAssignStatus::HUB_AND_RIDER_ASSIGNED); + $jo->setDeliveryStatus(DeliveryStatus::RIDER_ASSIGN); + + // set date_assigned for job order + $jo->setDateAssign(new DateTime()); + + $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']); + + // break out of loop + break; + } else { + // we just create the JO and let admin panel handle the hub assignment + // log hub into hub_filter_log + $hub_filter_logger->logFilteredHub($nearest_hub['hub'], 'no_available_rider', null, $cust->getID()); + // continue to go through list to find hub with an available rider + } + } else { + // TODO: log hub as cannot be auto rider assigned somewhere + // assign hub + // error_log('Rider cannot be auto assigned ' . $nearest_hub['hub']->getID()); + $jo->setHub($nearest_hub['hub']); + $jo->setStatus(JOStatus::RIDER_ASSIGN); + $jo->setStatusAutoAssign(AutoAssignStatus::HUB_ASSIGNED); + + // update redis hub_jo_count for hub + $hub_dist->incrementJoCountForHub($nearest_hub['hub']); + + break; + } + } else { + // TODO: log hub as cannot be auto assigned somewhere + // move to next hub + error_log('Hub cannot be auto-assigned ' . $nearest_hub['hub']->getID()); + continue; + } + } + } + } else { + $jo->setHub($hub); + $jo->setStatus(JOStatus::RIDER_ASSIGN); + $jo->setStatusAutoAssign(AutoAssignStatus::HUB_ASSIGNED); + + if ($date_schedule != null) + $jo->setDateSchedule($date_schedule); + + // update redis hub_jo_count for hub + $hub_dist->incrementJoCountForHub($hub); + } + + $this->em->persist($jo); + $this->em->persist($invoice); + + // add event log for JO + $event = new JOEvent(); + $event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::CREATE) + ->setJobOrder($jo); + $this->em->persist($event); + + // check JO status + if ($jo->getStatus() == JOStatus::ASSIGNED) { + // add event logs for hub and rider assignments + $hub_assign_event = new JOEvent(); + $hub_assign_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($jo); + + $this->em->persist($hub_assign_event); + + $rider_assign_event = new JOEvent(); + $rider_assign_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::RIDER_ASSIGN) + ->setJobOrder($jo); + + $this->em->persist($rider_assign_event); + + // user mqtt event + $payload = [ + 'event' => 'outlet_assign' + ]; + $mclient->sendEvent($jo, $payload); + + $rah->assignJobOrder($jo, $jo->getRider()); + } + + if ($jo->getStatus() == JOStatus::RIDER_ASSIGN) { + // add event logs for hub assignments + $hub_assign_event = new JOEvent(); + $hub_assign_event->setDateHappen(new DateTime()) + ->setTypeID(JOEventType::HUB_ASSIGN) + ->setJobOrder($jo); + + $this->em->persist($hub_assign_event); + + // user mqtt event + $payload = [ + 'event' => 'outlet_assign' + ]; + $mclient->sendEvent($jo, $payload); + } + + $this->em->flush(); + + // make invoice json data + $invoice_data = [ + 'total_price' => $invoice->getTotalPrice(), + 'vat_ex_price' => (float) $invoice->getVATExclusivePrice(), + 'vat' => $invoice->getVAT(), + 'discount' => $invoice->getDiscount(), + 'trade_in' => $invoice->getTradeIn(), + ]; + $items = $invoice->getItems(); + $items_data = []; + foreach ($items as $item) { + $items_data[] = [ + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity() + 0, + 'price' => $item->getPrice() + 0.0, + ]; + } + $invoice_data['items'] = $items_data; + + // need to check for customer tag/promo + // check service type + if ($jo->getServiceType() == ServiceType::BATTERY_REPLACEMENT_NEW) { + $customer = $cv->getCustomer(); + $customer_tags = $customer->getCustomerTagObjects(); + if (!empty($customer_tags)) { + foreach ($customer_tags as $customer_tag) { + // TODO: not too comfy with this being hardcoded + if ($customer_tag->getID() == $invoice->getUsedCustomerTagId()) { + // remove associated entity + $customer->removeCustomerTag($customer_tag); + + // log the availment of promo from customer + $created_by = $req->query->get('api_key');; + $cust_id = $jo->getCustomer()->getID(); + $cust_fname = $jo->getCustomer()->getFirstName(); + $cust_lname = $jo->getCustomer()->getLastName(); + $jo_id = $jo->getID(); + $invoice_id = $jo->getInvoice()->getID(); + // TODO: check if we store total price of invoice or just the discounted amount + $amount = $jo->getInvoice()->getTotalPrice(); + $promo_logger->logPromoInfo( + $created_by, + $cust_id, + $cust_fname, + $cust_lname, + $jo_id, + $invoice_id, + $amount + ); + } + } + } + } + + // response + return new ApiResponse(true, '', [ + 'jo_id' => $jo->getID(), + 'invoice' => $invoice_data, + ]); + } + + // commenting it out. Modify the getJOHistory instead to just get the fulfilled + // and cancelled job orders, since ongoing is not yet part of history + /* + public function getCompletedJobOrders(Request $req, EntityManagerInterface $em, RiderTracker $rt) + { + // validate params + $this->validateRequest($req); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) + { + return new ApiResponse(false, 'No customer information found.'); + } + + $completed_jos = $this->getCompletedJOs($cust); + + // initialize data + $jo_data = []; + foreach ($completed_jos as $jo) + { + $jo_data[] = $this->generateJobOrderData($req, $jo, $rt); + } + + // response + return new ApiResponse(true, '', [ + 'completed_job_orders' => $jo_data, + ]); + } + + protected function getCompletedJOs($cust) + { + $completed_jos = $this->em->getRepository(JobOrder::class)->findBy([ + 'customer' => $cust, + 'status' => [JOStatus::CANCELLED, JOStatus::FULFILLED], + ], ['date_schedule' => 'desc']); + + return $completed_jos; + } + */ + + protected function generateJobOrderData($req, $jo, $rt) + { + $status = $jo->getStatus(); + + $dest = $jo->getCoordinates(); + + $jo_data = [ + 'id' => $jo->getID(), + 'date_create' => $jo->getDateCreate()->format('M d, Y'), + 'service_type' => $jo->getServiceType(), + 'destination' => [ + 'long' => $dest->getLongitude(), + 'lat' => $dest->getLatitude(), + ], + 'delivery_address' => $jo->getDeliveryAddress(), + 'delivery_instructions' => $jo->getDeliveryInstructions(), + 'jo_status' => $status, + 'status' => $this->generateAPIRiderStatus($status), + ]; + + // customer vehicle and warranty + $cv = $jo->getCustomerVehicle(); + + // get latest warranty using plate number + $warranty = $this->findWarranty($cv->getPlateNumber()); + + $jo_data['customer_vehicle'] = [ + 'id' => $cv->getID(), + 'plate_number' => $cv->getPlateNumber(), + 'warranty' => $warranty, + ]; + + // customer information + $customer = $jo->getCustomer(); + $jo_data['customer'] = [ + 'first_name' => $customer->getFirstName(), + 'last_name' => $customer->getLastName(), + 'mobile_number' => $customer->getPhoneMobile(), + ]; + + // rider + $rider = $jo->getRider(); + if ($rider != null) { + // default image url + $url_prefix = $req->getSchemeAndHttpHost(); + $image_url = $url_prefix . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $url_prefix . '/uploads/' . $rider->getImageFile(); + + $coord = $rt->getRiderLocation($rider->getID()); + + $jo_data['rider'] = [ + 'id' => $rider->getID(), + 'name' => $rider->getFullName(), + 'plate_num' => $rider->getPlateNumber(), + 'contact_num' => $rider->getContactNumber(), + 'image_url' => $image_url, + 'location' => [ + 'long' => $coord->getLongitude(), + 'lat' => $coord->getLatitude() + ] + ]; + } else { + $jo_data['rider'] = null; + } + + // invoice items + $items = []; + $jo_items = $jo->getInvoice()->getItems(); + foreach ($jo_items as $item) { + $items[] = [ + 'id' => $item->getID(), + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity(), + 'price' => $item->getPrice(), + ]; + } + + $jo_data['items'] = $items; + + + // dates depending on status + switch ($status) { + case JOStatus::FULFILLED: + if ($jo->getDateFulfill() == null) + $jo_data['date_fulfilled'] = ''; + else + $jo_data['date_fulfilled'] = $jo->getDateFulfill()->format('M d, Y'); + break; + case JOStatus::CANCELLED: + $date_cancel = $jo->getDateCancel(); + if ($date_cancel == null) + $date_cancel = new DateTime(); + $jo_data['date_cancelled'] = $date_cancel->format('M d, Y'); + break; + } + + return $jo_data; + } + + protected function generateAPIRiderStatus($status) + { + switch ($status) { + case JOStatus::PENDING: + return APIRiderStatus::OUTLET_ASSIGN; + case JOStatus::RIDER_ASSIGN: + return APIRiderStatus::RIDER_ASSIGN; + case JOStatus::ASSIGNED: + case JOStatus::IN_TRANSIT: + case JOStatus::IN_PROGRESS: + return APIRiderStatus::RIDER_PICK_UP; + } + return 'unknown'; + } + + protected function generateLatestJobOrderData($req, $jo, $rt) + { + $status = $jo->getStatus(); + + $dest = $jo->getCoordinates(); + + $jo_data = [ + 'id' => $jo->getID(), + 'date_create' => $jo->getDateCreate()->format('M d, Y'), + 'service_type' => $jo->getServiceType(), + 'destination' => [ + 'long' => $dest->getLongitude(), + 'lat' => $dest->getLatitude(), + ], + 'delivery_address' => $jo->getDeliveryAddress(), + 'delivery_instructions' => $jo->getDeliveryInstructions(), + 'jo_status' => $status, + 'status' => $this->generateAPIRiderStatus($status), + 'landmark' => $jo->getLandmark(), + ]; + + // customer vehicle and warranty + $cv = $jo->getCustomerVehicle(); + + // get latest warranty using plate number + $warranty = $this->findWarranty($cv->getPlateNumber()); + + $jo_data['customer_vehicle'] = [ + 'id' => $cv->getID(), + 'plate_number' => $cv->getPlateNumber(), + 'warranty' => $warranty, + ]; + + // customer information + $customer = $jo->getCustomer(); + $jo_data['customer'] = [ + 'first_name' => $customer->getFirstName(), + 'last_name' => $customer->getLastName(), + 'mobile_number' => $customer->getPhoneMobile(), + ]; + + // rider + $rider = $jo->getRider(); + if ($rider != null) { + // default image url + $url_prefix = $req->getSchemeAndHttpHost(); + $image_url = $url_prefix . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $url_prefix . '/uploads/' . $rider->getImageFile(); + + $coord = $rt->getRiderLocation($rider->getID()); + + $jo_data['rider'] = [ + 'id' => $rider->getID(), + 'name' => $rider->getFullName(), + 'plate_num' => $rider->getPlateNumber(), + 'contact_num' => $rider->getContactNumber(), + 'image_url' => $image_url, + 'location' => [ + 'long' => $coord->getLongitude(), + 'lat' => $coord->getLatitude() + ] + ]; + } else { + $jo_data['rider'] = null; + } + + // invoice items + $items = []; + $jo_items = $jo->getInvoice()->getItems(); + foreach ($jo_items as $item) { + $items[] = [ + 'id' => $item->getID(), + 'title' => $item->getTitle(), + 'qty' => $item->getQuantity(), + 'price' => $item->getPrice(), + ]; + } + + $jo_data['items'] = $items; + + + // dates depending on status + switch ($status) { + case JOStatus::FULFILLED: + if ($jo->getDateFulfill() == null) + $jo_data['date_fulfilled'] = ''; + else + $jo_data['date_fulfilled'] = $jo->getDateFulfill()->format('M d, Y'); + break; + case JOStatus::CANCELLED: + $date_cancel = $jo->getDateCancel(); + if ($date_cancel == null) + $date_cancel = new DateTime(); + $jo_data['date_cancelled'] = $date_cancel->format('M d, Y'); + break; + } + + return $jo_data; + } + + protected function findWarranty($plate_number) + { + // NOTE: Modify the search for the latest warranty. This seems hacky. + // get latest warranty using plate number + $warranty_results = $this->em->getRepository(Warranty::class)->findBy( + ['plate_number' => $plate_number], + ['date_create' => 'desc'] + ); + + $warr = []; + + // check if warranty_results is empty + if (empty($warranty_results)) { + /* + $res->setError(true) + ->setErrorMessage('No warranty found for plate number'); + return $res->getReturnResponse(); + */ + + return $warr; + } + + // get first entry + $warranty = current($warranty_results); + + // check for null values for battery and date claim and date expire + $batt_model = ''; + $batt_size = ''; + $sap_batt = ''; + $claim_date = ''; + $expiry_date = ''; + + if (!(is_null($warranty->getBatteryModel()))) { + $batt_model = $warranty->getBatteryModel()->getName(); + } + if (!(is_null($warranty->getBatterySize()))) { + $batt_size = $warranty->getBatterySize()->getName(); + } + if (!(is_null($warranty->getSAPBattery()))) { + $sap_batt = $warranty->getSAPBattery()->getID(); + } + if (!(is_null($warranty->getDateClaim()))) { + $claim_date = $warranty->getDateClaim()->format("d M Y"); + } + if (!(is_null($warranty->getDateExpire()))) { + $expiry_date = $warranty->getDateExpire()->format("d M Y"); + } + + $warr[] = [ + 'id' => $warranty->getID(), + 'serial' => $warranty->getSerial(), + 'warranty_class' => $warranty->getWarrantyClass(), + 'plate_number' => $warranty->getPlateNumber(), + 'first_name' => $warranty->getFirstName(), + 'last_name' => $warranty->getLastName(), + 'mobile_number' => $warranty->getMobileNumber(), + 'battery_model' => $batt_model, + 'battery_size' => $batt_size, + 'sap_battery' => $sap_batt, + 'status' => $warranty->getStatus(), + 'date_create' => $warranty->getDateCreate()->format("d M Y g:i A"), + 'date_purchase' => $warranty->getDatePurchase()->format("d M Y"), + 'date_expire' => $expiry_date, + 'date_claim' => $claim_date, + 'claim_from' => $warranty->getClaimedFrom(), + 'is_activated' => $warranty->isActivated() ? 1 : 0, + ]; + + return $warr; + } + + protected function getTimeFromSlot($slot_id) + { + $time_selected = ''; + + switch ($slot_id) { + case '08_09': + $time_selected = AdvanceOrderSlot::_08_09; + break; + case '09_10': + $time_selected = AdvanceOrderSlot::_09_10; + break; + case '10_11': + $time_selected = AdvanceOrderSlot::_10_11; + break; + case '11_12': + $time_selected = AdvanceOrderSlot::_11_12; + break; + case '12_13': + $time_selected = AdvanceOrderSlot::_12_13; + break; + case '13_14': + $time_selected = AdvanceOrderSlot::_13_14; + break; + case '14_15': + $time_selected = AdvanceOrderSlot::_14_15; + break; + case '15_16': + $time_selected = AdvanceOrderSlot::_15_16; + break; + case '16_17': + $time_selected = AdvanceOrderSlot::_16_17; + break; + default: + error_log('Invalid slot id ' . $slot_id); + } + + return $time_selected; + } + + protected function randomizeRider($riders) + { + // TODO: get redis to track the sales per rider per day + // check the time they came in + // for now, randomize the rider + $selected_index = array_rand($riders); + + $selected_rider = $riders[$selected_index]; + + return $selected_rider; + } +} diff --git a/src/Controller/CustomerAppAPI/LocationController.php b/src/Controller/CustomerAppAPI/LocationController.php new file mode 100644 index 00000000..161583e2 --- /dev/null +++ b/src/Controller/CustomerAppAPI/LocationController.php @@ -0,0 +1,590 @@ +validateRequest($req, [ + 'longitude', + 'latitude', + ]); + + $long = $req->query->get('longitude'); + $lat = $req->query->get('latitude'); + + // NOTE: had to add this for promo tag + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + $is_covered = false; + // check if customer still has promo + if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) || + ($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')) + ) { + // if has customer tag, customer has not availed of promo + $is_covered = true; + } else { + // geofence + $is_covered = $geo->isCovered($long, $lat); + } + + // geofence + // $is_covered = $geo->isCovered($long, $lat); + + $data = [ + 'longitude' => $long, + 'latitude' => $lat, + 'supported' => $is_covered, + ]; + + // check if is_covered is false. If so, we need to set the error part in the response + if (!$is_covered) { + return new ApiResponse(false, $this->getGeoErrorMessage(), $data); + } + + // response + return new ApiResponse(true, '', $data); + } + + public function getNearestHubAndSlots( + Request $req, + MapTools $map_tools + ) { + // validate params + $this->validateRequest($req, [ + 'longitude', + 'latitude', + ]); + + $coordinates = new Point($req->query->get('longitude'), $req->query->get('latitude')); + + // add checking if customer has a pre-registered hub + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // check if customer has customer tag promo + if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) || + ($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')) + ) { + // if has customer tag, customer has not availed of promo, get the hub where customer is pre-registered + $car_club_cust_hub = $cust->getCarClubCustomerHub(); + if ($car_club_cust_hub != null) { + // need to get the rider slots for the pre-registered hub + $hub = $car_club_cust_hub->getHub(); + $nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $map_tools, $hub); + } else { + $nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $map_tools); + + if (empty($nearest_hub_slots['hub'])) { + return new ApiResponse(false, 'Thank you for reaching out to us. Please expect a call from us and we will assist you with your request. Thank you and stay safe!'); + } + } + } else { + $nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $map_tools); + + if (empty($nearest_hub_slots['hub'])) { + return new ApiResponse(false, 'Thank you for reaching out to us. Please expect a call from us and we will assist you with your request. Thank you and stay safe!'); + } + } + + // response + return new ApiResponse(true, '', [ + 'hub_id' => $nearest_hub_slots['hub']->getID(), + 'hub_slots' => $nearest_hub_slots['slots'], + ]); + } + + public function addLocation(Request $req) + { + // validate params + $this->validateRequest($req, [ + 'name', + 'address', + 'longitude', + 'latitude', + 'landmark', + ]); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get the information + $name = $req->request->get('name'); + $address = $req->request->get('address'); + $lng = $req->request->get('longitude'); + $lat = $req->request->get('latitude'); + $landmark = $req->request->get('landmark'); + + $loc_info = [ + 'address' => $address, + 'longitude' => $lng, + 'latitude' => $lat, + 'landmark' => $landmark, + ]; + + // check if customer already has existing metadata + $c_meta = $this->em->getRepository(CustomerMetadata::class)->findOneBy(['customer' => $cust]); + if ($c_meta == null) { + // create new customer meta + $cust_meta = new CustomerMetadata(); + $cust_meta->setCustomer($cust); + $cust_meta->addMetaInfo($name, $loc_info); + + $this->em->persist($cust_meta); + } else { + // limit locations to 6. If more than 6, pop the first one out + // add location to existing customer meta + $meta_count = count($c_meta->getAllMetaInfo()); + + if ($meta_count >= 6) + $c_meta->popMetaInfo(); + + $c_meta->addMetaInfo($name, $loc_info); + } + + $this->em->flush(); + + // response + return new ApiResponse(); + } + + public function getLocations(Request $req) + { + // validate params + $this->validateRequest($req); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get the customer meta for customer + $locations = []; + $cust_meta = $this->em->getRepository(CustomerMetadata::class)->findOneBy(['customer' => $cust]); + if ($cust_meta != null) { + $locations[] = $cust_meta->getAllMetaInfo(); + } + + $data = [ + 'locations' => $locations, + ]; + + // response + return new ApiResponse(); + } + + protected function findAdvanceNearestHubAndSlots(Point $coordinates, MapTools $map_tools, $hub = null) + { + $hub_data = []; + + if ($hub != null) { + // get the slots of hub + $hub_slots = $this->getHubRiderSlots($hub); + + $slots = $hub_slots['slot_data']; + + $hub_data = [ + 'hub' => $hub, + 'slots' => $slots, + ]; + return $hub_data; + } + + // get the nearest 10 hubs + $nearest_hubs_with_distance = []; + $hubs = $map_tools->getClosestOpenHubs($coordinates, 10); + + foreach ($hubs as $hub) { + $nearest_hubs_with_distance[] = $hub; + // TODO: insert checking for branch code here when inventory manager is up + } + + $nearest = null; + $hub_slots = []; + $slot_found = false; + // find the nearest hub + if (!empty($nearest_hubs_with_distance)) { + // get slots of nearest hub right after getting nearest hub. + // then check if hub has available slots. If not, get next nearest hub. + foreach ($nearest_hubs_with_distance as $nhd) { + if (empty($nearest)) { + // get the slots for the hub to check if hub is available for assignment + $hub_slots = $this->getHubRiderSlots($nhd['hub']); + + $flag_hub_available = $hub_slots['flag_hub_available']; + if ($flag_hub_available == true) { + $nearest = $nhd; + } + } else { + if ($nhd['distance'] < $nearest['distance']) { + // get the slots for nearest which is nhd right now + $hub_slots = $this->getHubRiderSlots($nhd['hub']); + + $flag_hub_available = $hub_slots['flag_hub_available']; + + // if hub is available, set hub to nearest + if ($flag_hub_available == true) { + $nearest = $nhd; + } + } + } + } + } + + if ($nearest != null) { + // set hub data to what is in nearest + $hub_data = [ + 'hub' => $nearest['hub'], + 'slots' => $hub_slots['slot_data'], + ]; + } + + return $hub_data; + } + + protected function getHubRiderSlots(Hub $hub) + { + // check hub's advance orders for the day + + /* + // get number of advance orders for the next day if request came in before midnight + // or for current day if request came in after midnight + // check request_time + $request_time = time(); + $midnight = strtotime('00:00'); + */ + $start_date = new DateTime(); + $end_date = new DateTime(); + + // to keep things simple, just start on next day regardless of midnight timer + $start_date->add(new DateInterval('P1D')); + $end_date->add(new DateInterval('P3D')); + + /* + if ($request_time < $midnight) + { + // add +1 to start date to get the next day + // add +3 to date to end date to get the advance orders for the next three days + $start_date->add(new DateInterval('P1D')); + $end_date->add(new DateInterval('P1D')); + } + $end_date->add(new DateInterval('P2D')); + */ + + // set time bounds for the start and end date + $start_date->setTime(0, 1); + $end_date->setTime(23, 59); + + // NOTE: get advance orders via query + // get JOs assigned to hub that are advance orders and scheduled for the next three days with + // for hub assignment status + $query = $this->em->createQuery('select jo from App\Entity\JobOrder jo where jo.hub = :hub and jo.flag_advance = true and + jo.date_schedule >= :date_start and jo.date_schedule <= :date_end and jo.status != :status_cancelled + and jo.status != :status_fulfilled'); + $jos_advance_orders = $query->setParameters([ + 'hub' => $hub, + 'date_start' => $start_date, + 'date_end' => $end_date, + 'status_cancelled' => JOStatus::CANCELLED, + 'status_fulfilled' => JOStatus::FULFILLED, + ]) + ->getResult(); + // check request_time + + // define slots + $slots = [ + '08_09' => '8:00 AM', + '09_10' => '9:00 AM', + '10_11' => '10:00 AM', + '11_12' => '11:00 AM', + '12_13' => '12:00 PM', + '13_14' => '1:00 PM', + '14_15' => '2:00 PM', + '15_16' => '3:00 PM', + '16_17' => '4:00 PM', + ]; + + + // get the dates for the next three days + $first_date = $start_date->format('Y-m-d'); + $second_date = $start_date->add(new DateInterval('P1D')); + $sec_date = $second_date->format('Y-m-d'); + $third_date = $end_date->format('Y-m-d'); + + // define days + $days = [ + $first_date => $first_date, + $sec_date => $sec_date, + $third_date => $third_date, + ]; + + // initialize hub rider slots + $hub_rider_slots = []; + foreach ($days as $day) { + foreach ($slots as $slot_key => $slot) { + $hub_rider_slots[$day][$slot_key] = $hub->getRiderSlots(); + } + } + + // check each JO's date_schedule, decrement rider_slots if date schedule falls in that slot + foreach ($jos_advance_orders as $jo) { + // get date key + $date_sched = $jo->getDateSchedule(); + $date_string = $date_sched->format('Y-m-d'); + $hour = $date_sched->format('H'); + $slot_id = sprintf('%02d_%02d', $hour, $hour + 1); + + // error_log("SLOT - $date_string - $slot_id"); + + // decrement rider slot + if (isset($hub_rider_slots[$date_string][$slot_id])) + $hub_rider_slots[$date_string][$slot_id]--; + + // check if it goes through next slot (10 min allowance) + $mins = $date_sched->format('i'); + if ($mins > 10) { + $next_slot_id = sprintf('%02d_%02d', $hour + 1, $hour + 2); + // error_log("NEXT SLOT - $date_string - $next_slot_id"); + // decrement rider slot + if (isset($hub_rider_slots[$date_string][$next_slot_id])) + $hub_rider_slots[$date_string][$next_slot_id]--; + } + } + + // error_log(print_r($hub_rider_slots, true)); + + $hub_slots = $this->generateHubSlots($hub_rider_slots, $slots); + + // error_log(print_r($hub_slots, true)); + + return $hub_slots; + } + + protected function generateHubSlots($rider_slots, $slots) + { + $data = []; + $total_rslots = 0; + $total_unavailable_rslots = 0; + foreach ($rider_slots as $day_id => $rslot) { + $data[$day_id] = []; + + foreach ($rslot as $slot_id => $avail_slots) { + // increment total rider slots + $total_rslots++; + + $slot_data = [ + 'id' => $slot_id, + 'label' => $slots[$slot_id], + 'available' => true, + ]; + + // mark unavailable ones + if ($avail_slots <= 0) { // increment total number of unavailable slots + $total_unavailable_rslots++; + $slot_data['available'] = false; + } + + // add to day data + $data[$day_id][] = $slot_data; + } + } + + // check if hub has available slots + $hub_available = true; + // error_log('total rider slots ' . $total_rslots); + // error_log('total unavailable slots ' . $total_unavailable_rslots); + if ($total_rslots == $total_unavailable_rslots) { + // error_log('hub has no available slots'); + $hub_available = false; + } + + $hs_data = [ + 'flag_hub_available' => $hub_available, + 'slot_data' => $data, + ]; + + return $hs_data; + } + + protected function findNearestHub($jo, MapTools $map_tools) + { + // get the nearest 10 hubs + $selected_hub = null; + $hubs = $map_tools->getClosestOpenHubs($jo->getCoordinates(), 10, date("H:i:s")); + + $nearest_hubs_with_distance = []; + $nearest_branch_codes = []; + foreach ($hubs as $hub) { + $nearest_hubs_with_distance[] = $hub; + //if (!empty($hub['hub']->getBranchCode())) + // $nearest_branch_codes[] = $hub['hub']->getBranchCode(); + } + + // check if nearest hubs have branch codes + //if (count($nearest_branch_codes) == 0) + // return $selected_hub; + + // assume all 10 have stock + // find the nearest hub with available riders + $nearest = null; + foreach ($nearest_hubs_with_distance as $nhd) { + // get number of available riders + $count_riders = count($nhd['hub']->getAvailableRiders()); + + // get number of advance orders in the next 3 hours + $time_now = new DateTime(); + $date_end = new DateTime(); + $date_end->add(new DateInterval('PT2H')); + + // NOTE: get advance orders via query + // get JOs assigned to hub that are advance orders and scheduled within X hours with + // for rider assignment status + $query = $this->em->createQuery('select count(jo) from App\Entity\JobOrder jo where jo.hub = :hub and jo.flag_advance = true and jo.date_schedule <= :date_end and jo.status = :status'); + $count_advance_orders = $query->setParameters([ + 'hub' => $nhd['hub'], + 'date_end' => $date_end, + 'status' => JOStatus::RIDER_ASSIGN, + ]) + ->setMaxResults(1) + ->getSingleScalarResult(); + + // error_log('HUB - ' . $nhd['hub']->getID()); + // error_log('RIDER COUNT - ' . $count_riders); + // error_log('ADVANCE ORDER COUNT - ' . $count_advance_orders); + + // if (count($nhd['hub']->getAvailableRiders()) > 0) + // if we have more riders than we have advance orders + if ($count_riders - $count_advance_orders > 0) { + if (empty($nearest)) + $nearest = $nhd; + else { + if ($nhd['distance'] < $nearest['distance']) + $nearest = $nhd; + } + } + } + + $selected_hub = $nearest['hub']; + + return $selected_hub; + } + + protected function findNearestHubWithInventory( + $jo, + Battery $batt, + MapTools $map_tools, + InventoryManager $im + ) { + // get the nearest 10 hubs + $selected_hub = null; + $hubs = $map_tools->getClosestOpenHubs($jo->getCoordinates(), 10, date("H:i:s")); + + $nearest_hubs_with_distance = []; + $nearest_branch_codes = []; + foreach ($hubs as $hub) { + $nearest_hubs_with_distance[] = $hub; + //if (!empty($hub['hub']->getBranchCode())) + // $nearest_branch_codes[] = $hub['hub']->getBranchCode(); + } + + // check if nearest hubs have branch codes + //if (count($nearest_branch_codes) == 0) + // return $selected_hub; + + // assume all 10 have stock + // find the nearest hub with available riders + $nearest = null; + foreach ($nearest_hubs_with_distance as $nhd) { + if (count($nhd['hub']->getAvailableRiders()) > 0) { + if (empty($nearest)) + $nearest = $nhd; + else { + if ($nhd['distance'] < $nearest['distance']) + $nearest = $nhd; + } + } + } + + $selected_hub = $nearest['hub']; + + // uncomment this snippet when inventory check becomes active + // get battery sku + /* + if ($batt != null) + { + $skus[] = $batt->getSAPCode(); + + // api call to check inventory + // pass the list of branch codes of nearest hubs and the skus + // go through returned list of branch codes + // bypass inventory check for now + // $hubs_with_inventory = $im->getBranchesInventory($nearest_branch_codes, $skus); + if (!empty($hubs_with_inventory)) + { + $nearest = []; + $flag_hub_found = false; + foreach ($hubs_with_inventory as $hub_with_inventory) + { + // find hub according to branch code + $found_hub = $this->em->getRepository(Hub::class)->findOneBy(['branch_code' => $hub_with_inventory['BranchCode']]); + if ($found_hub != null) + { + // check rider availability + if (count($found_hub->getAvailableRiders()) > 0) + { + // check against nearest hubs with distance + foreach ($nearest_hubs_with_distance as $nhd) + { + // get distance of hub from location, compare with $nearest. if less, replace nearest + if ($found_hub->getID() == $nhd['hub']->getID()) + { + if (empty($nearest)) + { + $nearest = $nhd; + $flag_hub_found = true; + } + else + { + if ($nhd['distance'] < $nearest['distance']) + { + $nearest = $nhd; + $flag_hub_found = true; + } + } + } + } + } + } + } + $selected_hub = $nearest['hub']; + } + } */ + return $selected_hub; + } +} diff --git a/src/Controller/CustomerAppAPI/PartnerController.php b/src/Controller/CustomerAppAPI/PartnerController.php new file mode 100644 index 00000000..c1cef726 --- /dev/null +++ b/src/Controller/CustomerAppAPI/PartnerController.php @@ -0,0 +1,152 @@ +validateRequest($req); + + // get partner + $partner = $this->em->getRepository(Partner::class)->findOneBy(['id' => $pid]); + if ($partner == null) { + return new ApiResponse(false, 'No partner found.'); + } + + // get reviews for partner + $reviews = $this->em->getRepository(Review::class)->findBy(['partner' => $partner]); + + // get average rating for all reviews + $average_rating = 0; + if (!empty($reviews)) { + $rating = 0; + foreach ($reviews as $review) { + $rating = $rating + $review->getRating(); + } + + $average_rating = $rating / sizeof($reviews); + } + + $data = []; + $data['partner'] = [ + 'id' => $partner->getID(), + 'name' => $partner->getName(), + 'branch' => $partner->getBranch(), + 'address' => $partner->getAddress(), + 'contact_nums' => $partner->getContactNumbers(), + 'time_open' => $partner->getTimeOpen()->format("g:i A"), + 'time_close' => $partner->getTimeClose()->format("g:i A"), + 'longitude' => $partner->getCoordinates()->getLongitude(), + 'latitude' => $partner->getCoordinates()->getLatitude(), + 'average_rating' => $average_rating, + ]; + + // response + return new ApiResponse(true, '', $data); + } + + public function getClosestPartners(Request $req) + { + // validate params + $this->validateRequest($req, [ + 'longitude', + 'latitude', + 'service_id', + 'limit', + ]); + + $long = $req->query->get('longitude'); + $lat = $req->query->get('latitude'); + $service_id = $req->query->get('service_id'); + $limit = $req->query->get('limit'); + + // get partners within range + $query = $this->em->createQuery('SELECT p, st_distance(p.coordinates, point(:lng, :lat)) as dist FROM App\Entity\Partner p + JOIN App\Entity\Service s where s.id = :service_id ORDER BY dist') + ->setParameter('lat', $lat) + ->setParameter('lng', $long) + ->setParameter('service_id', $service_id); + + $query->setMaxResults($limit); + $result = $query->getResult(); + + $data = []; + $partners = []; + foreach ($result as $row) { + // get all the reviews for each partner and average the ratings + $partner_id = $row[0]->getID(); + $partner = $this->em->getRepository(Partner::class)->find($partner_id); + $partner_reviews = $this->em->getRepository(Review::class)->findBy(['partner' => $partner]); + + $average_rating = 0; + if (count($partner_reviews) > 0) { + $rating = 0; + foreach ($partner_reviews as $review) { + $rating = $rating + $review->getRating(); + } + + $average_rating = $rating / sizeof($partner_reviews); + } + + $partners[] = [ + 'id' => $row[0]->getID(), + 'name' => $row[0]->getName(), + 'branch' => $row[0]->getBranch(), + 'address' => $row[0]->getAddress(), + 'contact_nums' => $row[0]->getContactNumbers(), + 'time_open' => $row[0]->getTimeOpen()->format("g:i A"), + 'time_close' => $row[0]->getTimeClose()->format("g:i A"), + 'longitude' => $row[0]->getCoordinates()->getLongitude(), + 'latitude' => $row[0]->getCoordinates()->getLatitude(), + 'db_distance' => $row['dist'], + 'rating' => $average_rating, + ]; + } + + $data['partners'] = $partners; + + // response + return new ApiResponse(true, '', $data); + } + + public function reviewPartner($pid, Request $req) + { + // validate params + $this->validateRequest($req, [ + 'rating', + 'message', + ]); + + $rating = $req->request->get('rating'); + $msg = $req->request->get('message'); + + // TODO: check rating if 1 - 5 + + // check if partner exists + $partner = $this->em->getRepository(Partner::class)->find($pid); + if ($partner == null) { + return new ApiResponse(false, 'No partner found.'); + } + + $rev = new Review(); + $rev->setRating($rating) + ->setMessage($msg) + ->setPartner($partner) + ->setMobileSession($this->session); + + // save to db + $this->em->persist($rev); + $this->em->flush(); + + // response + return new ApiResponse(); + } +} diff --git a/src/Controller/CustomerAppAPI/PrivacyController.php b/src/Controller/CustomerAppAPI/PrivacyController.php new file mode 100644 index 00000000..a57e8e50 --- /dev/null +++ b/src/Controller/CustomerAppAPI/PrivacyController.php @@ -0,0 +1,63 @@ +validateRequest($req, [ + 'priv_third_party', + // 'priv_promo', + ]); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // set privacy settings + $priv_promo = $req->request->get('priv_promo', false); + $priv_third_party = $req->request->get('priv_third_party'); + $cust->setPrivacyThirdParty($priv_third_party) + ->setPrivacyPromo($priv_promo); + + // get the policy ids from .env + $policy_promo_id = $_ENV['POLICY_PROMO']; + $policy_third_party_id = $_ENV['POLICY_THIRD_PARTY']; + + // check if privacy settings are true + // if true, set the private policy for the customer + if ($priv_promo) { + // find the promo policy + $policy = $this->em->getRepository(PrivacyPolicy::class)->find($policy_promo_id); + + // set policy id + if ($policy != null) { + $cust->setPrivacyPolicyPromo($policy); + } + } + + if ($priv_third_party) { + // find the third party policy + $policy = $this->em->getRepository(PrivacyPolicy::class)->find($policy_third_party_id); + + // set policy id + if ($policy != null) { + $cust->setPrivacyPolicyThirdParty($policy); + } + } + + $this->em->flush(); + + // response + return new ApiResponse(); + } +} diff --git a/src/Controller/CustomerAppAPI/PromoController.php b/src/Controller/CustomerAppAPI/PromoController.php new file mode 100644 index 00000000..accf2cac --- /dev/null +++ b/src/Controller/CustomerAppAPI/PromoController.php @@ -0,0 +1,18 @@ +validateRequest($req); + + // response + return new ApiResponse(); + } +} diff --git a/src/Controller/CustomerAppAPI/RiderController.php b/src/Controller/CustomerAppAPI/RiderController.php new file mode 100644 index 00000000..5f976083 --- /dev/null +++ b/src/Controller/CustomerAppAPI/RiderController.php @@ -0,0 +1,229 @@ +validateRequest($req); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // check if we have an ongoing job order + /* + $ongoing_jos = $em->getRepository(JobOrder::class)->findBy([ + 'customer' => $cust, + 'status' => [JOStatus::PENDING, JOStatus::RIDER_ASSIGN, JOStatus::IN_TRANSIT, JOStatus::ASSIGNED, JOStatus::IN_PROGRESS], + ]); + */ + $ongoing_jos = $this->getOngoingJobOrders($cust); + + // $res->setData(['here' => count($ongoing_jos)]); + // return $res->getReturnResponse(); + if (count($ongoing_jos) <= 0) { + try { + // check if the latest fulfilled jo they have needs rider rating + $query = $this->em->createQuery('select jo from App\Entity\JobOrder jo where jo.customer = :cust and jo.status = :status order by jo.date_fulfill desc'); + $fulfill_jo = $query->setParameters([ + 'cust' => $cust, + 'status' => JOStatus::FULFILLED, + ]) + ->setMaxResults(1) + ->getSingleResult(); + } catch (Exception $e) { + // response + return new ApiResponse(true, '', [ + 'status' => APIRiderStatus::NO_PENDING_JO, + ]); + } + + // we got a recently fulfilled job order + if ($fulfill_jo) { + // check if the rider has been rated + if (!$fulfill_jo->hasRiderRating()) { + $dest = $fulfill_jo->getCoordinates(); + + $data = [ + 'jo_id' => $fulfill_jo->getID(), + 'service_type' => $fulfill_jo->getServiceType(), + 'destination' => [ + 'long' => $dest->getLongitude(), + 'lat' => $dest->getLatitude(), + ], + 'delivery_address' => $fulfill_jo->getDeliveryAddress(), + 'delivery_instructions' => $fulfill_jo->getDeliveryInstructions(), + ]; + + $rider = $fulfill_jo->getRider(); + + // default image url + $url_prefix = $req->getSchemeAndHttpHost(); + $image_url = $url_prefix . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $url_prefix . '/uploads/' . $rider->getImageFile(); + + $data['status'] = APIRiderStatus::RIDER_RATING; + // default rider location to hub + $data['rider'] = [ + 'id' => $rider->getID(), + 'name' => $rider->getFullName(), + 'plate_num' => $rider->getPlateNumber(), + 'contact_num' => $rider->getContactNumber(), + 'image_url' => $image_url, + ]; + + // response + return new ApiResponse(true, '', $data); + } + } + + // response, no pending + return new ApiResponse(true, '', [ + 'status' => APIRiderStatus::NO_PENDING_JO, + ]); + } + + // get first jo that's pending + $jo = $ongoing_jos[0]; + $dest = $jo->getCoordinates(); + + $data = [ + 'jo_id' => $jo->getID(), + 'service_type' => $jo->getServiceType(), + 'destination' => [ + 'long' => $dest->getLongitude(), + 'lat' => $dest->getLatitude(), + ], + 'delivery_address' => $jo->getDeliveryAddress(), + 'delivery_instructions' => $jo->getDeliveryInstructions(), + ]; + + switch ($jo->getStatus()) { + case JOStatus::PENDING: + $data['status'] = APIRiderStatus::OUTLET_ASSIGN; + break; + case JOStatus::RIDER_ASSIGN: + $data['status'] = APIRiderStatus::RIDER_ASSIGN; + break; + case JOStatus::ASSIGNED: + case JOStatus::IN_TRANSIT: + case JOStatus::IN_PROGRESS: + $rider = $jo->getRider(); + // get rider coordinates from redis + $coord = $rt->getRiderLocation($rider->getID()); + + // default image url + $url_prefix = $req->getSchemeAndHttpHost(); + $image_url = $url_prefix . '/assets/images/user.gif'; + if ($rider->getImageFile() != null) + $image_url = $url_prefix . '/uploads/' . $rider->getImageFile(); + + $data['status'] = APIRiderStatus::RIDER_PICK_UP; + // TODO: fix this to actual location of rider + // default rider location to hub + $data['rider'] = [ + 'id' => $rider->getID(), + 'name' => $rider->getFullName(), + 'plate_num' => $rider->getPlateNumber(), + 'contact_num' => $rider->getContactNumber(), + 'image_url' => $image_url, + 'location' => [ + 'long' => $coord->getLongitude(), + 'lat' => $coord->getLatitude() + ] + ]; + + break; + } + + // response + return new ApiResponse(true, '', $data); + } + + public function addRiderRating(Request $req) + { + // validate params + $this->validateRequest($req, [ + 'jo_id', + 'rating', + ]); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get job order + $jo_id = $req->request->get('jo_id'); + $jo = $this->em->getRepository(JobOrder::class)->find($jo_id); + if ($jo == null) { + return new ApiResponse(false, 'No job order found.'); + } + + // get rider + $rider = $jo->getRider(); + if ($rider == null) { + return new ApiResponse(false, 'No rider found.'); + } + + // check that the customer owns the job order + $jo_cust = $jo->getCustomer(); + if ($jo_cust->getID() != $cust->getID()) { + return new ApiResponse(false, 'Job order was not initiated by customer.'); + } + + // TODO: check job order status, if it's complete + + // add rider rating + $rating_num = $req->request->get('rating', -1); + + // if rating is -1 + if ($rating_num == -1) { + $jo->setHasRiderRating(); + $this->em->flush(); + + // response + return new ApiResponse(); + } + + + $rating = new RiderRating(); + $rating->setRider($rider) + ->setCustomer($cust) + ->setJobOrder($jo) + ->setRating($rating_num); + + // rider rating comment + $comment = $req->request->get('comment'); + if (!empty($comment)) + $rating->setComment($comment); + + // mark jo as rider rated already + $jo->setHasRiderRating(); + + $this->em->persist($rating); + $this->em->flush(); + + // TODO: set average rating in rider entity + + // response + return new ApiResponse(); + } +} diff --git a/src/Controller/CustomerAppAPI/ScheduleController.php b/src/Controller/CustomerAppAPI/ScheduleController.php new file mode 100644 index 00000000..57faaa8e --- /dev/null +++ b/src/Controller/CustomerAppAPI/ScheduleController.php @@ -0,0 +1,57 @@ +validateRequest($req); + + $schedule_choice = true; + + // remove the time check after ECQ. This will then always return true + // get current time + $current_datetime = new DateTime(); + //$current_datetime = DateTime::createFromFormat('Y-m-d H:i', '2020-04-30 17:01'); + + // get the hour + $hour = $current_datetime->format('G'); + + // commenting out the time check since we can now book 24/7 + // this will get uncommented out if and when ECQ will kick in + //if (($hour < 8) || ($hour > 16)) + // $schedule_choice = false; + + // add checking if customer has a pre-registered hub + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + // check if customer has customer tag promo + if (($cust->getCustomerTag('TAG_CAR_CLUB_OFFICER_PROMO')) || + ($cust->getCustomerTag('TAG_CAR_CLUB_MEMBER_PROMO')) + ) { + // if has customer tag, customer has not availed of promo, get the hub where customer is pre-registered + $car_club_hub = $cust->getCarClubCustomerHub(); + if ($car_club_hub != null) { + $schedule_choice = false; + } + } + + // schedule_choice will always be true aka customer can opt to + // Book Now or Schedule Order EXCEPT if customer has customer tag promo + // or ECQ comes back + + // response + return new ApiResponse(true, '', [ + 'display_schedule_choice' => $schedule_choice, + ]); + } +} diff --git a/src/Controller/CustomerAppAPI/ServiceController.php b/src/Controller/CustomerAppAPI/ServiceController.php new file mode 100644 index 00000000..a134194b --- /dev/null +++ b/src/Controller/CustomerAppAPI/ServiceController.php @@ -0,0 +1,53 @@ +validateRequest($req); + + // services + $results = $this->em->getRepository(Service::class)->findAll(); + if (empty($results)) { + return new ApiResponse(false, 'No services available.'); + } + + $services = []; + foreach ($results as $result) { + /* + // get partners + $partners = []; + $service_partners = $result->getPartners(); + foreach($service_partners as $sp) + { + $partners[] = [ + 'id' => $sp->getID(), + 'name' => $sp->getName(), + 'branch' => $sp->getBranch(), + 'address' => $sp->getAddress(), + 'contact_nums' => $sp->getContactNumbers(), + 'time_open' => $sp->getTimeOpen()->format("g:i A"), + 'time_close' => $sp->getTimeClose()->format("g:i A"), + ]; + } + */ + + $services[] = [ + 'id' => $result->getID(), + 'name' => $result->getName(), + // 'partners' => $partners, + ]; + } + + // response + return new ApiResponse(true, '', [ + 'services' => $services, + ]); + } +} diff --git a/src/Controller/CustomerAppAPI/VehicleController.php b/src/Controller/CustomerAppAPI/VehicleController.php new file mode 100644 index 00000000..a48f046a --- /dev/null +++ b/src/Controller/CustomerAppAPI/VehicleController.php @@ -0,0 +1,323 @@ +validateRequest($req); + + // get manufacturer list + $mfgs = $this->em->getRepository(VehicleManufacturer::class)->findBy(['flag_mobile' => true], ['name' => 'asc']); + $mfg_list = []; + + foreach ($mfgs as $mfg) { + $mfg_list[] = [ + 'id' => $mfg->getID(), + 'name' => $mfg->getName(), + ]; + } + + return new ApiResponse(true, '', [ + 'manufacturers' => $mfg_list, + ]); + } + + public function listVehicleMakes(Request $req, $mfg_id) + { + // validate params + $this->validateRequest($req); + + // get manufacturer + $mfg = $this->em->getRepository(VehicleManufacturer::class)->find($mfg_id); + if ($mfg == null) { + // response + return new ApiResponse(false, 'Invalid vehicle manufacturer id.'); + } + + // get makes + $vehicles = $this->em->getRepository(Vehicle::class)->findBy( + [ + 'flag_mobile' => true, + 'manufacturer' => $mfg_id, + ], + ['make' => 'asc'] + ); + // $vehicles = $mfg->getVehicles(); + $vlist = []; + foreach ($vehicles as $v) { + $vlist[] = [ + 'id' => $v->getID(), + 'make' => trim($v->getMake() . ' ' . $v->getModelYearFormatted(false)), + // 'make' => $v->getMake() . ' ' . $v->getModelYearFrom() . '-' . $v->getModelYearTo(), + ]; + } + + // response + return new ApiResponse(true, '', [ + 'manufacturer' => [ + 'id' => $mfg->getID(), + 'name' => $mfg->getName(), + ], + 'makes' => $vlist, + ]); + } + + public function addVehicle(Request $req) + { + // check requirements + $this->checkVehicleRequirements($req); + + // customer vehicle + $cv = new CustomerVehicle(); + + // set object and return + $this->setCustomerVehicleObject($req, $cv); + } + + public function updateVehicle(Request $req, $id) + { + // check requirements + $this->checkVehicleRequirements($req); + + // get customer vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->find($id); + + // check if it exists + if ($cv == null) { + return new ApiResponse(false, 'Vehicle does not exist.'); + } + + // check if it's owned by customer + if ($cv->getCustomer()->getID() != $this->session->getCustomer()->getID()) { + return new ApiResponse(false, 'Invalid vehicle.'); + } + + // set object and return + $this->setCustomerVehicleObject($req, $cv); + } + + public function listVehicles(Request $req) + { + // validate params + $this->validateRequest($req); + + // customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // vehicles + $cv_list = []; + // $cvs = $cust->getVehicles(); + // only get the customer's vehicles whose flag_active is true + $cvs = $this->em->getRepository(CustomerVehicle::class)->findBy(['flag_active' => true, 'customer' => $cust]); + foreach ($cvs as $cv) { + $battery_id = null; + if ($cv->getCurrentBattery() != null) + $battery_id = $cv->getCurrentBattery()->getID(); + + $wty_ex = null; + if ($cv->getWarrantyExpiration() != null) + $wty_ex = $cv->getWarrantyExpiration()->format('Y-m-d'); + + $warranty = $this->findWarranty($cv->getPlateNumber()); + + $cv_name = ''; + if ($cv->getName() != null) + $cv_name = $cv->getName(); + + $cv_list[] = [ + 'cv_id' => $cv->getID(), + 'mfg_id' => $cv->getVehicle()->getManufacturer()->getID(), + 'make_id' => $cv->getVehicle()->getID(), + 'name' => $cv_name, + 'plate_num' => $cv->getPlateNumber(), + 'model_year' => $cv->getModelYear(), + 'color' => $cv->getColor(), + 'condition' => $cv->getStatusCondition(), + 'fuel_type' => $cv->getFuelType(), + 'wty_code' => $cv->getWarrantyCode(), + 'wty_expire' => $wty_ex, + 'curr_batt_id' => $battery_id, + 'is_motolite' => $cv->hasMotoliteBattery() ? 1 : 0, + 'is_active' => $cv->isActive() ? 1 : 0, + 'warranty' => $warranty, + ]; + } + + // response + return new ApiResponse(true, '', [ + 'vehicles' => $cv_list, + ]); + } + + public function getCompatibleBatteries(Request $req, $vid) + { + // validate params + $this->validateRequest($req); + + // get vehicle + $vehicle = $this->em->getRepository(Vehicle::class)->find($vid); + if ($vehicle == null) { + return new ApiResponse(false, 'Invalid vehicle.'); + } + + // batteries + $batt_list = []; + $batts = $vehicle->getBatteries(); + foreach ($batts as $batt) { + // TODO: Add warranty_tnv to battery information + $batt_list[] = [ + 'id' => $batt->getID(), + 'mfg_id' => $batt->getManufacturer()->getID(), + 'mfg_name' => $batt->getManufacturer()->getName(), + 'model_id' => $batt->getModel()->getID(), + 'model_name' => $batt->getModel()->getName(), + 'size_id' => $batt->getSize()->getID(), + 'size_name' => $batt->getSize()->getName(), + 'price' => $batt->getSellingPrice(), + 'wty_private' => $batt->getWarrantyPrivate(), + 'wty_commercial' => $batt->getWarrantyCommercial(), + 'image_url' => $this->getBatteryImageURL($req, $batt), + ]; + } + + // response + return new ApiResponse(true, '', [ + 'vehicle' => [ + 'id' => $vehicle->getID(), + 'mfg_id' => $vehicle->getManufacturer()->getID(), + 'mfg_name' => $vehicle->getManufacturer()->getName(), + 'make' => $vehicle->getMake(), + 'model_year_from' => $vehicle->getModelYearFrom(), + 'model_year_to' => $vehicle->getModelYearTo(), + ], + 'batteries' => $batt_list, + ]); + } + + public function removeVehicle($id, Request $req) + { + // validate params + $this->validateRequest($req); + + // get customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // find customer vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->find($id); + if ($cv == null) { + return new ApiResponse(false, 'Invalid customer vehicle id.'); + } + + // confirm that customer vehicle belongs to customer + if ($cv->getCustomer()->getID() != $cust->getID()) { + return new ApiResponse(false, 'Vehicle does not belong to customer.'); + } + + // we cannot remove a vehicle from customer if customer vehicle has already has JOs for it. + // instead we set the customer vehicle's flag_active to false + $cv->setActive(false); + $this->em->flush(); + + // response + return new ApiResponse(); + } + + protected function checkVehicleRequirements(Request $req) + { + // validate params + $this->validateRequest($req, [ + 'make_id', + 'name', + 'plate_num', + 'model_year', + 'color', + 'condition', + 'fuel_type', + ]); + + // TODO: check valid plate number + // TODO: check valid fuel type (gas / diesel) + // TODO: check current battery id + // TODO: check condition (brand new / second-hand) + // TODO: check is_motolite and is_active (1 or 0) + // TODO: check warranty expiration date (YYYYMMDD) + // TODO: check model year coverage if it fits in between + } + + protected function setCustomerVehicleObject(Request $req, CustomerVehicle $cv) + { + // check customer + $cust = $this->session->getCustomer(); + if ($cust == null) { + return new ApiResponse(false, 'No customer information found.'); + } + + // get vehicle + $vehicle = $this->em->getRepository(Vehicle::class)->find($req->request->get('make_id')); + if ($vehicle == null) { + return new ApiResponse(false, 'Invalid vehicle make id.'); + } + + $cv->setCustomer($cust) + ->setVehicle($vehicle) + ->setName($req->request->get('name')) + ->setPlateNumber($req->request->get('plate_num')) + ->setModelYear($req->request->get('model_year')) + ->setColor($req->request->get('color')) + ->setFuelType($this->normalizeString($req->request->get('fuel_type'))) + ->setStatusCondition($this->normalizeString($req->request->get('condition'))); + + // set warranty code and expiration + // TODO: check warranty requirements + if (!empty($req->request->get('wty_code'))) + $cv->setWarrantyCode($req->request->get('wty_code')); + if (!empty($req->request->get('wty_expire'))) + $cv->setWarrantyExpiration(new DateTime($req->request->get('wty_expire'))); + + // TODO: get current battery + + // is motolite + if ($req->request->get('is_motolite') == 0) + $cv->setHasMotoliteBattery(false); + else + $cv->setHasMotoliteBattery(true); + + // is active + if ($req->request->get('is_active') == 0) + $cv->setActive(false); + else + $cv->setActive(true); + + // save + $this->em->persist($cv); + $this->em->flush(); + + // response + return new ApiResponse(true, '', [ + 'cv_id' => $cv->getID(), + ]); + } + + protected function normalizeString($string) + { + return trim(strtolower($string)); + } +} diff --git a/src/Controller/CustomerAppAPI/WarrantyController.php b/src/Controller/CustomerAppAPI/WarrantyController.php new file mode 100644 index 00000000..28026ae2 --- /dev/null +++ b/src/Controller/CustomerAppAPI/WarrantyController.php @@ -0,0 +1,582 @@ +validateRequest($req, [ + 'plate_number', + ]); + + $plate_number = $req->request->get('plate_number'); + + // find warranty using plate number + $warranty_results = $this->em->getRepository(Warranty::class)->findBy( + ['plate_number' => $plate_number], + ['date_create' => 'desc'] + ); + + // check if warranty_results is empty + if (empty($warranty_results)) { + return new ApiResponse(false, 'No warranty found for plate number.'); + } + + // activate all entries + foreach ($warranty_results as $warranty) { + $warranty->setActivated(); + } + + $this->em->flush(); + + // response + return new ApiResponse(); + } + + public function warrantyCheck($serial, Request $req, WarrantyRaffleLogger $raffle_logger) + { + // validate params + $this->validateRequest($req); + + // check if warranty serial is there + $serial = $this->cleanSerial($serial); + $warr_serial = $this->em->getRepository(WarrantySerial::class)->find($serial); + $warr = $this->em->getRepository(Warranty::class)->findOneBy(['serial' => $serial]); + $batt = null; + $is_registered = false; + + if ($warr_serial == null) { + return new ApiResponse(false, 'Invalid warranty serial code.'); + } + + $today = new DateTime(); + + $user_id = $req->query->get('api_key'); + $raffle_data = [ + 'user_id' => $user_id, + 'serial' => $serial, + 'warranty_id' => null, + 'action' => '', + 'bmodel_name' => '', + 'bsize_name' => '', + 'first_name' => '', + 'last_name' => '', + 'plate_number' => '', + 'contact_num' => '', + 'email' => '', + 'address' => '', + ]; + + $data_sent = []; + + // if we have a warranty entry for the serial already + if ($warr != null) { + $warr_plate = $warr->getPlateNumber(); + $is_registered = true; + $is_customer_warranty = false; + + // check if the warranty is registered to a car owned by the customer + $cust = $this->session->getCustomer(); + $is_customer_warranty = $this->checkCustomerPlateNumber($warr_plate, $cust); + + // null mobile number should be blank string instead + if ($warr->getMobileNumber() == null) + $mobile_num = ''; + else + $mobile_num = $warr->getMobileNumber(); + + $can_edit = $is_customer_warranty; + + // if customer plate number matches the one registered on the warranty + if ($is_customer_warranty) { + // purchase date of customer + if ($warr->getDatePurchaseCustomer() != null) + $date_purchase_cust = $warr->getDatePurchaseCustomer()->format('Y-m-d'); + else + $date_purchase_cust = $today->format('Y-m-d'); + + + // invoice + if ($warr->getFileInvoice() != null) + $invoice_url = $req->getSchemeAndHttpHost() . '/warranty_uploads/' . $warr->getFileInvoice(); + else + $invoice_url = ''; + + // warranty card + if ($warr->getFileWarrantyCard() != null) + $warr_card_url = $req->getSchemeAndHttpHost() . '/warranty_uploads/' . $warr->getFileWarrantyCard(); + else + $warr_card_url = ''; + + $customer = [ + 'first_name' => $warr->getFirstName() ?? '', + 'last_name' => $warr->getLastName() ?? '', + 'mobile_number' => $mobile_num, + 'plate_number' => $warr_plate, + 'email' => $warr->getEmail() ?? '', + 'contact_num' => $warr->getContactNumber() ?? '', + 'address' => $warr->getCustomerAddress() ?? '', + ]; + $other_data = [ + 'odometer' => (int) $warr->getOdometer() ?? 0, + 'date_purchase' => $date_purchase_cust, + 'invoice' => $invoice_url, + 'warr_card' => $warr_card_url, + 'dealer_name' => $warr->getDealerName() ?? '', + 'dealer_address' => $warr->getDealerAddress() ?? '', + 'branch_code' => $warr->getDealerBranchCode() ?? '', + ]; + + // set customer info and action for raffle log + $raffle_data['action'] = 'serial_check_customer'; + $raffle_data['first_name'] = $customer['first_name']; + $raffle_data['last_name'] = $customer['last_name']; + $raffle_data['plate_number'] = $customer['plate_number']; + $raffle_data['email'] = $customer['email']; + $raffle_data['contact_num'] = $customer['contact_num']; + $raffle_data['address'] = $customer['address']; + $raffle_data['warranty_id'] = $warr->getID(); + } else { + // hide customer information if customer is not the one registered + $customer = [ + 'first_name' => '', + 'last_name' => '', + 'mobile_number' => '', + 'plate_number' => '', + 'email' => '', + 'contact_num' => '', + 'address' => '', + ]; + $other_data = [ + 'odometer' => 0, + 'date_purchase' => $today->format('Y-m-d'), + 'invoice' => '', + 'warr_card' => '', + 'dealer_name' => '', + 'dealer_address' => '', + 'branch_code' => '', + ]; + + // set action for raffle log + $raffle_data['action'] = 'serial_check_not_customer'; + } + } else { + $can_edit = true; + $customer = [ + 'first_name' => '', + 'last_name' => '', + 'mobile_number' => '', + 'plate_number' => '', + 'email' => '', + 'contact_num' => '', + 'address' => '', + ]; + $other_data = [ + 'odometer' => 0, + 'date_purchase' => $today->format('Y-m-d'), + 'invoice' => '', + 'warr_card' => '', + 'dealer_name' => '', + 'dealer_address' => '', + 'branch_code' => '', + ]; + + // set action for raffle log + $raffle_data['action'] = 'serial_check_customer'; + } + + $sku = $warr_serial->getSKU(); + $batt = null; + $cat_name = ''; + if ($sku != null) + $batt = $this->em->getRepository(SAPBattery::class)->find($sku); + else { + // get the category name of the serial + $cat_name = $warr_serial->getMetaInfo('category_name'); + } + + // TODO: put this in a config file + $image_url = $req->getSchemeAndHttpHost() . '/battery/generic.png'; + if ($batt != null) { + $battery = [ + 'brand' => $batt->getBrand()->getName(), + 'size' => $batt->getSize()->getName(), + 'image_url' => $image_url, + ]; + } else { + $battery = [ + 'brand' => $cat_name, + 'size' => '', + 'image_url' => '', + ]; + } + + // set the rest of the raffle log entry + $raffle_data['bmodel_name'] = $battery['brand']; + $raffle_data['bsize_name'] = $battery['size']; + + // log the raffle log + $raffle_logger->logRaffleInfo($data_sent, $raffle_data); + + // response + return new ApiResponse(true, '', [ + 'is_valid' => true, + 'is_registered' => $is_registered, + 'can_edit' => $can_edit, + 'customer' => $customer, + 'battery' => $battery, + 'odometer' => $other_data['odometer'], + 'invoice' => $other_data['invoice'], + 'warr_card' => $other_data['warr_card'], + 'date_purchase' => $other_data['date_purchase'], + 'dealer_name' => $other_data['dealer_name'], + 'dealer_address' => $other_data['dealer_address'], + 'branch_code' => $other_data['branch_code'], + 'message' => [ + 'register_error' => 'Warranty serial code has already been registered.', + 'edit_error' => 'Sorry, warranty is registered under another vehicle not in your list of vehicles.', + ], + ]); + } + + public function warrantyRegister( + $serial, + Request $req, + KernelInterface $kernel, + RisingTideGateway $rt, + TranslatorInterface $trans, + WarrantyRaffleLogger $raffle_logger, + WarrantyAPILogger $logger + ) { + // validate params + $this->validateRequest($req, [ + 'first_name', + 'last_name', + 'plate_number', + 'date_purchase', + ]); + + // handle file uploads + $invoice = $req->files->get('invoice'); + $warr_card = $req->files->get('warr_card'); + + // normalize serial + $serial = $this->cleanSerial($serial); + // $serial = trim(strtoupper($serial)); + + // process picture uploads + $upload_dir = $kernel->getProjectDir() . '/public/warranty_uploads'; + $inv_filename = $this->handlePictureUpload($invoice, $upload_dir, $serial, 'invoice'); + $wcard_filename = $this->handlePictureUpload($warr_card, $upload_dir, $serial, 'wcard'); + + $user_id = $req->query->get('api_key'); + $log_data = [ + 'plate_number' => $req->request->get('plate_num'), + 'first_name' => $req->request->get('first_name'), + 'last_name' => $req->request->get('last_name'), + 'date_purchase' => $req->request->get('date_purchase'), + ]; + $action = 'create/update'; + $source = WarrantySource::MOBILE; + + // update customer information + // $cust = $this->updateCustomerInfo($req, $em); + + // update warranty + $res = $this->updateWarranty( + $rt, + $trans, + $req, + $serial, + $inv_filename, + $wcard_filename, + $logger, + $log_data, + $user_id, + $action, + $source, + $raffle_logger + ); + + $this->em->flush(); + + return new ApiResponse(); + } + + protected function handlePictureUpload($file, $target_dir, $serial, $name) + { + // error_log("handling $name upload"); + // no file sent + if ($file == null) { + error_log("handling $name upload - no file"); + return null; + } + + // create target dir if it doesn't exist + if (!file_exists($target_dir)) { + if (!mkdir($target_dir, 0744, true)) { + error_log('failed to create folder for warranty pictures'); + return null; + } + } + + // move file + $filename = $name . '.' . $file->getClientOriginalExtension(); + $file->move($target_dir . '/' . $serial, $filename); + + // error_log("filename - $filename"); + // error_log($target_dir . '/' . $serial . '/' . $filename); + + return $serial . '/' . $filename; + } + + // TODO: put this in a service + protected function cleanSerial($serial) + { + // trim and make everything upper case + $clean_serial = trim(strtoupper($serial)); + + + // remove QR prefix if it exists + // $prefix = substr($clean_serial, 0, 2); + // if ($prefix == 'QR') + // $clean_serial = substr($clean_serial, 2); + + return $clean_serial; + } + + protected function checkCustomerPlateNumber($plate_number, $cust) + { + // strip spaces and make all caps + $plate_number = preg_replace('/\s+/', '', strtoupper($plate_number)); + + // if there's no customer linked to session + if ($cust != null) { + // check all the customer vehicles + $cvs = $cust->getVehicles(); + foreach ($cvs as $cv) { + $cv_plate = preg_replace('/\s+/', '', strtoupper($cv->getPlateNumber())); + + // did we find a match? + if ($cv_plate == $plate_number) { + return true; + } + } + } + + return false; + } + + protected function updateWarranty($rt, $trans, $req, $serial, $inv_filename = null, $wcard_filename = null, $logger, $log_data, $user_id, $action, $source, $raffle_logger) + { + // prepare raffle log entry + $raffle_data = [ + 'user_id' => $user_id, + 'serial' => $serial, + 'warranty_id' => null, + 'action' => '', + 'bmodel_name' => '', + 'bsize_name' => '', + 'first_name' => '', + 'last_name' => '', + 'plate_number' => '', + 'contact_num' => '', + 'email' => '', + 'address' => '', + ]; + + // get serial + $warr_serial = $this->em->getRepository(WarrantySerial::class)->find($serial); + if ($warr_serial == null) { + return new ApiResponse(false, 'Invalid warranty serial code.'); + } + + // check if warranty exists already + $warr = $this->em->getRepository(Warranty::class)->findOneBy(['serial' => $serial]); + + // skip warranty if it already exists + if ($warr != null) { + /* + // NOTE: we could not update in the old version + $res->setError(true) + ->setErrorMessage('Warranty registration entry already exists.'); + return $res; + */ + + // check if warranty is registered to a serial owned by customer + $warr_plate = $warr->getPlateNumber(); + $cust = $this->session->getCustomer(); + $is_customer_warranty = $this->checkCustomerPlateNumber($warr_plate, $cust); + + if (!$is_customer_warranty) { + $error_msg = 'Warranty registered to a vehicle not in your list of vehicles.'; + + // set action to update + $action = 'update'; + $logger->logWarrantyInfo($log_data, $error_msg, $user_id, $action, $source); + + // response + return new ApiResponse(false, $error_msg); + } + + $sms_msg = $trans->trans('warranty_update_confirm'); + + // update raffle data action + $raffle_data['action'] = 'warranty_update'; + } else { + $warr = new Warranty(); + $sms_msg = $trans->trans('warranty_register_confirm'); + + // set warranty source + $warr->setCreateSource($source); + + // update raffle data action + $raffle_data['action'] = 'warranty_create'; + } + + // get sap battery + $sku = $warr_serial->getSKU(); + $sap_bty = null; + if ($sku != null) { + $sap_bty = $this->em->getRepository(SAPBattery::class)->find($sku); + if ($sap_bty == null) { + $error_msg = 'Could not find battery entry for warranty.'; + $logger->logWarrantyInfo($log_data, $error_msg, $user_id, $action, $source); + + // response + return new ApiResponse(false, $error_msg); + } + } + + // default date purchase to today + // NOTE: might need to change this later + $date_pur = new DateTime(); + + // get date purchase specified by customer + $date_pur_cust = DateTime::createFromFormat('Y-m-d', $req->request->get('date_purchase')); + if (!$date_pur_cust) { + $error_msg = 'Invalid date format for date of purchase.'; + $logger->logWarrantyInfo($log_data, $error_msg, $user_id, $action, $source); + + // response + return new ApiResponse(false, $error_msg); + } + + $customer = $this->session->getCustomer(); + if ($customer != null) { + $warr->setCustomer($customer); + // get customer vehicles + + $vehicle = $this->findCustomerVehicle($customer, $req->request->get('plate_number')); + if ($vehicle != null) + $warr->setVehicle($vehicle); + } + + // TODO: make a standard clean plate number service + // clean plate number + $plate = $req->request->get('plate_number'); + // upper case and remove spaces + $plate = strtoupper(str_replace(' ', '', $plate)); + // remove special characters + $plate = preg_replace('/[^A-Za-z0-9. -]/', '', $plate); + + // create or update warranty entry + $warr->setSerial($serial) + ->setFirstName($req->request->get('first_name')) + ->setLastName($req->request->get('last_name')) + ->setEmail($req->request->get('email')) + ->setPlateNumber($plate) + // TODO: figure out how to compute date of purchase + ->setDatePurchase($date_pur) + // TODO: set status + // ->setStatus() + // TODO: set battery model and size id + // ->setBatterySize() + // ->setBatteryModel() + ->setSAPBattery($sap_bty) + ->setMobileNumber(substr($this->session->getPhoneNumber(), 2)) + ->setActivated(true) + + // files + ->setFileInvoice($inv_filename) + ->setFileWarrantyCard($wcard_filename) + + // new fields + ->setOdometer($req->request->get('odometer', 0)) + ->setDatePurchaseCustomer($date_pur_cust) + ->setContactNumber($req->request->get('contact_num')) + ->setCustomerAddress($req->request->get('cust_address')) + ->setDealerName($req->request->get('dealer_name')) + ->setDealerAddress($req->request->get('dealer_address')) + ->setDealerBranchCode($req->request->get('branch_code')) + ->setValidated(false); + + // TODO: check for date purchase and date expire + + $this->em->persist($warr); + + // TODO: check if we need to do anyting else + $logger->logWarrantyInfo($log_data, '', $user_id, $action, $source); + + // send sms + // error_log('sending sms to - ' . $this->session->getPhoneNumber()); + $rt->sendSMS($this->session->getPhoneNumber(), $trans->trans('message.battery_brand_allcaps'), $sms_msg); + + // prepare the rest of the raffle log entry + $raffle_data['warranty_id'] = $warr->getID(); + $raffle_data['bmodel_name'] = $sap_bty->getBrand()->getName(); + $raffle_data['bsize_name'] = $sap_bty->getSize()->getName(); + $raffle_data['first_name'] = $req->request->get('first_name', ''); + $raffle_data['last_name'] = $req->request->get('last_name', ''); + $raffle_data['plate_number'] = $plate; + $raffle_data['contact_num'] = $req->request->get('contact_num', ''); + $raffle_data['email'] = $req->request->get('email', ''); + $raffle_data['address'] = $req->request->get('cust_address', ''); + + $data_sent = [ + 'plate_number' => $req->request->get('plate_number'), + 'first_name' => $req->request->get('first_name'), + 'last_name' => $req->request->get('last_name'), + 'date_purchase' => $req->request->get('date_purchase'), + 'address' => $req->request->get('cust_address', ''), + 'email' => $req->request->get('email', ''), + 'contact_num' => $req->request->get('contact_num', ''), + ]; + + // log raffle data + $raffle_logger->logRaffleInfo($data_sent, $raffle_data); + + // response + return new ApiResponse(); + } + + protected function findCustomerVehicle($customer, $plate_number) + { + $clean_plate = Warranty::cleanPlateNumber($plate_number); + if ($clean_plate) { + // find the customer vehicle and get the vehicle + $cv = $this->em->getRepository(CustomerVehicle::class)->findOneBy(['plate_number' => $clean_plate, 'customer' => $customer]); + if ($cv != null) { + $vehicle = $cv->getVehicle(); + return $vehicle; + } + } + + return null; + } +}