diff --git a/config/services.yaml b/config/services.yaml index 12c32894..6af4a5d3 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -16,6 +16,7 @@ parameters: android_app_version: "%env(ANDROID_APP_VERSION)%" ios_app_version: "%env(IOS_APP_VERSION)%" insurance_premiums_banner_url: "%env(INSURANCE_PREMIUMS_BANNER_URL)%" + enabled_hub_filters: "%env(ENABLED_HUB_FILTERS)%" services: # default configuration for services in *this* file @@ -322,3 +323,36 @@ services: App\Service\PriceTierManager: arguments: $em: "@doctrine.orm.entity_manager" + + # hub filters + App\Service\HubFilter\BaseHubFilter: + arguments: + $hub_filter_logger: "@App\\Service\\HubFilterLogger" + $em: "@doctrine.orm.entity_manager" + $rt: "@App\\Service\\RisingTideGateway" + $trans: "@Symfony\\Contracts\\Translation\\TranslatorInterface" + + App\Service\HubFilter\Filters\DateAndTimeHubFilter: + public: true + + App\Service\HubFilter\Filters\JoTypeHubFilter: + public: true + + App\Service\HubFilter\Filters\MaxResultsHubFilter: + public: true + + App\Service\HubFilter\Filters\PaymentMethodHubFilter: + public: true + + App\Service\HubFilter\Filters\RiderAvailabilityHubFilter: + public: true + + App\Service\HubFilter\Filters\InventoryHubFilter: + public: true + arguments: + $im: "@App\\Service\\InventoryManager" + + App\Service\HubFilter\Filters\RoundRobinHubFilter: + public: true + arguments: + $hub_distributor: "@App\\Service\\HubDistributor" diff --git a/src/Controller/CustomerAppAPI/JobOrderController.php b/src/Controller/CustomerAppAPI/JobOrderController.php index fa5a9a85..4f5c95ae 100644 --- a/src/Controller/CustomerAppAPI/JobOrderController.php +++ b/src/Controller/CustomerAppAPI/JobOrderController.php @@ -488,6 +488,8 @@ class JobOrderController extends ApiController JobOrderManager $jo_manager, PriceTierManager $pt_manager ) { + //error_log("CREATING JOB ORDER WITH PARAMS " . print_r($req->request->all(), true)); + // validate params $validity = $this->validateRequest($req, [ 'service_type', @@ -575,6 +577,8 @@ class JobOrderController extends ApiController $flag_advance_order = true; // $flag_advance_order = $advance_order ? true : false; + //error_log("RUNNING QUERY NEXT"); + $jo = new JobOrder(); $jo->setSource(TransactionOrigin::MOBILE_APP) ->setStatus(JOStatus::PENDING) @@ -643,6 +647,8 @@ class JobOrderController extends ApiController $icrit->addPromo($promo); } + //error_log("CONTINUING QUERY BUILDING"); + // check customer vehicle $cv = $this->em->getRepository(CustomerVehicle::class)->find($req->request->get('cv_id')); if ($cv == null) { @@ -708,13 +714,30 @@ class JobOrderController extends ApiController $invoice = $ic->generateInvoice($icrit); $jo->setInvoice($invoice); + //error_log("GENERATED INVOICE"); + + // save here first so we have a JO ID which is required for the hub selector + $this->em->persist($invoice); + $this->em->persist($jo); + $this->em->flush(); + // assign hub and rider // check if hub is null if ($hub == null) { + //error_log("NO HUB"); + // TODO: need to factor out the setting of HubCriteria fields $hub_criteria = new HubCriteria(); $hub_criteria->setPoint($jo->getCoordinates()); + // set job order info + $hub_criteria->setJobOrderId($jo->getID()) + ->setJoType($jo->getServiceType()) + ->setJoOrigin($jo->getSource()) + ->setCustomerClass($cust->getCustomerClassification()) + ->setOrderDate($jo->getDateCreate()) + ->setServiceType($jo->getServiceType()); + // get distance limit for mobile from env // get value of hub_filter_enable from env $limit_distance = $_ENV['CUST_DISTANCE_LIMIT']; @@ -752,6 +775,10 @@ class JobOrderController extends ApiController $hub_criteria->setCustomerId($customer_id); + // set filter flags for inventory and available riders + $hub_criteria->setInventoryCheck(); + $hub_criteria->setRidersCheck(); + // find nearest hubs $nearest_hubs = $hub_select->find($hub_criteria); @@ -830,6 +857,8 @@ class JobOrderController extends ApiController } } } else { + //error_log("HAS HUB: " . $hub->getID()); + $jo->setHub($hub); $jo->setStatus(JOStatus::RIDER_ASSIGN); $jo->setStatusAutoAssign(AutoAssignStatus::HUB_ASSIGNED); @@ -841,8 +870,10 @@ class JobOrderController extends ApiController $hub_dist->incrementJoCountForHub($hub); } + //error_log("DONE SELECTING HUB"); + + // save additional hub related changes $this->em->persist($jo); - $this->em->persist($invoice); // add event log for JO $event = new JOEvent(); @@ -953,6 +984,8 @@ class JobOrderController extends ApiController } } + //error_log("DONE CREATING JOB ORDER " . $jo->getID()); + // response return new ApiResponse(true, '', [ 'jo_id' => $jo->getID(), diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index 17c62a45..29ef66d5 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -761,7 +761,7 @@ class JobOrderController extends Controller $lat = $req->request->get('coord_lat', 0); $price_tier = 0; - if (($lng != 0) && ($lat != 0)) + if (!empty($lng) && !empty($lat)) { $coordinates = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); $price_tier = $pt_manager->getPriceTier($coordinates); diff --git a/src/Controller/ReportController.php b/src/Controller/ReportController.php index e5309afb..5d05b7f8 100644 --- a/src/Controller/ReportController.php +++ b/src/Controller/ReportController.php @@ -248,7 +248,7 @@ class ReportController extends Controller JORejectionReason::getName($jor->getReason()), $jor->getContactPerson(), $jor->getRemarks(), - $dispatched_by, + !empty($jor->getUser()) ? $jor->getUser()->getFullName() : "", ServiceType::getName($jo->getServiceType()), ]; } diff --git a/src/Controller/TAPI/JobOrderController.php b/src/Controller/TAPI/JobOrderController.php index ae4d5a61..cedf0320 100644 --- a/src/Controller/TAPI/JobOrderController.php +++ b/src/Controller/TAPI/JobOrderController.php @@ -239,6 +239,10 @@ class JobOrderController extends ApiController // find nearest hubs $nearest_hubs = $hub_select->find($hub_criteria); + // set filter flags for inventory and available riders + $hub_criteria->setInventoryCheck(); + $hub_criteria->setRidersCheck(); + if (!empty($nearest_hubs)) { // go through the hub list, find the nearest hub diff --git a/src/Entity/Hub.php b/src/Entity/Hub.php index da3a44b9..7fba62c4 100644 --- a/src/Entity/Hub.php +++ b/src/Entity/Hub.php @@ -114,7 +114,9 @@ class Hub public function getAvailableRiders() { $crit = Criteria::create(); - $crit->where(Criteria::expr()->eq('flag_available', true)); + $crit->where(Criteria::expr()->eq('flag_available', true)) + ->where(Criteria::expr()->eq('flag_active', true)) + ->where(Criteria::expr()->eq('current_job_order', null)); return $this->riders->matching($crit); } diff --git a/src/Entity/SupportedArea.php b/src/Entity/SupportedArea.php index 0e176f6a..c9cd52bf 100644 --- a/src/Entity/SupportedArea.php +++ b/src/Entity/SupportedArea.php @@ -39,6 +39,12 @@ class SupportedArea */ protected $coverage_area; + // prevent certain hub filters from being used + /** + * @ORM\Column(type="json", nullable=true) + */ + protected $hub_filter_exceptions; + /** * @ORM\ManyToOne(targetEntity="PriceTier", inversedBy="supported_areas") * @ORM\JoinColumn(name="price_tier_id", referencedColumnName="id", nullable=true) @@ -50,6 +56,7 @@ class SupportedArea $this->date_create = new DateTime(); $this->price_tier = null; + $this->hub_filter_exceptions = []; } public function getID() @@ -65,7 +72,7 @@ class SupportedArea public function getDateCreate() { - return $this->date_Create; + return $this->date_create; } public function setName($name) @@ -101,5 +108,16 @@ class SupportedArea { return $this->price_tier; } + + public function setHubFilterExceptions($exceptions) + { + $this->hub_filter_exceptions = $exceptions; + return $this; + } + + public function getHubFilterExceptions() + { + return $this->hub_filter_exceptions; + } } diff --git a/src/Ramcar/DeliveryStatus.php b/src/Ramcar/DeliveryStatus.php index b4d06871..c567032f 100644 --- a/src/Ramcar/DeliveryStatus.php +++ b/src/Ramcar/DeliveryStatus.php @@ -23,8 +23,8 @@ class DeliveryStatus extends NameValue const COLLECTION = [ 'rider_assign' => 'Assigned Rider', 'requeue' => 'Requeue', - 'accept' => 'Rider Accept', - 'arrive' => 'Rider Arrive', + 'rider_accept' => 'Rider Accept', + 'rider_arrive' => 'Rider Arrive', 'rider_edit' => 'Rider Edit', 'rider_depart_hub' => 'Rider Depart Hub', 'rider_arrive_hub_pre_jo' => 'Rider Arrive Hub Pre JO', diff --git a/src/Ramcar/HubCriteria.php b/src/Ramcar/HubCriteria.php index 5a663551..3f7668d5 100644 --- a/src/Ramcar/HubCriteria.php +++ b/src/Ramcar/HubCriteria.php @@ -12,6 +12,7 @@ class HubCriteria protected $limit_results; // number of results to return protected $limit_distance; // distance limit for search in km protected $flag_inventory_check; // flag if we need to check for inventory + protected $flag_riders_check; // flag if we need to check for riders available protected $jo_type; // jo service needed protected $date_time; // date and time to check if hub is open or not protected $items; // array of items: items[sku] = quantity to check for @@ -20,6 +21,10 @@ class HubCriteria protected $flag_round_robin; // flag if we use round robin or not protected $jo_id; // JO id. This is null if called from mobile API protected $customer_id; // customer id + protected $customer_class; // customer class + protected $order_date; // date JO was created + protected $service_type; // service type of JO + protected $jo_origin; // origin of JO public function __construct() { @@ -29,12 +34,17 @@ class HubCriteria $this->jo_type = ''; $this->date_time = new DateTime(); $this->flag_inventory_check = false; + $this->flag_riders_check = false; $this->items = []; $this->payment_method = ''; - $flag_emergency = false; - $flag_round_robin = false; - $jo_id = null; - $customer_id = null; + $this->flag_emergency = false; + $this->flag_round_robin = false; + $this->jo_id = null; + $this->customer_id = null; + $this->customer_class = null; + $this->order_date = new DateTime(); + $this->service_type = null; + $this->jo_origin = null; } public function setPoint(Point $point) @@ -81,6 +91,17 @@ class HubCriteria return $this->flag_inventory_check; } + public function setRidersCheck($flag_riders_check = true) + { + $this->flag_riders_check = $flag_riders_check; + return $this; + } + + public function hasRidersCheck() + { + return $this->flag_riders_check; + } + public function setJoType($jo_type) { // TODO: validate the jo type @@ -171,5 +192,48 @@ class HubCriteria return $this->customer_id; } + public function setCustomerClass($customer_class) + { + $this->customer_class = $customer_class; + return $this; + } + + public function getCustomerClass() + { + return $this->customer_class; + } + + public function setOrderDate($order_date) + { + $this->order_date = $order_date; + return $this; + } + + public function getOrderDate() + { + return $this->order_date; + } + + public function setServiceType($service_type) + { + $this->service_type = $service_type; + return $this; + } + + public function getServiceType() + { + return $this->service_type; + } + + public function setJoOrigin($jo_origin) + { + $this->jo_origin = $jo_origin; + return $this; + } + + public function getJoOrigin() + { + return $this->jo_origin; + } } diff --git a/src/Ramcar/JORejectionReason.php b/src/Ramcar/JORejectionReason.php index 9dee1217..37d30c44 100644 --- a/src/Ramcar/JORejectionReason.php +++ b/src/Ramcar/JORejectionReason.php @@ -4,17 +4,24 @@ namespace App\Ramcar; class JORejectionReason extends NameValue { - const ADMINISTRATIVE = 'administrative'; - const NO_STOCK_SALES = 'no_stock_sales'; - const NO_STOCK_SERVICE = 'no_stock_service'; - const LINE_NO_ANSWER = 'line_no_answer'; - const LINE_BUSY = 'line_busy'; - const NO_RIDER_AVAILABLE = 'no_rider_available'; - const NO_RIDER_IN_TRANSIT = 'no_rider_in_transit'; - const REFUSAL = 'refusal'; - const STORE_CLOSED = 'store_closed'; - const NO_CREDIT_CARD = 'no_credit_card'; - const DISCOUNT = 'discount'; + const ADMINISTRATIVE = 'administrative'; + const NO_STOCK_SALES = 'no_stock_sales'; + const NO_STOCK_SERVICE = 'no_stock_service'; + const LINE_NO_ANSWER = 'line_no_answer'; + const LINE_BUSY = 'line_busy'; + const NO_RIDER_AVAILABLE = 'no_rider_available'; + const NO_RIDER_IN_TRANSIT = 'no_rider_in_transit'; + const REFUSAL = 'refusal'; + const STORE_CLOSED = 'store_closed'; + const NO_CREDIT_CARD = 'no_credit_card'; + const DISCOUNT = 'discount'; + const STORE_CLOSED_SCHEDULED = 'store_closed_scheduled'; + const STORE_CLOSED_HALF_DAY = 'store_closed_half_day'; + const STORE_CLOSED_NO_ADVISE = 'store_closed_no_advise'; + const PRIORITY_HUB_WTY_CLAIM = 'priority_hub_wty_claim'; + const PRIORITY_HUB_JUMPSTART = 'priority_hub_jumpstart'; + const PRIORITY_HUB_RESQ_REQUEST = 'priority_hub_resq_req'; + const CUSTOMER_REQUEST = 'customer_request'; const COLLECTION = [ 'administrative' => 'ADMINISTRATIVE', @@ -28,5 +35,16 @@ class JORejectionReason extends NameValue 'store_closed' => 'STORE CLOSED', 'no_credit_card' => 'NO CREDIT CARD PAYMENT / NO TERMINAL', 'discount' => 'DISCOUNT', + 'store_closed_scheduled' => 'STORE CLOSED (ON SCHEDULE)', + 'store_closed_half_day' => 'STORE CLOSED (HALF DAY)', + 'store_closed_no_advise' => 'STORE CLOSED (NO ADVISE)', + 'priority_hub_wty_claim' => 'PRIORITY HUB OUTLET FOR WARRANTY CLAIM', + 'priority_hub_jumpstart' => 'PRIORITY HUB OUTLET FOR JUMPSTART', + 'priority_hub_resq_req' => 'PRIORITY HUB OUTLET FOR RESQ-Q REQUEST', + 'customer_request' => 'CUSTOMER REQUEST', + ]; + + const BLACKLIST = [ + self::ADMINISTRATIVE => true, ]; } diff --git a/src/Ramcar/NameValue.php b/src/Ramcar/NameValue.php index 58b6d75e..99366a8d 100644 --- a/src/Ramcar/NameValue.php +++ b/src/Ramcar/NameValue.php @@ -4,9 +4,21 @@ namespace App\Ramcar; class NameValue { + const BLACKLIST = []; + static public function getCollection() { - return static::COLLECTION; + $result = []; + $blacklist = static::getBlacklist(); + + // filter from blacklist + foreach(static::COLLECTION as $key => $row) { + if (!isset($blacklist[$key])) { + $result[$key] = $row; + } + } + + return $result; } static public function validate($value) @@ -24,4 +36,9 @@ class NameValue return 'Unknown'; } + + static public function getBlacklist() + { + return static::BLACKLIST ?? []; + } } diff --git a/src/Service/HubDistributor.php b/src/Service/HubDistributor.php index 27a70f15..b1d20ad6 100644 --- a/src/Service/HubDistributor.php +++ b/src/Service/HubDistributor.php @@ -81,6 +81,7 @@ class HubDistributor 'distance' => $hub_data['distance'], 'duration' => $hub_data['duration'], 'jo_count' => $hub_jo_count, + 'inventory' => $hub_data['inventory'] ?? 0, ]; } else @@ -91,6 +92,7 @@ class HubDistributor 'distance' => $hub_data['distance'], 'duration' => $hub_data['duration'], 'jo_count' => 0, + 'inventory' => $hub_data['inventory'] ?? 0, ]; } } diff --git a/src/Service/HubFilter/BaseHubFilter.php b/src/Service/HubFilter/BaseHubFilter.php new file mode 100644 index 00000000..81f75b7d --- /dev/null +++ b/src/Service/HubFilter/BaseHubFilter.php @@ -0,0 +1,144 @@ +hub_filter_logger = $hub_filter_logger; + $this->em = $em; + $this->rt = $rt; + $this->trans = $trans; + + error_log("-------------------"); + error_log("HUB FILTER RUNNING: " . $this->getID()); + error_log("-------------------"); + } + + public function getID(): string + { + return $this->id; + } + + public function setJOID(int $jo_id) + { + $this->jo_id = $jo_id; + return $this; + } + + public function getJOID(): int + { + return $this->jo_id; + } + + public function setCustomerID(int $customer_id) + { + $this->customer_id = $customer_id; + return $this; + } + + public function getCustomerID(): int + { + return $this->customer_id; + } + + public function log(Hub $hub): void + { + $this->hub_filter_logger->logFilteredHub($hub, $this->getID(), $this->getJOID(), $this->getCustomerID()); + + // log to file + $filename = '/../../../var/log/hub_rejection.log'; + $date = date("Y-m-d H:i:s"); + + // build log entry + $entry = implode("", [ + "[JO: " . $this->getJOID() . "]", + "[" . $date . "]", + "[" . $this->getID() . "]", + " " . $hub->getName() . " (ID: " . $hub->getID() . ")", + "\r\n", + ]); + + @file_put_contents(__DIR__ . $filename, $entry, FILE_APPEND); + } + + protected function createRejectionEntry($hub, $reason, $remarks = ""): JORejection + { + $jo = $this->em->getRepository(JobOrder::class)->find($this->getJOID()); + + $robj = new JORejection(); + $robj->setDateCreate(new DateTime()) + ->setHub($hub) + ->setJobOrder($jo) + ->setReason($reason) + ->setRemarks(implode(" ", ["Automatically filtered by hub selector.", $remarks])); + + $this->em->persist($robj); + $this->em->flush(); + + return $robj; + } + + protected function sendSMSMessage($hub, $order_date, $service_type, $rejection, $reason = "", $remarks = ""): void + { + $jo_id = $this->getJOID(); + + // check if we already have a rejection record for this hub and JO. this also means an SMS was already sent + $rejection_count = $this->em->createQueryBuilder() + ->select('count(r)') + ->from(JORejection::class, 'r') + ->where('r.job_order = :jo_id') + ->andWhere('r.hub = :hub_id') + ->andWhere('r.id != :rejection_id') + ->setParameter('jo_id', $jo_id) + ->setParameter('hub_id', $hub->getID()) + ->setParameter('rejection_id', $rejection->getID()) + ->getQuery() + ->getSingleScalarResult(); + + // if we already have a rejection record for this hub and JO, do not send another SMS + if ($rejection_count >= 1) { + error_log("ALREADY SENT REJECTION SMS TO HUB " . $hub->getID() . " FOR JO " . $jo_id); + return; + } + + $message = 'Job Order #: ' . $jo_id . "\n" . + 'Order Date and Time: ' . $order_date->format('d M Y g:i A') . "\n" . + 'Date and Time Rejected: ' . $rejection->getDateCreate()->format('d M Y g:i A') . "\n" . + 'Enrollee Name: ' . implode(" - ", [$hub->getName(), $hub->getBranch()]) . "\n" . + 'Reason of Rejection: ' . $reason . "\n" . + 'Remarks: ' . $remarks . "\n" . + 'Type of Service: ' . ServiceType::getName($service_type); + + error_log("SENDING SMS MESSAGE:\r\n" . $message); + + // send SMS message to hub + $this->rt->sendSMS( + $hub->getNotifNumber(), + $this->trans->trans('message.battery_brand_allcaps'), + $message + ); + } +} \ No newline at end of file diff --git a/src/Service/HubFilter/Filters/DateAndTimeHubFilter.php b/src/Service/HubFilter/Filters/DateAndTimeHubFilter.php new file mode 100644 index 00000000..67097320 --- /dev/null +++ b/src/Service/HubFilter/Filters/DateAndTimeHubFilter.php @@ -0,0 +1,58 @@ +getTimeOpen()->format("H:i:s"); + $time_close = $hub->getTimeClose()->format("H:i:s"); + + $filter_time = $params['date_time']->format("H:i:s"); + + if (($filter_time >= $time_open) && + ($filter_time <= $time_close)) + { + $results[] = [ + 'hub' => $hub, + 'db_distance' => $hub_data['db_distance'], + 'distance' => $hub_data['distance'], + 'duration' => $hub_data['duration'], + 'jo_count' => 0, + 'inventory' => $hub_data['inventory'], + ]; + } + else + $this->log($hub); + } + + return $results; + } +} \ No newline at end of file diff --git a/src/Service/HubFilter/Filters/InventoryHubFilter.php b/src/Service/HubFilter/Filters/InventoryHubFilter.php new file mode 100644 index 00000000..d7526d0f --- /dev/null +++ b/src/Service/HubFilter/Filters/InventoryHubFilter.php @@ -0,0 +1,164 @@ +im = $im; + } + + public function getRequestedParams() : array + { + return [ + 'flag_inventory_check', + 'customer_class', + 'jo_type', + 'jo_origin', + 'order_date', + 'service_type', + 'items', + ]; + } + + public function filter(array $hubs, array $params = []) : array + { + // check if this is enabled + if (!$params['flag_inventory_check']) { + error_log("INVENTORY CHECK " . $this->getJOID() . ": DISABLED"); + return $hubs; + } + + // check customer class + if ((!empty($params['customer_class']) && $params['customer_class'] == CustomerClassification::VIP) || + $params['jo_origin'] === TransactionOrigin::VIP) { + error_log("INVENTORY CHECK " . $this->getJOID() . ": VIP CLASS"); + return $hubs; + } + + // check item list is not empty + if (empty($params['items'])) { + error_log("INVENTORY CHECK " . $this->getJOID() . ": NO ITEMS"); + return $hubs; + } + + // check this is a battery item related JO + if ($params['jo_type'] != ServiceType::BATTERY_REPLACEMENT_NEW && + $params['jo_type'] != ServiceType::BATTERY_REPLACEMENT_WARRANTY + ) { + error_log("INVENTORY CHECK " . $this->getJOID() . ": INVALID SERVICE TYPE: " . $params['jo_type']); + return $hubs; + } + + // get a list of all hubs with branch codes + $branch_codes = []; + foreach ($hubs as $hub_data) { + $branch_code = $hub_data['hub']->getBranchCode(); + if (!empty($branch_code)) { + $branch_codes[] = $branch_code; + } + }; + + $hubs_to_filter = []; + $results = []; + $qtys = []; + + // call inventory manager for all hubs for selected SKUs + $skus = array_keys($params['items']); + + error_log("CHECKING INVENTORY FOR " . count($skus) . " ITEM(S) ON HUBS " . count($branch_codes) . "..."); + + $branches = $this->im->getBranchesInventory($branch_codes, $skus); + + error_log("REQUEST COMPLETE, RESULT COUNT: " . count($branches)); + + // check each result to see if sufficient quantity exists to meet request + foreach ($branches as $branch) { + if (isset($branch['BranchCode'])) { + // filter out branch if it does not have sufficient inventory + if (!isset($params['items'][$branch['SapCode']]) || $branch['Quantity'] < $params['items'][$branch['SapCode']] && + !isset($hubs_to_filter[$branch['BranchCode']]) + ) { + error_log("FILTERING BRANCH WITH NO INVENTORY: " . $branch['BranchCode']); + $hubs_to_filter[$branch['BranchCode']] = true; + } else { + // save inventory count so we don't have to recheck later + $qtys[$branch['BranchCode']] = $branch['Quantity']; + } + } + } + + // get battery models for each requested SKU + $batteries = []; + foreach ($skus as $sku) { + $bobj = $this->em->getRepository(Battery::class)->findOneBy(['sap_code' => $sku]); + $batteries[] = implode(" ", [$bobj->getModel()->getName(), $bobj->getSize()->getName()]); + } + $battery_string = implode(", ", $batteries); + + // remove filtered hubs from list + foreach ($hubs as $hub_data) { + $hub = $hub_data['hub']; + $branch_code = $hub_data['hub']->getBranchCode(); + + // check if we are filtering this hub + if (isset($hubs_to_filter[$branch_code]) || empty($branch_code) || !isset($qtys[$branch_code])) { + // if we have a JO, create rejection record and notify + $jo_id = $this->getJOID(); + + if (!empty($jo_id)) { + // create rejection report entry + $robj = $this->createRejectionEntry( + $hub, + JORejectionReason::NO_STOCK_SALES, + "SKU(s): " . $battery_string, + ); + + // build SMS message + $this->sendSMSMessage( + $hub, + $params['order_date'], + $params['service_type'], + $robj, + JORejectionReason::getName(JORejectionReason::NO_STOCK_SALES), + "Requested SKU(s) - " . $battery_string + ); + } + + // log this filter + $this->log($hub); + + error_log("FILTERED HUB " . $hub->getID() . " (no_inventory)"); + } else { + // include inventory in hub data + $hub_data['inventory'] = $qtys[$branch_code]; + + // we only include branches with branch codes and quantities + $results[] = $hub_data; + } + } + + // return filtered hubs + return $results; + } +} \ No newline at end of file diff --git a/src/Service/HubFilter/Filters/JoTypeHubFilter.php b/src/Service/HubFilter/Filters/JoTypeHubFilter.php new file mode 100644 index 00000000..01ed733f --- /dev/null +++ b/src/Service/HubFilter/Filters/JoTypeHubFilter.php @@ -0,0 +1,52 @@ + $hub, + 'db_distance' => $hub_data['db_distance'], + 'distance' => $hub_data['distance'], + 'duration' => $hub_data['duration'], + 'jo_count' => 0, + 'inventory' => $hub_data['inventory'], + ]; + else + $this->log($hub); + } + + return $results; + } +} \ No newline at end of file diff --git a/src/Service/HubFilter/Filters/MaxResultsHubFilter.php b/src/Service/HubFilter/Filters/MaxResultsHubFilter.php new file mode 100644 index 00000000..16c1b514 --- /dev/null +++ b/src/Service/HubFilter/Filters/MaxResultsHubFilter.php @@ -0,0 +1,35 @@ +log($hubs[$i]['hub']); + } + + return $results; + } +} \ No newline at end of file diff --git a/src/Service/HubFilter/Filters/PaymentMethodHubFilter.php b/src/Service/HubFilter/Filters/PaymentMethodHubFilter.php new file mode 100644 index 00000000..8af6705b --- /dev/null +++ b/src/Service/HubFilter/Filters/PaymentMethodHubFilter.php @@ -0,0 +1,63 @@ +getPaymentMethods(); + if ($payment_methods != null) + { + $flag_found_pmethod = false; + foreach ($payment_methods as $pmethod) + { + if ($pmethod == $params['payment_method']) + { + $results[] = [ + 'hub' => $hub, + 'db_distance' => $hub_data['db_distance'], + 'distance' => $hub_data['distance'], + 'duration' => $hub_data['duration'], + 'jo_count' => 0, + 'inventory' => $hub_data['inventory'], + ]; + } + $flag_found_pmethod = true; + } + + if (!$flag_found_pmethod) + $this->log($hub); + } + else + $this->log($hub); + } + + return $results; + } +} \ No newline at end of file diff --git a/src/Service/HubFilter/Filters/RiderAvailabilityHubFilter.php b/src/Service/HubFilter/Filters/RiderAvailabilityHubFilter.php new file mode 100644 index 00000000..d2697feb --- /dev/null +++ b/src/Service/HubFilter/Filters/RiderAvailabilityHubFilter.php @@ -0,0 +1,75 @@ +getJOID() . ": VIP CLASS"); + return $hubs; + } + + $results = []; + + foreach ($hubs as $hub_data) { + $hub = $hub_data['hub']; + + $available_riders = count($hub->getAvailableRiders()); + // check we have available riders + error_log("TOTAL RIDERS: " . $available_riders); + if ($available_riders === 0) { + // if we have a JO, create rejection record and notify + $jo_id = $this->getJOID(); + + if (!empty($jo_id)) { + // create rejection report entry + $robj = $this->createRejectionEntry($hub, JORejectionReason::NO_RIDER_AVAILABLE); + + // build SMS message + $this->sendSMSMessage( + $hub, + $params['order_date'], + $params['service_type'], + $robj, + JORejectionReason::getName(JORejectionReason::NO_RIDER_AVAILABLE), + ); + } + + // log this filter + $this->log($hub); + + error_log("FILTERED HUB " . $hub->getID() . " (no_available_rider)"); + } else { + $results[] = $hub_data; + } + } + + return $results; + } +} \ No newline at end of file diff --git a/src/Service/HubFilter/Filters/RoundRobinHubFilter.php b/src/Service/HubFilter/Filters/RoundRobinHubFilter.php new file mode 100644 index 00000000..235293a9 --- /dev/null +++ b/src/Service/HubFilter/Filters/RoundRobinHubFilter.php @@ -0,0 +1,46 @@ +hub_distributor = $hub_distributor; + } + + public function getRequestedParams() : array + { + return [ + 'flag_round_robin', + ]; + } + + public function filter(array $hubs, array $params = []) : array + { + if (!$params['flag_round_robin']) + return $hubs; + + $results = []; + + // call hub distributor service + $arranged_hubs = $this->hub_distributor->arrangeHubs($hubs); + $results = $arranged_hubs; + + return $results; + } +} \ No newline at end of file diff --git a/src/Service/HubFilter/HubFilterInterface.php b/src/Service/HubFilter/HubFilterInterface.php new file mode 100644 index 00000000..f3b9d2ab --- /dev/null +++ b/src/Service/HubFilter/HubFilterInterface.php @@ -0,0 +1,20 @@ +container = $container; $this->em = $em; $this->im = $im; $this->hub_distributor = $hub_distributor; @@ -39,6 +39,18 @@ class HubSelector $this->rt = $rt; } + protected function getActiveFilters(): array + { + $fnames = explode(",", $this->container->getParameter('enabled_hub_filters')); + $enabled_filters = []; + + foreach ($fnames as $filter) { + $enabled_filters[] = 'App\\Service\\HubFilter\\Filters\\' . $filter; + } + + return $enabled_filters; + } + public function find(HubCriteria $criteria) { $point = $criteria->getPoint(); @@ -46,15 +58,20 @@ class HubSelector $limit_distance = $criteria->getLimitDistance(); $jo_type = $criteria->getJoType(); $flag_inventory_check = $criteria->hasInventoryCheck(); + $flag_riders_check = $criteria->hasRidersCheck(); $items = $criteria->getItems(); $date_time = $criteria->getDateTime(); $payment_method = $criteria->getPaymentMethod(); $flag_emergency = $criteria->isEmergency(); $flag_round_robin = $criteria->isRoundRobin(); $jo_id = $criteria->getJobOrderId(); + $jo_origin = $criteria->getJoOrigin(); $customer_id = $criteria->getCustomerId(); + $customer_class = $criteria->getCustomerClass(); - $results = []; + // needed for JORejection records and SMS notifs + $order_date = $criteria->getOrderDate(); + $service_type = $criteria->getServiceType(); // error_log('payment methods ' . $payment_method); // error_log('distance limit ' . $limit_distance); @@ -63,278 +80,52 @@ class HubSelector // get all the hubs within distance $filtered_hubs = $this->getClosestHubs($point, $limit_distance, $jo_id, $customer_id); - // error_log('closest hubs ' . json_encode($filtered_hubs)); + // build param list + $params = [ + 'date_time' => $date_time, + 'flag_inventory_check' => $flag_inventory_check, + 'customer_class' => $customer_class, + 'jo_type' => $jo_type, + 'jo_origin' => $jo_origin, + 'order_date' => $order_date, + 'service_type' => $service_type, + 'items' => $items, + 'flag_emergency' => $flag_emergency, + 'limit_results' => $limit_results, + 'payment_method' => $payment_method, + 'flag_riders_check' => $flag_riders_check, + 'flag_round_robin' => $flag_round_robin, + ]; - // filter the first hub results for date and opening times - $hubs_date_time = $this->filterHubsByDateAndTime($filtered_hubs, $date_time, $jo_id, $customer_id); - $filtered_hubs = $hubs_date_time; - - // error_log('date_time hubs ' . json_encode($filtered_hubs)); - - if (!$flag_emergency) - { - // filter jo types - $hubs_jo_type = $this->filterHubsByJoType($filtered_hubs, $jo_type, $jo_id, $customer_id); - $filtered_hubs = $hubs_jo_type; - - //error_log('jo_type hubs ' . json_encode($filtered_hubs)); - - // filter hubs by payment methods - $hubs_payment_method = $this->filterHubsByPaymentMethod($filtered_hubs, $payment_method, $jo_id, $customer_id); - $filtered_hubs = $hubs_payment_method; - - //error_log('payment hubs ' . json_encode($filtered_hubs)); - - // inventory filter - $hubs_inventory = $this->filterHubsByInventory($filtered_hubs, $flag_inventory_check, - $jo_type, $items, $jo_id, $customer_id); - $filtered_hubs = $hubs_inventory; - - //error_log('inventory hubs ' . json_encode($filtered_hubs)); - - // round robin filter - $hubs_round_robin = $this->filterHubsByRoundRobin($filtered_hubs, $flag_round_robin); - $filtered_hubs = $hubs_round_robin; - - // error_log('round robin hubs ' . json_encode($filtered_hubs)); - - // max results filter - $hubs_max_result = $this->filterHubsByMaxResults($filtered_hubs, $limit_results, $jo_id, $customer_id); - $filtered_hubs = $hubs_max_result; - } - - $results = $filtered_hubs; - - // error_log(json_encode($results)); - - return $results; - } - - protected function filterHubsByRoundRobin($hubs, $flag_round_robin) - { - if (empty($hubs)) - return $hubs; - if (!$flag_round_robin) - return $hubs; - - $results = []; - // call hub distributor service - $arranged_hubs = $this->hub_distributor->arrangeHubs($hubs); - $results = $arranged_hubs; - - return $results; - - } - - protected function filterHubsByMaxResults($hubs, $limit_result, $jo_id, $customer_id) - { - if (empty($hubs)) - return $hubs; - if (empty($limit_result)) - return $hubs; - - $results = []; - for ($i = 0; $i < count($hubs); $i++) - { - if ($i < $limit_result) - $results[] = $hubs[$i]; - else - $this->hub_filter_logger->logFilteredHub($hubs[$i]['hub'], 'max_results', $jo_id, $customer_id); - } - - return $results; - } - - protected function filterHubsByJoType($hubs, $jo_type, $jo_id, $customer_id) - { - if (empty($hubs)) - return $hubs; - if (empty($jo_type)) - return $hubs; - - $results = []; - foreach ($hubs as $hub_data) - { - $hub = $hub_data['hub']; - - // TODO: for now, have this return true - $has_jo_type = true; - // check if hub offers the jo_type - // TODO: add service to hub - if ($has_jo_type) - $results[] = [ - 'hub' => $hub, - 'db_distance' => $hub_data['db_distance'], - 'distance' => $hub_data['distance'], - 'duration' => $hub_data['duration'], - 'jo_count' => 0, - ]; - else - $this->hub_filter_logger->logFilteredHub($hub, 'job_order_type', $jo_id, $customer_id); - } - - return $results; - } - - protected function filterHubsByPaymentMethod($hubs, $payment_method, $jo_id, $customer_id) - { - if (empty($hubs)) - return $hubs; - if (empty($payment_method)) - return $hubs; - - $results = []; - foreach ($hubs as $hub_data) - { - $hub = $hub_data['hub']; - - // name of payment method is what is saved - $payment_methods = $hub->getPaymentMethods(); - if ($payment_methods != null) - { - $flag_found_pmethod = false; - foreach ($payment_methods as $pmethod) - { - if ($pmethod == $payment_method) - { - $results[] = [ - 'hub' => $hub, - 'db_distance' => $hub_data['db_distance'], - 'distance' => $hub_data['distance'], - 'duration' => $hub_data['duration'], - 'jo_count' => 0, - ]; - } - $flag_found_pmethod = true; - } - - if (!$flag_found_pmethod) - $this->hub_filter_logger->logFilteredHub($hub, 'no_payment_method', $jo_id, $customer_id); + // loop through all enabled filters + foreach ($this->getActiveFilters() as $hub_filter) { + // no hubs left to filter + if (empty($filtered_hubs)) { + break; } - else - $this->hub_filter_logger->logFilteredHub($hub, 'no_payment_method', $jo_id, $customer_id); - } - return $results; - } + $f = $this->container->get($hub_filter); - protected function filterHubsByDateAndTime($hubs, $date_time, $jo_id, $customer_id) - { - if (empty($hubs)) - return $hubs; - - if ($date_time == null) - return $hubs; - - $results = []; - - foreach ($hubs as $hub_data) - { - // go through each hub's opening times to check if hub is open - // for the specified time - // get hub opening and closing times - // TODO: maybe in the future, might also have to check if hub - // is open/available on date/day - $hub = $hub_data['hub']; - - $time_open = $hub->getTimeOpen()->format("H:i:s"); - $time_close = $hub->getTimeClose()->format("H:i:s"); - - $filter_time = $date_time->format("H:i:s"); - - if (($filter_time >= $time_open) && - ($filter_time <= $time_close)) - { - $results[] = [ - 'hub' => $hub, - 'db_distance' => $hub_data['db_distance'], - 'distance' => $hub_data['distance'], - 'duration' => $hub_data['duration'], - 'jo_count' => 0, - ]; + // check if supported area is exempted from this filter + if ($this->isExemptedByArea($f->getID(), $point)) { + continue; } - else - $this->hub_filter_logger->logFilteredHub($hub, 'date_and_time', $jo_id, $customer_id); - } - - return $results; - } - protected function filterHubsByInventory($hubs, $flag_inventory_check, $jo_type, $items, $jo_id, $customer_id) - { - if (empty($hubs)) - return $hubs; + $f->setJOID($jo_id); + $f->setCustomerID($customer_id); - if (!$flag_inventory_check) - return $hubs; + // get requested params only + $req_params = array_intersect_key($params, array_flip($f->getRequestedParams())); - $results = []; - if ($flag_inventory_check) - { - foreach ($hubs as $hub_data) - { - $hub = $hub_data['hub']; + // filter hub list + $filtered_hubs = $f->filter($filtered_hubs, $req_params); - if ($jo_type == ServiceType::BATTERY_REPLACEMENT_NEW) - { - // call inventory - $has_items = $this->checkInventory($items, $hub); - if ($has_items) - $results[] = [ - 'hub' => $hub, - 'db_distance' => $hub_data['db_distance'], - 'distance' => $hub_data['distance'], - 'duration' => $hub_data['duration'], - 'jo_count' => 0, - ]; - else - { - // get the skus for the message - $sku_text = ''; - foreach ($items as $key => $value) - { - $sku_text .= ' ' . $key; - } - // send SMS to hub - $message = str_replace('item_display', trim($sku_text), $this->trans->trans('no_inventory_message')); - // error_log($message); - $this->sendSMSMessage($hub, $items); - - $this->hub_filter_logger->logFilteredHub($hub, 'no_inventory', $jo_id, $customer_id); - } - } - if ($jo_type == ServiceType::BATTERY_REPLACEMENT_WARRANTY) - { - // call inventory - $has_items = $this->checkInventory($items, $hub); - if ($has_items) - $results[] = [ - 'hub' => $hub, - 'db_distance' => $hub_data['db_distance'], - 'distance' => $hub_data['distance'], - 'duration' => $hub_data['duration'], - 'jo_count' => 0, - ]; - else - { - // get the skus for the message - $sku_text = ''; - foreach ($items as $key => $value) - { - $sku_text .= ' ' . $key; - } - // send SMS to hub - $message = str_replace('item_display', trim($sku_text), $this->trans->trans('no_inventory_message')); - // error_log($message); - $this->sendSMSMessage($hub, $items); - - $this->hub_filter_logger->logFilteredHub($hub, 'no_inventory', $jo_id, $customer_id); - } - } - } + // error_log($f->getID() . ' hubs ' . json_encode($filtered_hubs)); } - return $results; + // error_log('final hub list ' . json_encode($filtered_hubs)); + + return $filtered_hubs; } protected function getClosestHubs(Point $point, $limit_distance, $jo_id, $customer_id) @@ -375,7 +166,11 @@ class HubSelector 'distance' => $dist, 'duration' => 0, 'jo_count' => 0, + 'inventory' => 0, ]; + + // log to file + $this->logClosestHubResult($jo_id, $row[0], $dist, $limit_distance); } else { @@ -386,45 +181,6 @@ class HubSelector return $hubs_data; } - protected function checkInventory($items, $hub) - { - // check if hub has all items - $skus = []; - $branch_codes[] = $hub->getBranchCode(); - $result = false; - - foreach ($items as $key=> $value) - { - // add sap code of item/battery into array since - // getBranchesInventory takes in an array of hubs/branches - // and an array of skus - // $items as format: $items[sku] = quantity - $skus[] = $key; - } - - // call InventoryManager's getBranchesInventory to check if hub has all items - $branches_with_items = $this->im->getBranchesInventory($branch_codes, $skus); - - if (!empty($branches_with_items)) - { - // check if branch has enough quantity for item - foreach ($branches_with_items as $branch) - { - // get quantity from call - $qty_available = $branch['Quantity']; - - // get the quantity request - $sku_requested = $branch['SapCode']; - $qty_requested = $items[$sku_requested]; - if ($qty_available >= $qty_requested) - $result = true; - } - } - - // return true or false - return $result; - } - // convert db distance to kilometers protected function distance($lat1, $lon1, $lat2, $lon2) { @@ -440,26 +196,50 @@ class HubSelector return round(($miles * 1.609344), 1); } - protected function sendSMSMessage($hub, $items) + protected function isExemptedByArea(string $filter_id, Point $coordinates): bool { - // compose message - // get the skus for the message - $sku_text = ''; - foreach ($items as $key => $value) - { - $sku_text .= ' ' . $key; - } - $message = str_replace('item_display', trim($sku_text), $this->trans->trans('no_inventory_message')); + $long = $coordinates->getLongitude(); + $lat = $coordinates->getLatitude(); - // get hub notification number - $mobile_number = $hub->getNotifNumber(); + // get supported area given a set of coordinates + $query = $this->em->createQuery('SELECT s from App\Entity\SupportedArea s where st_contains(s.coverage_area, point(:long, :lat)) = true'); + $area = $query->setParameter('long', $long) + ->setParameter('lat', $lat) + ->setMaxResults(1) + ->getOneOrNullResult(); - if (!empty($mobile_number)) - { - // send SMS message - // error_log('sending sms to - ' . $mobile_number); - $this->rt->sendSMS($mobile_number, $this->trans->trans('message.battery_brand_allcaps'), $message); + if ($area !== null) { + // get all exceptions + $exceptions = $area->getHubFilterExceptions(); + + if (isset($exceptions[$filter_id])) { + error_log("FILTER " . $filter_id . " DISABLED FOR AREA: " . $area->getName()); + + // disable this filter for this area + return true; + } } + + // filter is in place + return false; + } + + protected function logClosestHubResult($jo_id, $hub, $distance, $limit_distance) + { + // log to file + $filename = '/../../var/log/closest_hubs_selected.log'; + $date = date("Y-m-d H:i:s"); + + // build log entry + $entry = implode("", [ + "[JO: " . $jo_id . "]", + "[" . $date . "]", + "[Distance: " . $distance . " vs " . $limit_distance . "]", + " " . $hub->getName() . " (ID: " . $hub->getID() . ")", + "\r\n", + ]); + + @file_put_contents(__DIR__ . $filename, $entry, FILE_APPEND); } } diff --git a/src/Service/JobOrderHandler/ResqJobOrderHandler.php b/src/Service/JobOrderHandler/ResqJobOrderHandler.php index 419c27c3..b2d6fd68 100644 --- a/src/Service/JobOrderHandler/ResqJobOrderHandler.php +++ b/src/Service/JobOrderHandler/ResqJobOrderHandler.php @@ -95,7 +95,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface protected $hub_dist; protected $hub_geofence; protected $cust_distance_limit; - protected $hub_filter_enable; + protected $hub_filter_enabled; protected $jo_manager; protected $pt_manager; @@ -2551,10 +2551,20 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ->setDateTime($obj->getDateSchedule()) ->setLimitResults(50); + // NOTE: set JO type regardless, for now + $hub_criteria->setJoType($obj->getServiceType()) + ->setOrderDate($obj->getDateCreate()) + ->setServiceType($obj->getServiceType()); + + // set customer class + $cust = $obj->getCustomer(); + $hub_criteria->setCustomerClass($cust->getCustomerClassification()); + // check if hub filter is enabled. If not, use default values // for the rest of the HubCriteria fields if ($this->hub_filter_enabled == 'true') { + // TODO: allow this to be disabled via env or CRM. commenting out for now // error_log('hub filter is enabled'); if ($this->hub_geofence->isCovered($long, $lat)) { @@ -2562,7 +2572,6 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface // error_log('Area is covered by hub filtering'); $hub_criteria->setLimitDistance($this->cust_distance_limit) ->setPaymentMethod($obj->getModeOfPayment()) - ->setJoType($obj->getServiceType()) ->setRoundRobin(true); } } @@ -2573,15 +2582,22 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface { // reset distance limit if emergency //TODO: move to .env the emergency distance limit - $hub_criteria->setLimitDistance(500); - $hub_criteria->setEmergency(true); + $hub_criteria->setLimitDistance(500) + ->setEmergency(true) + ->setPaymentMethod(null) + ->setRoundRobin(false); } + // set filter flags for inventory and available riders + $hub_criteria->setInventoryCheck(); + $hub_criteria->setRidersCheck(); + // get JO and customer id for logging purposes $jo_id = $obj->getID(); $customer_id = $obj->getCustomer()->getID(); $hub_criteria->setJobOrderId($jo_id) + ->setJoOrigin($obj->getSource()) ->setCustomerId($customer_id); $hubs = $hub_selector->find($hub_criteria); @@ -2646,7 +2662,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface // handle inventory data $bcode = $hub['hub']->getBranchCode(); - $hub['inventory'] = 0; + //$hub['inventory'] = 0; if ($bcode != '') { $branch_codes[] = $bcode; @@ -2664,7 +2680,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface // get template to display $params['template'] = $this->getTwigTemplate('jo_processing_form'); + // NOTE: as we have included inventory now from the hub selector, we no longer have to redo it here + // get battery (if any) + /* $skus = []; $invoice = $obj->getInvoice(); $inv_items = $invoice->getItems(); @@ -2694,6 +2713,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface } } } + */ // error_log(print_r($mres, true)); @@ -2889,13 +2909,22 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface ->setDateTime($obj->getDateSchedule()) ->setLimitResults(50); + // NOTE: set JO type regardless, for now + $hub_criteria->setJoType($obj->getServiceType()) + ->setOrderDate($obj->getDateCreate()) + ->setServiceType($obj->getServiceType()); + + // set customer class + $cust = $obj->getCustomer(); + $hub_criteria->setCustomerClass($cust->getCustomerClassification()); + + // TODO: allow this to be disabled via env or CRM. commenting out for now if ($this->hub_geofence->isCovered($long, $lat)) { // if true, set other values for HubCriteria // error_log('Area is covered by hub'); $hub_criteria->setLimitDistance($this->cust_distance_limit) ->setPaymentMethod($obj->getModeOfPayment()) - ->setJoType($obj->getServiceType()) ->setRoundRobin(true); } @@ -2904,15 +2933,22 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface if ($willing_to_wait == WillingToWaitContent::NOT_WILLING_TO_WAIT) { //TODO: move to .env the emergency distance limit - $hub_criteria->setLimitDistance(500); - $hub_criteria->setEmergency(true); + $hub_criteria->setLimitDistance(500) + ->setEmergency(true) + ->setPaymentMethod(null) + ->setRoundRobin(false); } + // set filter flags for inventory and available riders + $hub_criteria->setInventoryCheck(); + $hub_criteria->setRidersCheck(); + // get JO and customer id for logging purposes $jo_id = $obj->getID(); - $customer_id = $obj->getCustomer()->getID(); + $customer_id = $cust->getID(); $hub_criteria->setJobOrderId($jo_id) + ->setJoOrigin($obj->getSource()) ->setCustomerId($customer_id); $hubs = $hub_selector->find($hub_criteria); @@ -2921,6 +2957,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface $params['hubs'] = []; $branch_codes = []; + $inv_data = []; + // format duration and distance into friendly time foreach ($hubs as $hub) { // duration @@ -2976,7 +3014,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface // handle inventory data $bcode = $hub['hub']->getBranchCode(); - $hub['inventory'] = 0; + //$hub['inventory'] = 0; if ($bcode != '') { $branch_codes[] = $bcode; @@ -2990,39 +3028,65 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface $params['hubs'][$hub_id] = $hub; } - // get battery (if any) - $skus = []; - $invoice = $obj->getInvoice(); - $inv_items = $invoice->getItems(); - foreach ($inv_items as $inv_item) - { - $batt = $inv_item->getBattery(); - if ($batt == null) - continue; + // NOTE: as we have included inventory now from the hub selector, we no longer have to redo it here - $skus[] = $batt->getSapCode(); - } + //error_log("TOTAL HUBS FOUND WITH BRANCH CODE: " . count($inv_data)); - // get inventory - $mres = $motiv->getInventory($branch_codes, $skus); - foreach ($mres as $mres_item) - { - // check if we have a valid response from motiv, ignore otherwise - if (isset($mres_item['BranchCode'])) + // get all enabled filters + $enabled_filter_str = $_ENV['ENABLED_HUB_FILTERS']; + $enabled_filters = explode(",", $enabled_filter_str); + + // if inventory filter is disabled, fetch inventory here + if (!in_array('InventoryHubFilter', $enabled_filters) || $this->skipInventoryCheck($obj->getCoordinates())) { + error_log("NO INVENTORY CHECKS, GETTING INVENTORY FOR JO " . $obj->getID()); + + // get battery (if any) + $skus = []; + $invoice = $obj->getInvoice(); + $inv_items = $invoice->getItems(); + foreach ($inv_items as $inv_item) { - $bcode = $mres_item['BranchCode']; - $inv_count = $mres_item['Quantity']; - if (isset($inv_data[$bcode])) - { - $hub_id = $inv_data[$bcode]['hub_id']; + $batt = $inv_item->getBattery(); + if ($batt == null) + continue; - $params['hubs'][$hub_id]['inventory'] = $inv_count; + $skus[] = $batt->getSapCode(); + } + + // get inventory + $mres = $motiv->getInventory($branch_codes, $skus); + $x = 0; + + error_log("TOTAL RESULTS FROM MOTIV: " . count($mres) . " OUT OF " . count($branch_codes) . " BRANCH CODES AND " . count($skus) . " SKUS"); + + foreach ($mres as $mres_item) + { + // check if we have a valid response from motiv, ignore otherwise + if (isset($mres_item['BranchCode'])) + { + $bcode = $mres_item['BranchCode']; + $inv_count = $mres_item['Quantity']; + if (isset($inv_data[$bcode])) + { + $hub_id = $inv_data[$bcode]['hub_id']; + + $params['hubs'][$hub_id]['inventory'] = $inv_count; + + error_log("SETTING HUB " . $hub_id . " INVENTORY TO " . $inv_count); + $x++; + } else { + error_log("CANNOT FIND BCODE FOR " . $bcode); + } + } else { + error_log("CANNOT FIND BCODE FOR RESULT: " . print_r($mres_item, true)); } } + + error_log("SET QUANTITY OF " . $x . " HUBS TO NON ZERO"); + + // error_log(print_r($mres, true)); } - - // error_log(print_r($mres, true)); - + $params['obj'] = $obj; // get template to display $params['template'] = $this->getTwigTemplate('jo_open_hub_form'); @@ -4298,4 +4362,28 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface $this->rt->sendSMS($phone_number, $this->translator->trans('message.battery_brand_allcaps'), $msg); } + protected function skipInventoryCheck(Point $coordinates): bool + { + $long = $coordinates->getLongitude(); + $lat = $coordinates->getLatitude(); + + // get supported area given a set of coordinates + $query = $this->em->createQuery('SELECT s from App\Entity\SupportedArea s where st_contains(s.coverage_area, point(:long, :lat)) = true'); + $area = $query->setParameter('long', $long) + ->setParameter('lat', $lat) + ->setMaxResults(1) + ->getOneOrNullResult(); + + if ($area !== null) { + // get all exceptions + $exceptions = $area->getHubFilterExceptions(); + + if (isset($exceptions['no_inventory'])) { + return true; + } + } + + // filter is in place + return false; + } } diff --git a/translations/messages.en.yaml b/translations/messages.en.yaml index 8387663e..5fbc11a9 100644 --- a/translations/messages.en.yaml +++ b/translations/messages.en.yaml @@ -13,7 +13,8 @@ add_cust_vehicle_battery_info: This vehicle is using a Motolite battery jo_title_pdf: Motolite Res-Q Job Order country_code_prefix: '+63' delivery_instructions_label: Delivery Instructions -no_inventory_message: No stock for [item_display] +no_inventory_message: 'A Job Order was created but there is insufficient stock for the following SKU(s) on this branch: [item_display]' +no_riders_message: A Job Order was created but there are no riders available for this branch. # images image_logo_login: /assets/images/logo-resq.png diff --git a/utils/hub_filter_exceptions/hub_filter_exceptions.sql b/utils/hub_filter_exceptions/hub_filter_exceptions.sql new file mode 100644 index 00000000..ef165614 --- /dev/null +++ b/utils/hub_filter_exceptions/hub_filter_exceptions.sql @@ -0,0 +1,2 @@ +update supported_area set hub_filter_exceptions = '{"no_inventory":true,"no_available_rider":true}' where id = 34; +update supported_area set hub_filter_exceptions = '{"no_inventory":true,"no_available_rider":true}' where id = 35; \ No newline at end of file