Refactor hub selector to separate filters into individual classes #800

This commit is contained in:
Ramon Gutierrez 2024-06-25 03:19:10 +08:00
parent b4057de938
commit 204c039fba
11 changed files with 663 additions and 546 deletions

View file

@ -322,3 +322,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"

View file

@ -0,0 +1,123 @@
<?php
namespace App\Service\HubFilter;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use App\Service\HubFilterLogger;
use App\Entity\Hub;
use App\Entity\JobOrder;
use App\Entity\JORejection;
use App\Ramcar\ServiceType;
use App\Ramcar\JORejectionReason;
use App\Service\RisingTideGateway;
use DateTime;
class BaseHubFilter
{
protected $id;
protected $jo_id;
protected $customer_id;
protected $hub_filter_logger;
protected $em;
protected $rt;
protected $trans;
public function __construct(HubFilterLogger $hub_filter_logger, EntityManagerInterface $em, RisingTideGateway $rt, TranslatorInterface $trans)
{
$this->hub_filter_logger = $hub_filter_logger;
$this->em = $em;
$this->rt = $rt;
$this->trans = $trans;
}
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());
}
protected function createRejectionEntry($jo_id, $hub, $remarks = ""): JORejection
{
$jo = $this->em->getRepository(JobOrder::class)->find($jo_id);
$robj = new JORejection();
$robj->setDateCreate(new DateTime())
->setHub($hub)
->setJobOrder($jo)
->setReason(JORejectionReason::NO_STOCK_SALES)
->setRemarks(implode(" ", ["Automatically filtered by hub selector.", $remarks]));
$this->em->persist($robj);
$this->em->flush();
return $robj;
}
protected function sendSMSMessage($hub, $jo_id, $order_date, $service_type, $rejection, $reason = "", $remarks = ""): void
{
// 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
);
}
}

View file

