validateRequest($req); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // 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 = $this->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 $validity = $this->validateRequest($req, [ 'jo_id', ]); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // 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 $validity = $this->validateRequest($req, [ 'jo_id', 'reason', ]); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // 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 $validity = $this->validateRequest($req); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // 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 $validity = $this->validateRequest($req); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // 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 $validity = $this->validateRequest($req); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // 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 $validity = $this->validateRequest($req); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // 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 $validity = $this->validateRequest($req); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // 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, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger, HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger, HubFilteringGeoChecker $hub_geofence, JobOrderManager $jo_manager, PriceTierManager $pt_manager ) { // validate params $validity = $this->validateRequest($req, [ 'service_type', 'cv_id', 'long', 'lat', 'warranty', 'mode_of_payment', ]); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // trade in type $trade_in_batt = $req->request->get('trade_in_batt'); $trade_in_type = $req->request->get('trade_in_type', ''); // 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_type) ->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(); // } // check if customer has more than one job order already $flag_cust_new = false; $cust_jo_count = $jo_manager->getCustomerJobOrderCount($cust->getID()); if ($cust_jo_count <= 1) $flag_cust_new = true; $jo->setCustomer($cust); $jo->setCustNew($flag_cust_new); // 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_type) { case TradeInType::MOTOLITE: case TradeInType::OTHER: break; default: $trade_in_type = ''; break; } // add the actual battery item first $icrit->addEntry($batt, null, 1); // if we have a trade in, add it as well if (!empty($trade_in_type) && !empty($trade_in_batt)) { $ti_batt_obj = $this->em->getRepository(Battery::class)->find($trade_in_batt); if (!empty($ti_batt_obj)) { $battery_size = $ti_batt_obj->getSize(); $icrit->addTradeInEntry($battery_size, $trade_in_type, 1); } } // set taxable $icrit->setIsTaxable(); // set JO source $icrit->setSource(TransactionOrigin::MOBILE_APP); // set price tier $pt_id = $pt_manager->getPriceTier($jo->getCoordinates()); $icrit->setPriceTier($pt_id); // 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); $this->em->flush(); // 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', ]; $mclientv2->sendEvent($jo, $payload); $fcmclient->sendJoEvent($jo, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); $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', ]; $mclientv2->sendEvent($jo, $payload); $fcmclient->sendJoEvent($jo, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); } $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('session_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, ]); } // TODO: remove later // mobile app no longer calls this public function requestJobOrder( Request $req, InvoiceGeneratorInterface $ic, GeofenceTracker $geo, MapTools $map_tools, InventoryManager $im, MQTTClientApiv2 $mclientv2, FCMSender $fcmclient, RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger, HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger, HubFilteringGeoChecker $hub_geofence, JobOrderManager $jo_manager, PriceTierManager $pt_manager ) { // validate params $validity = $this->validateRequest($req, [ 'service_type', 'cv_id', // 'batt_id', 'long', 'lat', 'warranty', 'mode_of_payment', ]); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // trade in type $trade_in_batt = $req->request->get('trade_in_batt'); $trade_in_type = $req->request->get('trade_in_type', ''); // 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 return new ApiResponse(false, $this->getGeoErrorMessage()); } $jo = new JobOrder(); $jo->setSource(TransactionOrigin::MOBILE_APP) ->setStatus(JOStatus::PENDING) ->setDeliveryInstructions('') ->setTier1Notes('') ->setTier2Notes('') ->setDeliveryAddress($address) ->setTradeInType($trade_in_type) ->setDeliveryInstructions($instructions) // TODO: error check for valid mode of payment ->setModeOfPayment($req->request->get('mode_of_payment')); // customer $cust = $this->session->getCustomer(); if ($cust == null) { return new ApiResponse(false, 'No customer information found.'); } // check if customer has more than one job order already $flag_cust_new = false; $cust_jo_count = $jo_manager->getCustomerJobOrderCount($cust->getID()); if ($cust_jo_count <= 1) $flag_cust_new = true; $jo->setCustomer($cust); $jo->setCustNew($flag_cust_new); // 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_type) { case TradeInType::MOTOLITE: case TradeInType::OTHER: break; default: $trade_in_type = ''; break; } // add the actual battery item first $icrit->addEntry($batt, null, 1); // if we have a trade in, add it as well if (!empty($trade_in_type) && !empty($trade_in_batt)) { $ti_batt_obj = $this->em->getRepository(Battery::class)->find($trade_in_batt); if (!empty($ti_batt_obj)) { $battery_size = $ti_batt_obj->getSize(); $icrit->addTradeInEntry($battery_size, $trade_in_type, 1); } } // set taxable $icrit->setIsTaxable(); // set JO source $icrit->setSource(TransactionOrigin::MOBILE_APP); // set price tier $pt_id = $pt_manager->getPriceTier($jo->getCoordinates()); $icrit->setPriceTier($pt_id); // send to invoice generator $invoice = $ic->generateInvoice($icrit); $jo->setInvoice($invoice); // set more hub criteria fields $hub_criteria = new HubCriteria(); $hub_criteria->setPoint($jo->getCoordinates()); // get distance limit for mobile from env $limit_distance = $_ENV['CUST_DISTANCE_LIMIT']; // set distance limit $hub_criteria->setLimitDistance($limit_distance); 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); } // 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); $assigned_rider = null; 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) { 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); // 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 { // 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 } } } $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', ]; $mclientv2->sendEvent($jo, $payload); $fcmclient->sendJoEvent($jo, "jo_fcm_title_outlet_assign", "jo_fcm_body_outlet_assign"); $rah->assignJobOrder($jo, $jo->getRider()); } $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; // 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('session_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 $validity = $this->validateRequest($req); if (!$validity['is_valid']) { return new ApiResponse(false, $validity['error']); } // 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'), 'date_schedule' => $jo->getDateSchedule()->format('M d, Y H:i'), 'service_type' => $jo->getServiceType(), 'destination' => [ 'long' => $dest->getLongitude(), 'lat' => $dest->getLatitude(), ], 'delivery_address' => $jo->getDeliveryAddress(), 'delivery_instructions' => $jo->getDeliveryInstructions(), 'landmark' => $jo->getLandmark(), '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(), 'curr_rating' => $rider->getCurrentRating(), 'image_url' => $image_url, 'location' => [ 'long' => $coord->getLongitude(), 'lat' => $coord->getLatitude() ] ]; // check if jo has rider rating set to true $has_rider_rating = $jo->hasRiderRating(); $rating = 0; $comment = ''; if ($rider != null) { // 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; } else { $jo_data['rider'] = null; $jo_data['has_rider_rating'] = null; $jo_data['rider_rating'] = null; $jo_data['comment'] = 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; } }