Merge branch 'master' into 803-fix-invoice-data-disappearing-on-open-edit-form

This commit is contained in:
Ramon Gutierrez 2024-07-18 16:34:59 +08:00
commit 511aa5ec9b
25 changed files with 1105 additions and 381 deletions

View file

@ -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"

View file

@ -489,6 +489,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',
@ -576,6 +578,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)
@ -644,6 +648,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) {
@ -710,13 +716,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'];
@ -754,6 +777,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);
@ -832,6 +859,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);
@ -843,8 +872,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();
@ -955,6 +986,8 @@ class JobOrderController extends ApiController
}
}
//error_log("DONE CREATING JOB ORDER " . $jo->getID());
// response
return new ApiResponse(true, '', [
'jo_id' => $jo->getID(),

View file

@ -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);

View file

@ -236,6 +236,10 @@ class ReportController extends Controller
$reason = $jor->getReason();
$dispatched_by = 'system';
if ($jor->getUser() != null)
$dispatched_by = $jor->getUser()->getFullName();
$res[] = [
$jo->getID(),
$jo->getDateSchedule()->format('m/d/Y H:i'),
@ -244,7 +248,7 @@ class ReportController extends Controller
JORejectionReason::getName($jor->getReason()),
$jor->getContactPerson(),
$jor->getRemarks(),
$jor->getUser()->getFullName(),
!empty($jor->getUser()) ? $jor->getUser()->getFullName() : "",
ServiceType::getName($jo->getServiceType()),
];
}

View file

@ -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

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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',

View file

@ -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;
}
}

View file

@ -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,
];
}

View file

@ -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 ?? [];
}
}

View file

@ -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,
];
}
}

View file

@ -0,0 +1,144 @@
<?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;
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
);
}
}

View file

@ -0,0 +1,58 @@
<?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 getRequestedParams() : array
{
return [
'date_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,164 @@
<?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;
use App\Ramcar\TransactionOrigin;
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 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;
}
}

View file

@ -0,0 +1,52 @@
<?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 getRequestedParams() : array
{
return [
'flag_emergency',
'jo_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,35 @@
<?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 getRequestedParams() : array
{
return [
'limit_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,63 @@
<?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 getRequestedParams() : array
{
return [
'flag_emergency',
'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,75 @@
<?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 getRequestedParams() : array
{
return [
'flag_riders_check',
'customer_class',
'order_date',
'service_type',
];
}
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("RIDER CHECK " . $this->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;
}
}

View file

@ -0,0 +1,46 @@
<?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 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;
}
}

View file

@ -0,0 +1,20 @@
<?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;
public function getRequestedParams() : array;
}

View file

@ -5,21 +5,20 @@ namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Contracts\Translation\TranslatorInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use CrEOF\Spatial\PHP\Types\Geometry\Point;
use App\Entity\Hub;
use App\Service\HubDistributor;
use App\Service\InventoryManager;
use App\Service\HubFilterLogger;
use App\Service\RisingTideGateway;
use App\Ramcar\HubCriteria;
use App\Ramcar\ServiceType;
class HubSelector
{
protected $container;
protected $em;
protected $im;
protected $hub_distributor;
@ -27,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;
@ -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);
}
}

View file

@ -96,7 +96,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;
@ -2559,10 +2559,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))
{
@ -2570,7 +2580,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);
}
}
@ -2581,15 +2590,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);
@ -2654,7 +2670,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// handle inventory data
$bcode = $hub['hub']->getBranchCode();
$hub['inventory'] = 0;
//$hub['inventory'] = 0;
if ($bcode != '')
{
$branch_codes[] = $bcode;
@ -2672,7 +2688,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();
@ -2702,6 +2721,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
}
}
}
*/
// error_log(print_r($mres, true));
@ -2897,13 +2917,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);
}
@ -2912,15 +2941,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);
@ -2929,6 +2965,8 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$params['hubs'] = [];
$branch_codes = [];
$inv_data = [];
// format duration and distance into friendly time
foreach ($hubs as $hub) {
// duration
@ -2984,7 +3022,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// handle inventory data
$bcode = $hub['hub']->getBranchCode();
$hub['inventory'] = 0;
//$hub['inventory'] = 0;
if ($bcode != '')
{
$branch_codes[] = $bcode;
@ -2998,39 +3036,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');
@ -4306,4 +4370,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;
}
}

View file

@ -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

View file

@ -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;