acl_gen = $acl_gen; } public function requestJobOrder(Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo, InventoryManager $im, MQTTClient $mclient, RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger, EntityManagerInterface $em, HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger, HubFilteringGeoChecker $hub_geofence, MobileAPIHandler $mah) { $this->denyAccessUnlessGranted('mobile_jo.request', null, 'No access.'); // check required parameters $required_params = [ 'service_type', 'cv_id', // 'batt_id', 'trade_in', 'long', 'lat', 'warranty', 'mode_of_payment', ]; $msg = $this->checkRequiredParameters($req, $required_params); if ($msg) return new APIResponse(false, $msg); // get capi user to link to mobile user $user_id = $this->getUser()->getID(); // get mobile user $mobile_user = $mah->findMobileUser($user_id); if ($mobile_user == null) return new APIResponse(false, 'No mobile user found.'); // validate request $msg = $this->validateJORequest($req, $geo); if ($msg) return new APIResponse(false, $msg); $jo = new JobOrder(); $this->setJOObject($jo, $req); // customer $cust = $mobile_user->getCustomer(); if ($cust == null) return new APIResponse(false, 'No customer information found'); $jo->setCustomer($cust); // make invoice criteria $icrit = new InvoiceCriteria(); $msg = $this->setInvoiceCriteria($em, $icrit, $req, $cust, $jo); if ($msg != null) return new APIResponse(false, $msg); // send to invoice generator $invoice = $ic->generateInvoice($icrit); $jo->setInvoice($invoice); // set hub criteria details $hub_criteria = new HubCriteria(); $this->setHubCriteria($em, $hub_geofence, $hub_criteria, $jo, $req); // find nearest hubs $nearest_hubs = $hub_select->find($hub_criteria); if (!empty($nearest_hubs)) $this->assignRider($nearest_hubs, $jo, $hub_filter_logger, $hub_dist); $em->persist($jo); $em->persist($invoice); $this->processEvents($em, $jo, $rah, $mclient); $em->flush(); // make invoice json data $invoice_data = [ 'total_price' => $invoice->getTotalPrice(), 'vat_ex_price' => $invoice->getVATExclusivePrice(), 'vat' => $invoice->getVAT(), 'discount' => $invoice->getDiscount(), 'trade_in' => $invoice->getTradeIn(), ]; $items = $invoice->getItems(); $items_data = []; foreach ($items as $item) { $items_data[] = [ 'title' => $item->getTitle(), 'qty' => $item->getQuantity() + 0, 'price' => $item->getPrice() + 0.0, ]; } $invoice_data['items'] = $items_data; // make job order data $data = [ 'jo_id' => $jo->getID(), 'invoice' => $invoice_data ]; $this->removeCustomerTag($jo, $jo->getCustomerVehicle(), $promo_logger, $mobile_user->getID()); return new APIResponse(true, 'Job order creatd', $data); } // TODO: modify for MobileUser // break this down into smaller functions // do we still use this? 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, EntityManagerInterface $em) { // check required parameters and api key $required_params = [ 'service_type', 'cv_id', 'trade_in', 'long', 'lat', 'warranty', 'mode_of_payment', ]; $res = $this->checkParamsAndKey($req, $em, $required_params); if ($res->isError()) return $res->getReturnResponse(); // trade in type $trade_in = $req->request->get('trade_in'); // address $address = $req->request->get('delivery_address', 'Set by mobile application'); // instructions $instructions = $req->request->get('delivery_instructions', ''); // longitude and latitude $long = $req->request->get('long'); $lat = $req->request->get('lat'); // geofence $is_covered = $geo->isCovered($long, $lat); if (!$is_covered) { // TODO: put geofence error message in config file somewhere $res->setError(true) ->setErrorMessage('Oops! Our service is limited to some areas in Metro Manila, Laguna, and Baguio only. We will update you as soon as we are able to cover your area'); return $res->getReturnResponse(); } $hub = null; $hub_id = $req->request->get('hub_id'); if (strlen($hub_id) > 0) $hub = $em->getRepository(Hub::class)->find($hub_id); $schedule_date = $req->request->get('date_schedule'); $slot_id = $req->request->get('slot_id'); $advance_order = $req->request->get('flag_advance_order'); // check for 'false' text if ($advance_order === false || $advance_order === 0 || $advance_order === '0' || $advance_order == 'false') $flag_advance_order = false; else $flag_advance_order = true; $jo = new JobOrder(); $jo->setSource(TransactionOrigin::MOBILE_APP) ->setStatus(JOStatus::PENDING) ->setDeliveryInstructions('') ->setTier1Notes('') ->setTier2Notes('') ->setDeliveryAddress($address) ->setTradeInType($trade_in) ->setDeliveryInstructions($instructions) // TODO: error check for valid mode of payment ->setModeOfPayment($req->request->get('mode_of_payment')) ->setAdvanceOrder($flag_advance_order) ->setStatusAutoAssign(AutoAssignStatus::NOT_ASSIGNED); // customer $cust = $this->session->getCustomer(); if ($cust == null) { $res->setError(true) ->setErrorMessage('No customer information found'); return $res->getReturnResponse(); } $jo->setCustomer($cust); // validate service type $stype = $req->request->get('service_type'); if (!ServiceType::validate($stype)) { $res->setError(true) ->setErrorMessage('Invalid service type'); return $res->getReturnResponse(); } $jo->setServiceType($stype); // validate warranty $warr = $req->request->get('warranty'); if (!WarrantyClass::validate($warr)) { $res->setError(true) ->setErrorMessage('Invalid warranty class'); return $res->getReturnResponse(); } $jo->setWarrantyClass($warr); // set coordinates $point = new Point($long, $lat); $jo->setCoordinates($point); // make invoice criteria $icrit = new InvoiceCriteria(); $icrit->setServiceType($stype); // check promo $promo_id = $req->request->get('promo_id'); if (!empty($promo_id)) { $promo = $em->getRepository(Promo::class)->find($promo_id); if ($promo == null) { $res->setError(true) ->setErrorMessage('Invalid promo id'); return $res->getReturnResponse(); } // put in criteria $icrit->addPromo($promo); } // check customer vehicle $cv = $em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id')); if ($cv == null) { $res->setError(true) ->setErrorMessage('Invalid customer vehicle id'); return $res->getReturnResponse(); } $icrit->setCustomerVehicle($cv); $jo->setCustomerVehicle($cv); // check if customer owns vehicle if ($cust->getID() != $cv->getCustomer()->getID()) { $res->setError(true) ->setErrorMessage('Customer does not own vehicle'); return $res->getReturnResponse(); } // check battery $batt_id = $req->request->get('batt_id'); if ($batt_id != null) { $batt = $em->getRepository(Battery::class)->find($batt_id); if ($batt == null) { $res->setError(true) ->setErrorMessage('Invalid battery id'); return $res->getReturnResponse(); } } else $batt = null; /* // put battery in criteria $icrit->addBattery($batt); */ // check trade-in // only allow motolite, other, none switch ($trade_in) { case TradeInType::MOTOLITE: case TradeInType::OTHER: break; default: $trade_in = ''; break; } $icrit->addEntry($batt, $trade_in, 1); // send to invoice generator $invoice = $ic->generateInvoice($icrit); $jo->setInvoice($invoice); // assign hub and rider // check if hub is null if ($hub == null) { $hub_criteria = new HubCriteria(); $hub_criteria->setPoint($jo->getCoordinates()); 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); } // add battery to items $sku = $batt->getSAPCode(); if (!empty($sku)) $hub_criteria->addItem($batt->getSAPCode(), 1); // 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) { $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); $assigned_rider->setAvailable(false); // update redis hub_jo_count for hub $hub_dist->incrementJoCountForHub($nearest_hub['hub']); // break out of loop break; } else { // log hub into hub_filter_log $hub_filter_logger->logFilteredHub($nearest_hub['hub'], 'no_available_rider'); // continue to go through list to find hub with an available rider } } } } else { $date_schedule = null; if ((strlen($schedule_date) > 0) && (strlen($slot_id) > 0)) { $time_schedule = $this->getTimeFromSlot($slot_id); if (!empty($time_schedule)) { $s_date = $schedule_date . ' ' . $time_schedule; $date_schedule = DateTime::createFromFormat('Y-m-d H:i', $s_date); //error_log($date_schedule->format('Y-m-d H:i')); } } $jo->setHub($hub); $jo->setStatus(JOStatus::RIDER_ASSIGN); $jo->setStatusAutoAssign(AutoAssignStatus::HUB_ASSIGNED); if ($date_schedule != null) $jo->setDateSchedule($date_schedule); // update redis hub_jo_count for hub $hub_dist->incrementJoCountForHub($hub); } $em->persist($jo); $em->persist($invoice); // add event log for JO $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::CREATE) ->setJobOrder($jo); $em->persist($event); // check JO status if ($jo->getStatus() == JOStatus::ASSIGNED) { // add event logs for hub and rider assignments $hub_assign_event = new JOEvent(); $hub_assign_event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::HUB_ASSIGN) ->setJobOrder($jo); $em->persist($hub_assign_event); $rider_assign_event = new JOEvent(); $rider_assign_event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::RIDER_ASSIGN) ->setJobOrder($jo); $em->persist($rider_assign_event); // user mqtt event $payload = [ 'event' => 'outlet_assign' ]; $mclient->sendEvent($jo, $payload); $rah->assignJobOrder($jo, $jo->getRider()); } if ($jo->getStatus() == JOStatus::RIDER_ASSIGN) { // add event logs for hub assignments $hub_assign_event = new JOEvent(); $hub_assign_event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::HUB_ASSIGN) ->setJobOrder($jo); $em->persist($hub_assign_event); // user mqtt event $payload = [ 'event' => 'outlet_assign' ]; $mclient->sendEvent($jo, $payload); } $em->flush(); // make invoice json data $invoice_data = [ 'total_price' => $invoice->getTotalPrice(), 'vat_ex_price' => $invoice->getVATExclusivePrice(), 'vat' => $invoice->getVAT(), 'discount' => $invoice->getDiscount(), 'trade_in' => $invoice->getTradeIn(), ]; $items = $invoice->getItems(); $items_data = []; foreach ($items as $item) { $items_data[] = [ 'title' => $item->getTitle(), 'qty' => $item->getQuantity() + 0, 'price' => $item->getPrice() + 0.0, ]; } $invoice_data['items'] = $items_data; // make job order data $data = [ 'jo_id' => $jo->getID(), 'invoice' => $invoice_data ]; // 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) { 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); } } } } // set data $res->setData($data); return $res->getReturnResponse(); } // TODO: modify for MobileUser public function getEstimate(Request $req, InvoiceGeneratorInterface $ic, EntityManagerInterface $em) { // $this->debugRequest($req); // check required parameters and api key $required_params = [ 'service_type', 'cv_id', // 'batt_id', 'trade_in', ]; $res = $this->checkParamsAndKey($req, $em, $required_params); if ($res->isError()) return $res->getReturnResponse(); // customer $cust = $this->session->getCustomer(); if ($cust == null) { $res->setError(true) ->setErrorMessage('No customer information found'); return $res->getReturnResponse(); } // make invoice criteria $icrit = new InvoiceCriteria(); $icrit->setServiceType($req->request->get('service_type')); // check promo $promo_id = $req->request->get('promo_id'); if (!empty($promo_id)) { $promo = $em->getRepository(Promo::class)->find($promo_id); if ($promo == null) { $res->setError(true) ->setErrorMessage('Invalid promo id'); return $res->getReturnResponse(); } // put in criteria $icrit->addPromo($promo); } // check customer vehicle $cv = $em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id')); if ($cv == null) { $res->setError(true) ->setErrorMessage('Invalid customer vehicle id'); return $res->getReturnResponse(); } $icrit->setCustomerVehicle($cv); // check if customer owns vehicle if ($cust->getID() != $cv->getCustomer()->getID()) { $res->setError(true) ->setErrorMessage('Customer does not own vehicle'); return $res->getReturnResponse(); } // check battery $batt_id = $req->request->get('batt_id'); if ($batt_id != null) { $batt = $em->getRepository(Battery::class)->find($batt_id); if ($batt == null) { $res->setError(true) ->setErrorMessage('Invalid battery id'); return $res->getReturnResponse(); } } else $batt = null; /* // put battery in criteria $icrit->addBattery($batt); */ // check trade-in // only allow motolite, other, none $trade_in = $req->request->get('trade_in'); switch ($trade_in) { case TradeInType::MOTOLITE: case TradeInType::OTHER: break; default: $trade_in = ''; break; } $icrit->addEntry($batt, $trade_in, 1); // send to invoice generator $invoice = $ic->generateInvoice($icrit); // make invoice json data $data = [ 'total_price' => (float) $invoice->getTotalPrice(), 'vat_ex_price' => (float) $invoice->getVATExclusivePrice(), 'vat' => (float) $invoice->getVAT(), 'discount' => (float) $invoice->getDiscount(), 'trade_in' => (float) $invoice->getTradeIn(), ]; $items = $invoice->getItems(); $items_data = []; foreach ($items as $item) { $my_data = [ 'title' => $item->getTitle(), 'qty' => (int) $item->getQuantity() + 0, 'price' => (float) $item->getPrice() + 0.0, ]; $item_batt = $item->getBattery(); if ($item_batt != null) { $my_data['image_url'] = $this->getBatteryImageURL($req, $item_batt); } $items_data[] = $my_data; } $data['items'] = $items_data; // error_log(print_r($data, true)); // set data $res->setData($data); return $res->getReturnResponse(); } // TODO: modify for MobileUser public function getOngoing(Request $req, EntityManagerInterface $em) { $required_params = []; $res = $this->checkParamsAndKey($req, $em, $required_params); if ($res->isError()) return $res->getReturnResponse(); // get customer $cust = $this->session->getCustomer(); if ($cust == null) { $res->setError(true) ->setErrorMessage('No customer information found'); return $res->getReturnResponse(); } /* // check if we have an ongoing job order $ongoing_jos = $em->getRepository(JobOrder::class)->findBy([ 'customer' => $cust, 'status' => [JOStatus::PENDING, JOStatus::RIDER_ASSIGN, JOStatus::IN_TRANSIT, JOStatus::ASSIGNED, JOStatus::IN_PROGRESS], ]); */ $ongoing_jos = $this->getOngoingJobOrders($cust, $em); // initialize data $data = []; // no ongoing if (count($ongoing_jos) <= 0) { $data = [ 'has_ongoing' => false, ]; } else { $data = [ 'has_ongoing' => true, ]; } $res->setData($data); return $res->getReturnResponse(); } // TODO: modify for MobileUser public function cancelJobOrder(Request $req, MQTTClient $mclient, EntityManagerInterface $em) { $required_params = [ 'jo_id', 'reason' ]; $res = $this->checkParamsAndKey($req, $em, $required_params); if ($res->isError()) return $res->getReturnResponse(); // get job order $jo_id = $req->request->get('jo_id'); $jo = $em->getRepository(JobOrder::class)->find($jo_id); if ($jo == null) { $res->setError(true) ->setErrorMessage('No job order found'); return $res->getReturnResponse(); } // get customer $cust = $this->session->getCustomer(); if ($cust == null) { $res->setError(true) ->setErrorMessage('No customer information found'); return $res->getReturnResponse(); } // check that the customer owns the job order $jo_cust = $jo->getCustomer(); if ($jo_cust->getID() != $cust->getID()) { $res->setError(true) ->setErrorMessage('Job order was not initiated by customer'); return $res->getReturnResponse(); } // TODO: check job order status, if it's cancellable $cancel_reason = $req->request->get('reason'); $jo->cancel($cancel_reason); // add event log $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::CANCEL) ->setJobOrder($jo); $em->persist($event); $em->flush(); // send mobile app event $payload = [ 'event' => 'cancelled', 'reason' => $cancel_reason, 'jo_id' => $jo->getID(), ]; // $mclient->sendEvent($jo, $payload); $mclient->sendRiderEvent($jo, $payload); $res->setData([]); return $res->getReturnResponse(); } // TODO: modify for MobileUser public function getJOHistory(Request $req, EntityManagerInterface $em) { $res = $this->checkParamsAndKey($req, $em, []); if ($res->isError()) return $res->getReturnResponse(); // get customer $cust = $this->session->getCustomer(); if ($cust == null) { $res->setError(true) ->setErrorMessage('No customer information found'); return $res->getReturnResponse(); } // get job orders $all_jo_data = []; $jos = $cust->getJobOrders(); foreach ($jos as $jo) { $status = $jo->getStatus(); $jo_data = [ 'id' => $jo->getID(), 'date_create' => $jo->getDateCreate()->format('M d, Y'), 'service_type' => $jo->getServiceType(), 'status' => $status, ]; // customer vehicle and warranty $cv = $jo->getCustomerVehicle(); // get latest warranty using plate number $warranty = $this->findWarranty($cv->getPlateNumber()); $jo_data['customer_vehicle'] = [ 'id' => $cv->getID(), 'plate_number' => $cv->getPlateNumber(), 'warranty' => $warranty, ]; // rider $rider = $jo->getRider(); if ($rider != null) { $jo_data['rider'] = $rider->getFullName(); } // invoice items $items = []; $jo_items = $jo->getInvoice()->getItems(); foreach ($jo_items as $item) { $items[] = [ 'id' => $item->getID(), 'title' => $item->getTitle(), 'qty' => $item->getQuantity(), 'price' => $item->getPrice(), ]; } $jo_data['items'] = $items; // dates depending on status switch ($status) { case JOStatus::FULFILLED: if ($jo->getDateFulfill() == null) $jo_data['date_fulfilled'] = ''; else $jo_data['date_fulfilled'] = $jo->getDateFulfill()->format('M d, Y'); break; case JOStatus::CANCELLED: $date_cancel = $jo->getDateCancel(); if ($date_cancel == null) $date_cancel = new DateTime(); $jo_data['date_cancelled'] = $date_cancel->format('M d, Y'); break; } $all_jo_data[] = $jo_data; } // return data $data = [ 'job_orders' => $all_jo_data ]; $res->setData($data); // response return $res->getReturnResponse(); } // TODO: modify for MobileUser public function getJOInvoice(Request $req, EntityManagerInterface $em) { $required_params = [ 'jo_id', ]; $res = $this->checkParamsAndKey($req, $em, $required_params); if ($res->isError()) return $res->getReturnResponse(); // get job order $jo_id = $req->query->get('jo_id'); $jo = $em->getRepository(JobOrder::class)->find($jo_id); if ($jo == null) { $res->setError(true) ->setErrorMessage('No job order found'); return $res->getReturnResponse(); } // get customer $cust = $this->session->getCustomer(); if ($cust == null) { $res->setError(true) ->setErrorMessage('No customer information found'); return $res->getReturnResponse(); } // check that the customer owns the job order $jo_cust = $jo->getCustomer(); if ($jo_cust->getID() != $cust->getID()) { $res->setError(true) ->setErrorMessage('Job order was not initiated by customer'); return $res->getReturnResponse(); } $invoice = $jo->getInvoice(); // make invoice json data $data = [ 'total_price' => (float) $invoice->getTotalPrice(), 'vat_ex_price' => (float) $invoice->getVATExclusivePrice(), 'vat' => (float) $invoice->getVAT(), 'discount' => (float) $invoice->getDiscount(), 'trade_in' => (float) $invoice->getTradeIn(), ]; $items = $invoice->getItems(); $items_data = []; foreach ($items as $item) { $my_data = [ 'title' => $item->getTitle(), 'qty' => (int) $item->getQuantity() + 0, 'price' => (float) $item->getPrice() + 0.0, ]; $item_batt = $item->getBattery(); if ($item_batt != null) { $my_data['image_url'] = $this->getBatteryImageURL($req, $item_batt); } $items_data[] = $my_data; } $data['items'] = $items_data; // set data $res->setData($data); return $res->getReturnResponse(); } public function locationSupport(Request $req, GeofenceTracker $geo, EntityManagerInterface $em) { $required_params = [ 'longitude', 'latitude', ]; $res = $this->checkParamsAndKey($req, $em, $required_params); if ($res->isError()) return $res->getReturnResponse(); $long = $req->query->get('longitude'); $lat = $req->query->get('latitude'); // geofence $is_covered = $geo->isCovered($long, $lat); $data = [ 'longitude' => $long, 'latitude' => $lat, 'supported' => $is_covered, ]; $res->setData($data); return $res->getReturnResponse(); } // TODO: do we make this use the HubCriteria and HubSelector? YES? public function getNearestHubAndSlots(Request $req, EntityManagerInterface $em, MapTools $map_tools) { $required_params = [ 'longitude', 'latitude', ]; $res = $this->checkParamsAndKey($req, $em, $required_params); if ($res->isError()) return $res->getReturnResponse(); $coordinates = new Point($req->query->get('longitude'), $req->query->get('latitude')); $nearest_hub_slots = $this->findAdvanceNearestHubAndSlots($coordinates, $em, $map_tools); if (empty($nearest_hub_slots['hub'])) { $res->setError(true) ->setErrorMessage('Thank you for reaching out to us. Due to the General Community Quarantine, our Operations are from 8AM to 6PM only. Please expect a call from us tomorrow and we will assist you with your request. Thank you and stay safe!'); return $res->getReturnResponse(); } // make hub data $data = [ 'hub_id' => $nearest_hub_slots['hub']->getID(), 'hub_slots' => $nearest_hub_slots['slots'], ]; $res->setData($data); return $res->getReturnResponse(); } public function scheduleOptionStatus(Request $req, EntityManagerInterface $em) { // check required parameters and api key $required_params = []; $res = $this->checkParamsAndKey($req, $em, $required_params); if ($res->isError()) return $res->getReturnResponse(); $schedule_choice = true; // TODO: remove the time check after ECQ. This will then always return true // get current time $current_datetime = new DateTime(); //$current_datetime = DateTime::createFromFormat('Y-m-d H:i', '2020-04-30 17:01'); // get the hour $hour = $current_datetime->format('G'); if (($hour < 8) || ($hour > 16)) $schedule_choice = false; $data = [ 'display_schedule_choice' => $schedule_choice, ]; $res->setData($data); return $res->getReturnResponse(); } protected function getOngoingJobOrders($cust, $em) { $ongoing_jos = $em->getRepository(JobOrder::class)->findBy([ 'customer' => $cust, 'status' => [JOStatus::PENDING, JOStatus::RIDER_ASSIGN, JOStatus::IN_TRANSIT, JOStatus::ASSIGNED, JOStatus::IN_PROGRESS], ]); return $ongoing_jos; } // TODO: what to do with this? listVehicles in CustomerVehicleController als calls this protected function findWarranty($plate_number, $em) { // NOTE: Modify the search for the latest warranty. This seems hacky. // get latest warranty using plate number $warranty_results = $em->getRepository(Warranty::class)->findBy(['plate_number' => $plate_number], ['date_create' => 'desc']); $warr = []; // check if warranty_results is empty if (empty($warranty_results)) { /* $res->setError(true) ->setErrorMessage('No warranty found for plate number'); return $res->getReturnResponse(); */ return $warr; } // get first entry $warranty = current($warranty_results); // check for null values for battery and date claim and date expire $batt_model = ''; $batt_size = ''; $sap_batt = ''; $claim_date = ''; $expiry_date = ''; if (!(is_null($warranty->getBatteryModel()))) { $batt_model = $warranty->getBatteryModel()->getName(); } if (!(is_null($warranty->getBatterySize()))) { $batt_size = $warranty->getBatterySize()->getName(); } if (!(is_null($warranty->getSAPBattery()))) { $sap_batt = $warranty->getSAPBattery()->getID(); } if (!(is_null($warranty->getDateClaim()))) { $claim_date = $warranty->getDateClaim()->format("d M Y"); } if (!(is_null($warranty->getDateExpire()))) { $expiry_date = $warranty->getDateExpire()->format("d M Y"); } $warr[] = [ 'id' => $warranty->getID(), 'serial' => $warranty->getSerial(), 'warranty_class' => $warranty->getWarrantyClass(), 'plate_number' => $warranty->getPlateNumber(), 'first_name' => $warranty->getFirstName(), 'last_name' => $warranty->getLastName(), 'mobile_number' => $warranty->getMobileNumber(), 'battery_model' => $batt_model, 'battery_size' => $batt_size, 'sap_battery' => $sap_batt, 'status' => $warranty->getStatus(), 'date_create' => $warranty->getDateCreate()->format("d M Y g:i A"), 'date_purchase' => $warranty->getDatePurchase()->format("d M Y"), 'date_expire' => $expiry_date, 'date_claim' => $claim_date, 'claim_from' => $warranty->getClaimedFrom(), 'is_activated' => $warranty->isActivated() ? 1 : 0, ]; return $warr; } 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; } // TODO: this might become irrelevant if we use HubCriteria (which I think we should, my blooper) protected function findAdvanceNearestHubAndSlots(Point $coordinates, EntityManagerInterface $em, MapTools $map_tools) { // get the nearest 10 hubs $hub_data = []; $nearest_hubs_with_distance = []; $hubs = $map_tools->getClosestOpenHubs($coordinates, 10); foreach ($hubs as $hub) { $nearest_hubs_with_distance[] = $hub; // TODO: insert checking for branch code here when inventory manager is up } $nearest = null; $slot_found = false; // find the nearest hub if (!empty($nearest_hubs_with_distance)) { foreach ($nearest_hubs_with_distance as $nhd) { if (empty($nearest)) $nearest = $nhd; else { if ($nhd['distance'] < $nearest['distance']) $nearest = $nhd; } } // get slots of nearest hub if ($nearest != null) { $hub_slots = $this->getHubRiderSlots($nearest['hub'], $em); $hub_data = [ 'hub' => $nearest['hub'], 'slots' => $hub_slots, ]; } } return $hub_data; } protected function getHubRiderSlots(Hub $hub, EntityManagerInterface $em) { // check hub's advance orders for the day /* // get number of advance orders for the next day if request came in before midnight // or for current day if request came in after midnight // check request_time $request_time = time(); $midnight = strtotime('00:00'); */ $start_date = new DateTime(); $end_date = new DateTime(); // to keep things simple, just start on next day regardless of midnight timer $start_date->add(new DateInterval('P1D')); $end_date->add(new DateInterval('P3D')); /* if ($request_time < $midnight) { // add +1 to start date to get the next day // add +3 to date to end date to get the advance orders for the next three days $start_date->add(new DateInterval('P1D')); $end_date->add(new DateInterval('P1D')); } $end_date->add(new DateInterval('P2D')); */ // set time bounds for the start and end date $start_date->setTime(0, 1); $end_date->setTime(23, 59); // NOTE: get advance orders via query // get JOs assigned to hub that are advance orders and scheduled for the next three days with // for hub assignment status $query = $em->createQuery('select jo from App\Entity\JobOrder jo where jo.hub = :hub and jo.flag_advance = true and jo.date_schedule >= :date_start and jo.date_schedule <= :date_end and jo.status != :status_cancelled and jo.status != :status_fulfilled'); $jos_advance_orders = $query->setParameters([ 'hub' => $hub, 'date_start' => $start_date, 'date_end' => $end_date, 'status_cancelled' => JOStatus::CANCELLED, 'status_fulfilled' => JOStatus::FULFILLED, ]) ->getResult(); // check request_time // define slots $slots = [ '08_09' => '8:00 AM', '09_10' => '9:00 AM', '10_11' => '10:00 AM', '11_12' => '11:00 AM', '12_13' => '12:00 PM', '13_14' => '1:00 PM', '14_15' => '2:00 PM', '15_16' => '3:00 PM', '16_17' => '4:00 PM', ]; // get the dates for the next three days $first_date = $start_date->format('Y-m-d'); $second_date = $start_date->add(new DateInterval('P1D')); $sec_date = $second_date->format('Y-m-d'); $third_date = $end_date->format('Y-m-d'); // define days $days = [ $first_date => $first_date, $sec_date => $sec_date, $third_date => $third_date, ]; // initialize hub rider slots $hub_rider_slots = []; foreach ($days as $day) { foreach ($slots as $slot_key => $slot) { $hub_rider_slots[$day][$slot_key] = $hub->getRiderSlots(); } } // check each JO's date_schedule, decrement rider_slots if date schedule falls in that slot foreach ($jos_advance_orders as $jo) { // get date key $date_sched = $jo->getDateSchedule(); $date_string = $date_sched->format('Y-m-d'); $hour = $date_sched->format('H'); $slot_id = sprintf('%02d_%02d', $hour, $hour + 1); error_log("SLOT - $date_string - $slot_id"); // decrement rider slot if (isset($hub_rider_slots[$date_string][$slot_id])) $hub_rider_slots[$date_string][$slot_id]--; // check if it goes through next slot (10 min allowance) $mins = $date_sched->format('i'); if ($mins > 10) { $next_slot_id = sprintf('%02d_%02d', $hour + 1, $hour + 2); error_log("NEXT SLOT - $date_string - $next_slot_id"); // decrement rider slot if (isset($hub_rider_slots[$date_string][$next_slot_id])) $hub_rider_slots[$date_string][$next_slot_id]--; } } error_log(print_r($hub_rider_slots, true)); $hub_slots = $this->generateHubSlots($hub_rider_slots, $slots); return $hub_slots; } protected function generateHubSlots($rider_slots, $slots) { $data = []; foreach ($rider_slots as $day_id => $rslot) { $data[$day_id] = []; foreach ($rslot as $slot_id => $avail_slots) { $slot_data = [ 'id' => $slot_id, 'label' => $slots[$slot_id], 'available' => true, ]; // mark unavailable ones if ($avail_slots <= 0) $slot_data['available'] = false; // add to day data $data[$day_id][] = $slot_data; } } return $data; } protected function getTimeFromSlot($slot_id) { $time_selected = ''; switch($slot_id) { case '08_09': $time_selected = AdvanceOrderSlot::_08_09; break; case '09_10': $time_selected = AdvanceOrderSlot::_09_10; break; case '10_11': $time_selected = AdvanceOrderSlot::_10_11; break; case '11_12': $time_selected = AdvanceOrderSlot::_11_12; break; case '12_13': $time_selected = AdvanceOrderSlot::_12_13; break; case '13_14': $time_selected = AdvanceOrderSlot::_13_14; break; case '14_15': $time_selected = AdvanceOrderSlot::_14_15; break; case '15_16': $time_selected = AdvanceOrderSlot::_15_16; break; case '16_17': $time_selected = AdvanceOrderSlot::_16_17; break; default: error_log('Invalid slot id ' . $slot_id); } return $time_selected; } protected function validateJORequest(Request $req, GeofenceTracker $geo) { // geofence $long = $req->request->get('long'); $lat = $req->request->get('lat'); $is_covered = $geo->isCovered($long, $lat); if (!$is_covered) { // TODO: put geofence error message in config file somewhere $msg = 'Oops! Our service is limited to some areas in Metro Manila, Laguna, and Baguio only. We will update you as soon as we are able to cover your area'; return $msg; } // validate service type $stype = $req->request->get('service_type'); if (!ServiceType::validate($stype)) { $msg = 'Invalid service type'; return $msg; } // validate warranty $warr = $req->request->get('warranty'); if (!WarrantyClass::validate($warr)) { $msg = 'Invalid warranty class'; return $msg; } // validate payment method $payment_method = $req->request->get('mode_of_payment'); if (!ModeOfPayment::validate($payment_method)) { $msg = 'Invalid payment method'; return $msg; } return null; } protected function setJOObject(JobOrder $jo, Request $req) { // longitude and latitude $long = $req->request->get('long'); $lat = $req->request->get('lat'); $point = new Point($long, $lat); $jo->setSource(TransactionOrigin::MOBILE_APP) ->setStatus(JOStatus::PENDING) ->setDeliveryInstructions('') ->setTier1Notes('') ->setTier2Notes('') ->setDeliveryAddress($req->request->get('delivery_address', 'Set by mobile application')) ->setTradeInType($req->request->get('trade_in')) ->setDeliveryInstructions($req->request->get('delivery_instructions', '')) ->setModeOfPayment($req->request->get('mode_of_payment')) ->setServiceType($req->request->get('service_type')) ->setWarrantyClass($req->request->get('warranty')) ->setCoordinates($point); } protected function setInvoiceCriteria(EntityManagerInterface $em, InvoiceCriteria $icrit, Request $req, Customer $cust, JobOrder $jo) { $icrit->setServiceType($req->request->get('service_type')); // check promo $promo_id = $req->request->get('promo_id'); if (!empty($promo_id)) { $promo = $em->getRepository(Promo::class)->find($promo_id); if ($promo == null) { $msg = 'Invalid promo id'; return $msg; } // put in criteria $icrit->addPromo($promo); } // check customer vehicle $cv = $em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id')); if ($cv == null) { $msg = 'Invalid customer vehicle id'; return $msg; } $icrit->setCustomerVehicle($cv); $jo->setCustomerVehicle($cv); // check if customer owns vehicle if ($cust->getID() != $cv->getCustomer()->getID()) { $msg = 'Customer does not own vehicle'; return $msg; } // check battery $batt_id = $req->request->get('batt_id'); if ($batt_id != null) { $batt = $em->getRepository(Battery::class)->find($batt_id); if ($batt == null) { $msg = 'Invalid battery id'; return $msg; } } 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); return null; } protected function setHubCriteria(EntityManagerInterface $em, HubFilteringGeoChecker $hub_geofence, HubCriteria $hub_criteria, JobOrder $jo, Request $req) { $hub_criteria->setPoint($jo->getCoordinates()); $long = $jo->getCoordinates()->getLongitude(); $lat = $jo->getCoordinates()->getLatitude(); if ($hub_geofence->isCovered($long, $lat)) { // TODO: set this properly, since the other flags // are on default values. // if true, set other values for HubCriteria // error_log('Area is covered by hub filtering'); $hub_criteria->setJoType($jo->getServiceType()) ->setPaymentMethod($jo->getModeOfPayment()) ->setRoundRobin(true); } $batt_id = $req->request->get('batt_id'); if ($batt_id != null) { $batt = $em->getRepository(Battery::class)->find($batt_id); if ($batt != null) { // add battery to items $sku = $batt->getSAPCode(); if (!empty($sku)) $hub_criteria->addItem($batt->getSAPCode(), 1); } } } protected function assignRider($nearest_hubs, JobOrder $jo, HubFilterLogger $hub_filter_logger, HubDistributor $hub_dist) { // try to assin rider // 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) { $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); $assigned_rider->setAvailable(false); // update redis hub_jo_count for hub $hub_dist->incrementJoCountForHub($nearest_hub['hub']); // break out of loop break; } else { // log hub into hub_filter_log $hub_filter_logger->logFilteredHub($nearest_hub['hub'], 'no_available_rider', $jo->getID(), $jo->getCustomer()->getID()); // continue to go through list to find hub with an available rider } } } protected function processEvents(EntityManagerInterface $em, JobOrder $jo, RiderAssignmentHandlerInterface $rah, MQTTClient $mclient) { // add event log for JO $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::CREATE) ->setJobOrder($jo); $em->persist($event); // check JO status if ($jo->getStatus() == JOStatus::ASSIGNED) { // add event logs for hub and rider assignments $hub_assign_event = new JOEvent(); $hub_assign_event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::HUB_ASSIGN) ->setJobOrder($jo); $em->persist($hub_assign_event); $rider_assign_event = new JOEvent(); $rider_assign_event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::RIDER_ASSIGN) ->setJobOrder($jo); $em->persist($rider_assign_event); // user mqtt event $payload = [ 'event' => 'outlet_assign' ]; $mclient->sendEvent($jo, $payload); $rah->assignJobOrder($jo, $jo->getRider()); } } protected function removeCustomerTag(JobOrder $jo, CustomerVehicle $cv, PromoLogger $promo_logger, $mobile_user_id) { // 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) { if ($customer_tag->getID() == $invoice->getUsedCustomerTagId()) { // remove associated entity $customer->removeCustomerTag($customer_tag); // log the availment of promo from customer $created_by = $mobile_user_id; $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); } } } } } }