@ -0,0 +1,51 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
class DateAndTimeHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'date_and_time';
public function filter(array $hubs, array $params = []) : array
{
if ($params['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 = $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;
}
}

View file

@ -0,0 +1,148 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use App\Ramcar\JORejectionReason;
use App\Ramcar\ServiceType;
use App\Ramcar\CustomerClassification;
use App\Service\InventoryManager;
use App\Service\HubFilterLogger;
use App\Service\RisingTideGateway;
use App\Entity\Battery;
class InventoryHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'no_inventory';
protected $im;
public function __construct(HubFilterLogger $hub_filter_logger, EntityManagerInterface $em, RisingTideGateway $rt, TranslatorInterface $trans, InventoryManager $im)
{
parent::__construct($hub_filter_logger, $em, $rt, $trans);
$this->im = $im;
}
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) {
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 ($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
if (!empty($jo_id)) {
// create rejection report entry
$robj = $this->createRejectionEntry(
$jo_id,
$hub,
"SKU(s): " . $battery_string
);
// build SMS message
$this->sendSMSMessage(
$hub,
$jo_id,
$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;
}
}

View file

@ -0,0 +1,44 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
class JoTypeHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'job_order_type';
public function filter(array $hubs, array $params = []) : array
{
if ($params['flag_emergency'])
return $hubs;
if (empty($params['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,
'inventory' => $hub_data['inventory'],
];
else
$this->log($hub);
}
return $results;
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
class MaxResultsHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'max_results';
public function filter(array $hubs, array $params = []) : array
{
if (empty($params['limit_results']))
return $hubs;
$results = [];
for ($i = 0; $i < count($hubs); $i++)
{
if ($i < $params['limit_results'])
$results[] = $hubs[$i];
else
$this->log($hubs[$i]['hub']);
}
return $results;
}
}

View file

@ -0,0 +1,55 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
class PaymentMethodHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'no_payment_method';
public function filter(array $hubs, array $params = []) : array
{
if ($params['flag_emergency'])
return $hubs;
if (empty($params['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 == $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;
}
}

View file

@ -0,0 +1,62 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
use App\Ramcar\JORejectionReason;
use App\Ramcar\CustomerClassification;
class RiderAvailabilityHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'no_available_rider';
public function filter(array $hubs, array $params = []) : array
{
// check if this is enabled
if (!$params['flag_riders_check']) {
return $hubs;
}
// check customer class
if (!empty($params['customer_class']) && $params['customer_class'] == CustomerClassification::VIP) {
error_log("INVENTORY CHECK " . $this->getJOID() . ": VIP CLASS");
return $hubs;
}
$results = [];
foreach ($hubs as $hub_data) {
$hub = $hub_data['hub'];
// check we have available riders
if ($hub->getAvailableRiders() === 0) {
// if we have a JO, create rejection record and notify
if (!empty($jo_id)) {
// create rejection report entry
$robj = $this->createRejectionEntry($jo_id, $hub);
// build SMS message
$this->sendSMSMessage(
$hub,
$jo_id,
$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;
}
}

View file

@ -0,0 +1,39 @@
<?php
namespace App\Service\HubFilter\Filters;
use App\Service\HubFilter\BaseHubFilter;
use App\Service\HubFilter\HubFilterInterface;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use App\Service\HubDistributor;
use App\Service\HubFilterLogger;
use App\Service\RisingTideGateway;
class RoundRobinHubFilter extends BaseHubFilter implements HubFilterInterface
{
protected $id = 'round_robin';
protected $hub_distributor;
public function __construct(HubFilterLogger $hub_filter_logger, EntityManagerInterface $em, RisingTideGateway $rt, TranslatorInterface $trans, HubDistributor $hub_distributor)
{
parent::__construct($hub_filter_logger, $em, $rt, $trans);
$this->hub_distributor = $hub_distributor;
}
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;
}
}

View file

@ -0,0 +1,18 @@
<?php
namespace App\Service\HubFilter;
interface HubFilterInterface
{
public function getID() : string;
public function filter(array $hubs, array $params = []) : array;
public function setJOID(int $jo_id);
public function getJOID(): int;
public function setCustomerID(int $customer_id);
public function getCustomerID(): int;
}

View file

@ -4,28 +4,21 @@ namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use Proxies\__CG__\App\Entity\JORejection;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use App\Entity\Hub;
use App\Entity\JobOrder;
use App\Entity\Battery;
use App\Ramcar\CustomerClassification;
use App\Service\HubDistributor;
use App\Service\InventoryManager;
use App\Service\HubFilterLogger;
use App\Service\RisingTideGateway;
use App\Ramcar\HubCriteria;
use App\Ramcar\ServiceType;
use App\Ramcar\JOEventType;
use App\Ramcar\JORejectionReason;
use DateTime;
class HubSelector
{
protected $container;
protected $em;
protected $im;
protected $hub_distributor;
@ -33,10 +26,11 @@ class HubSelector
protected $trans;
protected $rt;
public function __construct(EntityManagerInterface $em, InventoryManager $im,
public function __construct(ContainerInterface $container, EntityManagerInterface $em, InventoryManager $im,
HubDistributor $hub_distributor, HubFilterLogger $hub_filter_logger,
TranslatorInterface $trans, RisingTideGateway $rt)
{
$this->container = $container;
$this->em = $em;
$this->im = $im;
$this->hub_distributor = $hub_distributor;
@ -45,6 +39,20 @@ class HubSelector
$this->rt = $rt;
}
// TODO: move this to env or something so we can enable and disable filters without code changes
protected function getActiveFilters(): array
{
return [
'App\Service\HubFilter\Filters\DateAndTimeHubFilter',
'App\Service\HubFilter\Filters\JoTypeHubFilter',
'App\Service\HubFilter\Filters\PaymentMethodHubFilter',
'App\Service\HubFilter\Filters\RiderAvailabilityHubFilter',
'App\Service\HubFilter\Filters\InventoryHubFilter',
'App\Service\HubFilter\Filters\RoundRobinHubFilter',
'App\Service\HubFilter\Filters\MaxResultsHubFilter',
];
}
public function find(HubCriteria $criteria)
{
$point = $criteria->getPoint();
@ -66,461 +74,63 @@ class HubSelector
$order_date = $criteria->getOrderDate();
$service_type = $criteria->getServiceType();
$results = [];
// error_log('payment methods ' . $payment_method);
// error_log('distance limit ' . $limit_distance);
// error_log('emergency flag ' . $flag_emergency);
// get all the hubs within distance
$filtered_hubs = $this->getClosestHubs($point, $limit_distance, $jo_id, $customer_id);
// gather all params in one array
// TODO: figure out a better way to do this where we don't have to specify the filter names here
$params = [
'date_and_time' => [
'date_time' => $date_time,
],
'no_inventory' => [
'flag_inventory_check' => $flag_inventory_check,
'customer_class' => $customer_class,
'jo_type' => $jo_type,
'order_date' => $order_date,
'service_type' => $service_type,
'items' => $items,
],
'job_order_type' => [
'flag_emergency' => $flag_emergency,
'jo_type' => $jo_type,
],
'max_results' => [
'limit_results' => $limit_results,
],
'no_payment_method' => [
'flag_emergency' => $flag_emergency,
'payment_method' => $payment_method,
],
'no_available_rider' => [
'flag_riders_check' => $flag_riders_check,
'customer_class' => $customer_class,
'order_date' => $order_date,
'service_type' => $service_type,
],
'round_robin' => [
'flag_round_robin' => $flag_round_robin,
],
];
// error_log('closest hubs ' . json_encode($filtered_hubs));
// loop through all enabled filters
foreach ($this->getActiveFilters() as $hub_filter) {
$f = $this->container->get($hub_filter);
$f->setJOID($jo_id);
$f->setCustomerID($customer_id);
// 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;
$filtered_hubs = $f->filter($filtered_hubs, $params[$f->getID()]);
// error_log('date_time hubs ' . json_encode($filtered_hubs));
// TODO: allow toggling of each filter individually for specific conditions (e.g. customer class, emergency)
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));
// error_log($f->getID() . ' hubs ' . json_encode($filtered_hubs));
}
// only enable rider and inventory checks if not VIP
if (!empty($customer_class) && $customer_class !== CustomerClassification::VIP) {
// available riders filter
$hubs_riders = $this->filterHubsByRiderAvailability($filtered_hubs, $flag_riders_check, $jo_id, $customer_id, $order_date, $service_type);
$filtered_hubs = $hubs_riders;
// error_log('available riders hubs ' . json_encode($filtered_hubs));
// inventory filter
$hubs_inventory = $this->filterHubsByInventory($filtered_hubs, $flag_inventory_check, $jo_type, $items, $jo_id, $customer_id, $order_date, $service_type);
$filtered_hubs = $hubs_inventory;
// error_log('inventory hubs ' . json_encode($filtered_hubs));
}
if (!$flag_emergency) {
// 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('final hub list ' . json_encode($filtered_hubs));
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,
'inventory' => $hub_data['inventory'],
];
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,
'inventory' => $hub_data['inventory'],
];
}
$flag_found_pmethod = true;
}
if (!$flag_found_pmethod)
$this->hub_filter_logger->logFilteredHub($hub, 'no_payment_method', $jo_id, $customer_id);
}
else
$this->hub_filter_logger->logFilteredHub($hub, 'no_payment_method', $jo_id, $customer_id);
}
return $results;
}
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,
'inventory' => $hub_data['inventory'],
];
}
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, $order_date, $service_type)
{
// check if this is enabled
if (!$flag_inventory_check) {
error_log("INVENTORY CHECK " . $jo_id . ": DISABLED");
return $hubs;
}
// check hub list is not empty
if (empty($hubs)) {
error_log("INVENTORY CHECK " . $jo_id . ": NO HUBS");
return $hubs;
}
// check item list is not empty
if (empty($items)) {
error_log("INVENTORY CHECK " . $jo_id . ": NO ITEMS");
return $hubs;
}
// check this is a battery item related JO
if ($jo_type != ServiceType::BATTERY_REPLACEMENT_NEW &&
$jo_type != ServiceType::BATTERY_REPLACEMENT_WARRANTY
) {
error_log("INVENTORY CHECK " . $jo_id . ": INVALID SERVICE TYPE: " . $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($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 ($branch['Quantity'] < $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_obj = $hub_data['hub'];
$branch_code = $hub_data['hub']->getBranchCode();
// check if we are filtering this hub
if (isset($hubs_to_filter[$branch_code])) {
// if we have a JO, create rejection record and notify
if (!empty($jo_id)) {
// create rejection report entry
$robj = $this->createRejectionEntry(
$jo_id,
$hub_obj,
"SKU(s): " . $battery_string
);
// build SMS message
$this->sendSMSMessage(
$hub_obj,
$jo_id,
$order_date,
$service_type,
$robj,
JORejectionReason::getName(JORejectionReason::NO_STOCK_SALES),
"Requested SKU(s) - " . $battery_string
);
}
// log this filter
$this->hub_filter_logger->logFilteredHub($hub_obj, 'no_inventory', $jo_id, $customer_id);
error_log("FILTERED HUB " . $hub_obj->getID() . " (no_inventory)");
} else if (!empty($branch_code) && isset($qtys[$branch_code])) {
// 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;
// NOTE: leaving the old code here for posterity, for now
/*
if ($flag_inventory_check)
{
foreach ($hubs as $hub_data)
{
$hub = $hub_data['hub'];
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);
}
}
}
}
return $results;
*/
}
protected function filterHubsByRiderAvailability($hubs, $flag_riders_check, $jo_id, $customer_id, $order_date, $service_type)
{
// check if this is enabled
if (!$flag_riders_check) {
return $hubs;
}
// check hub list is not empty
if (empty($hubs)) {
return $hubs;
}
$results = [];
foreach ($hubs as $hub_data) {
$hub_obj = $hub_data['hub'];
// check we have available riders
if ($hub_obj->getAvailableRiders() === 0) {
// if we have a JO, create rejection record and notify
if (!empty($jo_id)) {
// create rejection report entry
$robj = $this->createRejectionEntry($jo_id, $hub_obj);
// build SMS message
$this->sendSMSMessage(
$hub_obj,
$jo_id,
$order_date,
$service_type,
$robj,
JORejectionReason::getName(JORejectionReason::NO_RIDER_AVAILABLE),
);
}
// log this filter
$this->hub_filter_logger->logFilteredHub($hub_obj, 'no_available_rider', $jo_id, $customer_id);
error_log("FILTERED HUB " . $hub_obj->getID() . " (no_available_rider)");
} else {
$results[] = $hub_data;
}
}
return $results;
return $filtered_hubs;
}
protected function getClosestHubs(Point $point, $limit_distance, $jo_id, $customer_id)
@ -573,44 +183,6 @@ class HubSelector
return $hubs_data;
}
protected function checkInventory($items, $branch_codes)
{
// check if hub has all items
$skus = [];
$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)
{
@ -625,61 +197,5 @@ class HubSelector
return round(($miles * 1.609344), 1);
}
protected function createRejectionEntry($jo_id, $hub, $remarks = ""): JORejection
{
$jo = $this->em->getRepository(JobOrder::class)->find($jo_id);
$robj = new JORejection();
$robj->setDateCreate(new DateTime())
->setHub($hub)
->setJobOrder($jo)
->setReason(JORejectionReason::NO_STOCK_SALES)
->setRemarks(implode(" ", ["Automatically filtered by hub selector.", $remarks]));
$this->em->persist($robj);
$this->em->flush();
return $robj;
}
protected function sendSMSMessage($hub, $jo_id, $order_date, $service_type, $rejection, $reason = "", $remarks = ""): void
{
// 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
);
}
}