Compare commits

...

11 commits

17 changed files with 1096 additions and 12 deletions

View file

@ -65,6 +65,13 @@ class User extends BaseUser
*/ */
protected $metadata; protected $metadata;
// third party job order source linked to api_user
/**
* @ORM\ManyToOne(targetEntity="App\Entity\JobOrderSource", inversedBy="api_users")
* @ORM\JoinColumn(name="source_id", referencedColumnName="id", nullable=true)
*/
protected $source;
public function __construct() public function __construct()
{ {
parent::__construct(); parent::__construct();
@ -76,6 +83,8 @@ class User extends BaseUser
// set date created // set date created
$this->date_create = new DateTime(); $this->date_create = new DateTime();
$this->metadata = []; $this->metadata = [];
$this->source = null;
} }
public function getID() public function getID()
@ -182,5 +191,17 @@ class User extends BaseUser
{ {
return $this->rider; return $this->rider;
} }
public function setSource($source = null)
{
$this->source = $source;
return $this;
}
public function getSource()
{
return $this->source;
}
} }

View file

@ -586,3 +586,17 @@ access_keys:
label: Update label: Update
- id: ownership_type.delete - id: ownership_type.delete
label: Delete label: Delete
- id: job_order_source
label: Job Order Source Access
acls:
- id: job_order_source.menu
label: Menu
- id: job_order_source.list
label: List
- id: job_order_source.add
label: Add
- id: job_order_source.update
label: Update
- id: job_order_source.delete
label: Delete

View file

@ -249,3 +249,7 @@ main_menu:
acl: ownership_type.menu acl: ownership_type.menu
label: Ownership Types label: Ownership Types
parent: database parent: database
- id: job_order_source_list
acl: job_order_source.menu
label: Job Order Source
parent: database

View file

@ -0,0 +1,35 @@
job_order_source_list:
path: /job-order-sources
controller: App\Controller\JobOrderSourceController::index
methods: [GET]
job_order_source_rows:
path: /job-order-sources/rowdata
controller: App\Controller\JobOrderSourceController::datatableRows
methods: [POST]
job_order_source_add_form:
path: /job-order-sources/newform
controller: App\Controller\JobOrderSourceController::addForm
methods: [GET]
job_order_source_add_submit:
path: /job-order-sources
controller: App\Controller\JobOrderSourceController::addSubmit
methods: [POST]
job_order_source_update_form:
path: /job-order-sources/{id}
controller: App\Controller\JobOrderSourceController::updateForm
methods: [GET]
job_order_source_update_submit:
path: /job-order-sources/{id}
controller: App\Controller\JobOrderSourceController::updateSubmit
methods: [POST]
job_order_source_delete:
path: /job-order-sources/{id}
controller: App\Controller\JobOrderSourceController::deleteSubmit
methods: [DELETE]

View file

@ -15,6 +15,7 @@ use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Catalyst\MenuBundle\Annotation\Menu; use Catalyst\MenuBundle\Annotation\Menu;
use App\Entity\Rider; use App\Entity\Rider;
use App\Entity\JobOrderSource;
class APIUserController extends Controller class APIUserController extends Controller
{ {
@ -138,6 +139,7 @@ class APIUserController extends Controller
$em = $this->getDoctrine()->getManager(); $em = $this->getDoctrine()->getManager();
$params['roles'] = $em->getRepository(APIRole::class)->findAll(); $params['roles'] = $em->getRepository(APIRole::class)->findAll();
$params['riders'] = $em->getRepository(Rider::class)->findBy([], ['first_name' => 'asc']); $params['riders'] = $em->getRepository(Rider::class)->findBy([], ['first_name' => 'asc']);
$params['sources'] = $em->getRepository(JobOrderSource::class)->findBy([], ['name' => 'asc']);
// response // response
return $this->render('api-user/form.html.twig', $params); return $this->render('api-user/form.html.twig', $params);
@ -166,6 +168,15 @@ class APIUserController extends Controller
->setMetadata($meta); ->setMetadata($meta);
} }
// source, if any
$source_id = $req->request->get('source_id', '');
if (!empty($source_id))
{
$source = $em->getRepository(JobOrderSource::class)->find($source_id);
if ($source != null)
$obj->setSource($source);
}
// set and save values // set and save values
$obj->setName($req->request->get('name')) $obj->setName($req->request->get('name'))
->setEnabled($req->request->get('enabled') ? true : false) ->setEnabled($req->request->get('enabled') ? true : false)
@ -239,6 +250,7 @@ class APIUserController extends Controller
// get roles // get roles
$params['roles'] = $em->getRepository(APIRole::class)->findAll(); $params['roles'] = $em->getRepository(APIRole::class)->findAll();
$params['riders'] = $em->getRepository(Rider::class)->findBy([], ['first_name' => 'asc']); $params['riders'] = $em->getRepository(Rider::class)->findBy([], ['first_name' => 'asc']);
$params['sources'] = $em->getRepository(JobOrderSource::class)->findBy([], ['name' => 'asc']);
$params['obj'] = $obj; $params['obj'] = $obj;
@ -260,15 +272,34 @@ class APIUserController extends Controller
// set and save values // set and save values
// metadata // metadata
$rider_id = $req->request->get('rider_id'); $rider_id = $req->request->get('rider_id', '');
$rider = null;
if (!empty($rider_id))
{
$rider = $em->getRepository(Rider::class)->find($rider_id); $rider = $em->getRepository(Rider::class)->find($rider_id);
// TODO: check for null rider if ($rider != null)
{
// set api user in rider
$rider->setAPIUser($obj);
}
}
$meta = $obj->getMetadata(); $meta = $obj->getMetadata();
$meta['rider_id'] = $rider_id; $meta['rider_id'] = $rider_id;
// set api user in rider // source, if any
$rider->setAPIUser($obj); $source_id = $req->request->get('source_id', '');
if (!empty($source_id))
{
$source = $em->getRepository(JobOrderSource::class)->find($source_id);
if ($source != null)
$obj->setSource($source);
}
else
{
// reset source to null, if source was removed from api_user
$obj->setSource();
}
$obj->setName($req->request->get('name')) $obj->setName($req->request->get('name'))
->setEnabled($req->request->get('enabled') ? true : false) ->setEnabled($req->request->get('enabled') ? true : false)

View file

@ -32,6 +32,7 @@ use App\Service\JobOrderHandlerInterface;
use App\Service\InvoiceGeneratorInterface; use App\Service\InvoiceGeneratorInterface;
use App\Service\RisingTideGateway; use App\Service\RisingTideGateway;
use App\Service\RiderTracker; use App\Service\RiderTracker;
use App\Service\JobOrderCallbackRouter;
use App\Ramcar\ServiceType; use App\Ramcar\ServiceType;
use App\Ramcar\TradeInType; use App\Ramcar\TradeInType;
@ -388,7 +389,7 @@ class RiderAppController extends APIController
} }
public function acceptJobOrder(Request $req, EntityManagerInterface $em) public function acceptJobOrder(Request $req, EntityManagerInterface $em, JobOrderCallbackRouter $jo_callback)
{ {
$required_params = ['jo_id']; $required_params = ['jo_id'];
@ -406,6 +407,9 @@ class RiderAppController extends APIController
if (!empty($msg)) if (!empty($msg))
return new APIResponse(false, $msg); return new APIResponse(false, $msg);
// get previous status
$old_status = $jo->getStatus();
// TODO: refactor this into a jo handler class, so we don't have to repeat for control center // TODO: refactor this into a jo handler class, so we don't have to repeat for control center
// set jo status to in transit // set jo status to in transit
@ -432,12 +436,16 @@ class RiderAppController extends APIController
$em->flush(); $em->flush();
// get current status and send JO callback
$current_status = $jo->getStatus();
$jo_callback->sendJOStatusCallback($jo, $old_status, $current_status);
$data = []; $data = [];
return new APIResponse(true, 'Job order accepted.', $data); return new APIResponse(true, 'Job order accepted.', $data);
} }
public function cancelJobOrder(Request $req, EntityManagerInterface $em, MQTTClient $mclient) public function cancelJobOrder(Request $req, EntityManagerInterface $em, MQTTClient $mclient, JobOrderCallbackRouter $jo_callback)
{ {
$required_params = ['jo_id']; $required_params = ['jo_id'];
@ -456,6 +464,9 @@ class RiderAppController extends APIController
// TODO: this is a workaround for requeue, because rider app gets stuck in accept / decline screen // TODO: this is a workaround for requeue, because rider app gets stuck in accept / decline screen
return new APIResponse(true, $msg); return new APIResponse(true, $msg);
// get previous status
$old_status = $jo->getStatus();
// requeue it, instead of cancelling it // requeue it, instead of cancelling it
$jo->requeue(); $jo->requeue();
@ -483,6 +494,10 @@ class RiderAppController extends APIController
]; ];
$mclient->sendEvent($jo, $payload); $mclient->sendEvent($jo, $payload);
// get current status and send JO callback
$current_status = $jo->getStatus();
$jo_callback->sendJOStatusCallback($jo, $old_status, $current_status);
$data = []; $data = [];
return new APIResponse(true, 'Job order requeued.', $data); return new APIResponse(true, 'Job order requeued.', $data);
} }
@ -648,7 +663,7 @@ class RiderAppController extends APIController
} }
public function arrive(Request $req, EntityManagerInterface $em, MQTTClient $mclient) public function arrive(Request $req, EntityManagerInterface $em, MQTTClient $mclient, JobOrderCallbackRouter $jo_callback)
{ {
$required_params = ['jo_id']; $required_params = ['jo_id'];
@ -666,6 +681,9 @@ class RiderAppController extends APIController
if (!empty($msg)) if (!empty($msg))
return new APIResponse(false, $msg); return new APIResponse(false, $msg);
// get previous status
$old_status = $jo->getStatus();
// TODO: refactor this into a jo handler class, so we don't have to repeat for control center // TODO: refactor this into a jo handler class, so we don't have to repeat for control center
// set jo status to in progress // set jo status to in progress
@ -698,6 +716,10 @@ class RiderAppController extends APIController
]; ];
$mclient->sendEvent($jo, $payload); $mclient->sendEvent($jo, $payload);
// get current status and send JO callback
$current_status = $jo->getStatus();
$jo_callback->sendJOStatusCallback($jo, $old_status, $current_status);
$data = []; $data = [];
return new APIResponse(true, 'Rider arrived at customer location.', $data); return new APIResponse(true, 'Rider arrived at customer location.', $data);
} }
@ -749,7 +771,8 @@ class RiderAppController extends APIController
} }
public function payment(Request $req, EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler, public function payment(Request $req, EntityManagerInterface $em, JobOrderHandlerInterface $jo_handler,
RisingTideGateway $rt, WarrantyHandler $wh, MQTTClient $mclient, TranslatorInterface $translator) RisingTideGateway $rt, WarrantyHandler $wh, MQTTClient $mclient, TranslatorInterface $translator,
JobOrderCallbackRouter $jo_callback)
{ {
$required_params = ['jo_id']; $required_params = ['jo_id'];
@ -767,6 +790,9 @@ class RiderAppController extends APIController
if (!empty($msg)) if (!empty($msg))
return new APIResponse(false, $msg); return new APIResponse(false, $msg);
// get previous status
$old_status = $jo->getStatus();
// set invoice to paid // set invoice to paid
$jo->getInvoice()->setStatus(InvoiceStatus::PAID); $jo->getInvoice()->setStatus(InvoiceStatus::PAID);
@ -815,6 +841,10 @@ class RiderAppController extends APIController
$em->flush(); $em->flush();
// get current status and send JO callback
$current_status = $jo->getStatus();
$jo_callback->sendJOStatusCallback($jo, $old_status, $current_status);
// create warranty // create warranty
if($jo_handler->checkIfNewBattery($jo)) if($jo_handler->checkIfNewBattery($jo))
{ {

View file

@ -0,0 +1,256 @@
<?php
namespace App\Controller;
use App\Entity\JobOrderSource;
use Doctrine\ORM\Query;
use Doctrine\ORM\QueryBuilder;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Validator\Validator\ValidatorInterface;
use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\ParamConverter;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\IsGranted;
use Catalyst\MenuBundle\Annotation\Menu;
class JobOrderSourceController extends Controller
{
/**
* @Menu(selected="job_order_source_list")
* @IsGranted("job_order_source.list")
*/
public function index()
{
$this->denyAccessUnlessGranted('job_order_source.list', null, 'No access.');
return $this->render('job-order-source/list.html.twig');
}
/**
* @IsGranted("job_order_source.list")
*/
public function datatableRows(Request $req)
{
// get query builder
$qb = $this->getDoctrine()
->getRepository(JobOrderSource::class)
->createQueryBuilder('q');
// get datatable params
$datatable = $req->request->get('datatable');
// count total records
$tquery = $qb->select('COUNT(q)');
$this->setQueryFilters($datatable, $tquery);
$total = $tquery->getQuery()
->getSingleScalarResult();
// get current page number
$page = $datatable['pagination']['page'] ?? 1;
$perpage = $datatable['pagination']['perpage'];
$offset = ($page - 1) * $perpage;
// add metadata
$meta = [
'page' => $page,
'perpage' => $perpage,
'pages' => ceil($total / $perpage),
'total' => $total,
'sort' => 'asc',
'field' => 'id'
];
// build query
$query = $qb->select('q');
$this->setQueryFilters($datatable, $query);
// check if sorting is present, otherwise use default
if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) {
$order = $datatable['sort']['sort'] ?? 'asc';
$query->orderBy('q.' . $datatable['sort']['field'], $order);
} else {
$query->orderBy('q.id', 'asc');
}
// get rows for this page
$obj_rows = $query->setFirstResult($offset)
->setMaxResults($perpage)
->getQuery()
->getResult();
// process rows
$rows = [];
foreach ($obj_rows as $orow) {
// add row data
$row['id'] = $orow->getID();
$row['name'] = $orow->getName();
// add row metadata
$row['meta'] = [
'update_url' => '',
'delete_url' => ''
];
// add crud urls
if ($this->isGranted('job_order_source.update'))
$row['meta']['update_url'] = $this->generateUrl('job_order_source_update_form', ['id' => $row['id']]);
if ($this->isGranted('job_order_source.delete'))
$row['meta']['delete_url'] = $this->generateUrl('job_order_source_delete', ['id' => $row['id']]);
$rows[] = $row;
}
// response
return $this->json([
'meta' => $meta,
'data' => $rows
]);
}
/**
* @Menu(selected="job_order_source.list")
* @IsGranted("job_order_source.add")
*/
public function addForm()
{
$jo_source = new JobOrderSource();
$params = [
'jo_source' => $jo_source,
'mode' => 'create',
];
// response
return $this->render('job-order-source/form.html.twig', $params);
}
/**
* @IsGranted("job_order_source.add")
*/
public function addSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator)
{
$jo_source = new JobOrderSource();
$this->setObject($jo_source, $req);
// validate
$errors = $validator->validate($jo_source);
// initialize error list
$error_array = [];
// add errors to list
foreach ($errors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// validated! save the entity
$em->persist($jo_source);
$em->flush();
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
/**
* @Menu(selected="job_order_source_list")
* @ParamConverter("jo_source", class="App\Entity\JobOrderSource")
* @IsGranted("job_order_source.update")
*/
public function updateForm($id, EntityManagerInterface $em, JobOrderSource $jo_source)
{
$params = [];
$params['jo_source'] = $jo_source;
$params['mode'] = 'update';
// response
return $this->render('job-order-source/form.html.twig', $params);
}
/**
* @ParamConverter("jo_source", class="App\Entity\JobOrderSource")
* @IsGranted("job_order_source.update")
*/
public function updateSubmit(Request $req, EntityManagerInterface $em, ValidatorInterface $validator, JobOrderSource $jo_source)
{
$this->setObject($jo_source, $req);
// validate
$errors = $validator->validate($jo_source);
// initialize error list
$error_array = [];
// add errors to list
foreach ($errors as $error) {
$error_array[$error->getPropertyPath()] = $error->getMessage();
}
// check if any errors were found
if (!empty($error_array)) {
// return validation failure response
return $this->json([
'success' => false,
'errors' => $error_array
], 422);
}
// validated! save the entity
$em->flush();
// return successful response
return $this->json([
'success' => 'Changes have been saved!'
]);
}
/**
* @ParamConverter("jo_source", class="App\Entity\JobOrderSource")
* @IsGranted("job_order_source.update")
*/
public function deleteSubmit(EntityManagerInterface $em, JobOrderSource $jo_source)
{
// delete this object
$em->remove($jo_source);
$em->flush();
// response
$response = new Response();
$response->setStatusCode(Response::HTTP_OK);
$response->send();
}
protected function setObject(JobOrderSource $obj, Request $req)
{
// set and save values
$obj->setCode($req->request->get('code'))
->setName($req->request->get('name'))
->setCallbackURL($req->request->get('callback_url'));
}
protected function setQueryFilters($datatable, QueryBuilder $query)
{
if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) {
$query->where('q.name LIKE :filter')
->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%');
}
}
}

View file

@ -43,6 +43,7 @@ use App\Service\HubFilteringGeoChecker;
use App\Service\RiderTracker; use App\Service\RiderTracker;
use App\Service\PromoLogger; use App\Service\PromoLogger;
use App\Service\MapTools; use App\Service\MapTools;
use App\Service\JobOrderCallbackRouter;
use App\Entity\JobOrder; use App\Entity\JobOrder;
use App\Entity\CustomerVehicle; use App\Entity\CustomerVehicle;
@ -75,7 +76,8 @@ class JobOrderController extends APIController
InventoryManager $im, MQTTClient $mclient, InventoryManager $im, MQTTClient $mclient,
RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger, RiderAssignmentHandlerInterface $rah, PromoLogger $promo_logger,
HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger, HubSelector $hub_select, HubDistributor $hub_dist, HubFilterLogger $hub_filter_logger,
HubFilteringGeoChecker $hub_geofence, EntityManagerInterface $em) HubFilteringGeoChecker $hub_geofence, EntityManagerInterface $em,
JobOrderCallbackRouter $jo_callback)
{ {
$this->denyAccessUnlessGranted('tapi_jo.request', null, 'No access.'); $this->denyAccessUnlessGranted('tapi_jo.request', null, 'No access.');
@ -112,6 +114,10 @@ class JobOrderController extends APIController
// geofence // geofence
$is_covered = $geo->isCovered($data['long'], $data['lat']); $is_covered = $geo->isCovered($data['long'], $data['lat']);
// get the api_user
$api_user = $this->getUser();
$jo_source = $api_user->getSource();
if (!$is_covered) if (!$is_covered)
{ {
// TODO: put geofence error message in config file somewhere // TODO: put geofence error message in config file somewhere
@ -133,7 +139,8 @@ class JobOrderController extends APIController
->setModeOfPayment($data['payment_mode']) ->setModeOfPayment($data['payment_mode'])
->setAdvanceOrder($data['is_advance_order']) ->setAdvanceOrder($data['is_advance_order'])
->setStatusAutoAssign(AutoAssignStatus::NOT_ASSIGNED) ->setStatusAutoAssign(AutoAssignStatus::NOT_ASSIGNED)
->setLandmark($data['landmark']); ->setLandmark($data['landmark'])
->setJOSource($jo_source);
$jo->setCustomer($data['customer']); $jo->setCustomer($data['customer']);
$jo->setCustomerVehicle($data['customer_vehicle']); $jo->setCustomerVehicle($data['customer_vehicle']);
@ -375,6 +382,10 @@ class JobOrderController extends APIController
$em->flush(); $em->flush();
// send callback
//$old_status = null;
//$jo_callback->sendJOStatusCallback($jo, $old_status, $jo->getStatus());
// make invoice json data // make invoice json data
$invoice_data = [ $invoice_data = [
'total_price' => number_format($invoice->getTotalPrice(), 2, '.', ''), 'total_price' => number_format($invoice->getTotalPrice(), 2, '.', ''),

View file

@ -422,6 +422,13 @@ class JobOrder
*/ */
protected $ownership_type; protected $ownership_type;
// third party jo source linked to job order
/**
* @ORM\ManyToOne(targetEntity="App\Entity\JobOrderSource", inversedBy="job_orders")
* @ORM\JoinColumn(name="job_order_source_id", referencedColumnName="id", nullable=true)
*/
protected $jo_source;
public function __construct() public function __construct()
{ {
$this->date_create = new DateTime(); $this->date_create = new DateTime();
@ -446,6 +453,8 @@ class JobOrder
$this->phone_mobile = ''; $this->phone_mobile = '';
$this->will_wait = WillingToWaitContent::WILLING_TO_WAIT; $this->will_wait = WillingToWaitContent::WILLING_TO_WAIT;
$this->jo_source = null;
} }
public function getID() public function getID()
@ -1199,4 +1208,15 @@ class JobOrder
{ {
return $this->ownership_type; return $this->ownership_type;
} }
public function setJOSource($jo_source = null)
{
$this->jo_source = $jo_source;
return $this;
}
public function getJOSource()
{
return $this->jo_source;
}
} }

View file

@ -0,0 +1,114 @@
<?php
namespace App\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
* @ORM\Entity
* @ORM\Table(name="job_order_source")
*/
class JobOrderSource
{
// unique id
/**
* @ORM\Id
* @ORM\Column(type="integer")
* @ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// third party JO source
/**
* @ORM\Column(type="string", length=80, unique=true)
* @Assert\NotBlank()
*/
protected $code;
/**
* @ORM\Column(type="string", length=80)
* @Assert\NotBlank()
*/
protected $name;
// callback URL
/**
* @ORM\Column(type="string", length=150, nullable=true)
*/
protected $callback_url;
// one job order source can be associated to many api_users
/**
* @ORM\OneToMany(targetEntity="Catalyst\APIBundle\Entity\User", mappedBy="source")
*/
protected $api_users;
// one job order source can be associated to many job orders
/**
* @ORM\OneToMany(targetEntity="JobOrder", mappedBy="jo_source")
*/
protected $job_orders;
public function __construct()
{
$this->code = '';
$this->name = '';
$this->callback_url = '';
$this->api_users = new ArrayCollection();
$this->job_orders = new ArrayCollection();
}
public function getID()
{
return $this->id;
}
public function setCode($code)
{
$this->code = $code;
return $this;
}
public function getCode()
{
return $this->code;
}
public function setName($name)
{
$this->name = $name;
return $this;
}
public function getName()
{
return $this->name;
}
public function setCallbackURL($callback_url)
{
$this->callback_url = $callback_url;
return $this;
}
public function getCallbackURL()
{
return $this->callback_url;
}
public function getApiUsers()
{
return $this->api_users;
}
public function getJobOrders()
{
return $this->job_orders;
}
}

View file

@ -0,0 +1,54 @@
<?php
namespace App\EntityListener;
use Doctrine\ORM\Event\LifecycleEventArgs;
use Doctrine\ORM\Event\PreFlushEventArgs;
use Doctrine\ORM\Event\PreUpdateEventArgs;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\JobOrder;
use App\Service\JobOrderCallbackRouter;
class JobOrderStatusListener
{
protected $jo_callback;
public function __construct(JobOrderCallbackRouter $jo_callback)
{
$this->jo_callback = $jo_callback;
}
public function preUpdate(JobOrder $jo, PreUpdateEventArgs $args)
{
// error_log('PRE UPDATE');
// check if status of third party JO has changed
// check if JO is third party
$source = $jo->getJOSource();
if ($source != null)
{
if ($args->hasChangedField('status'))
{
// error_log('status has changed');
// send callback
$this->jo_callback->sendJOStatusCallback($jo);
}
}
}
public function postPersist(JobOrder $jo, LifecycleEventArgs $args)
{
// error_log('POST PERSIST');
// check if JO is third party
$source = $jo->getJOSource();
if ($source != null)
{
// need to send a callback for a new third party JO
$this->jo_callback->sendJOStatusCallback($jo);
}
}
}

View file

@ -0,0 +1,71 @@
<?php
namespace App\Service;
use App\Entity\JobOrder;
use App\Ramcar\JOStatus;
class JobOrderCallbackRouter
{
public function sendJOStatusCallback(JobOrder $jo, $old_status, $new_status)
{
// check status change
$is_status_change = $this->checkStatusChange($old_status, $new_status);
if ($is_status_change)
{
// get the job order source
$source = $jo->getJOSource();
if ($source != null)
{
// check if source has a callback url
$callback_url = $source->getCallbackURL();
if ($callback_url != null)
{
// form the body for the callback
$jo_data = [
'job_id' => $jo->getID(),
];
// send status
$this->sendJOInfo($jo_data, $callback_url);
}
}
}
}
protected function checkStatusChange($old_status, $new_status)
{
// NOTE: what do we do if a third party JO has been cancelled and is then fulfilled?
// This is a request resq asked for, to be able to fulfill cancelled JOs.
if ($old_status != $new_status)
return true;
return false;
}
protected function sendJOInfo($jo_data, $callback_url)
{
$params = http_build_query($jo_data);
//error_log('Sending output to ' . $callback_url . $params);
$curl = curl_init();
$options = [
CURLOPT_URL => $callback_url . $params,
CURLOPT_POST => false,
CURLOPT_RETURNTRANSFER => true,
];
curl_setopt_array($curl, $options);
$res = curl_exec($curl);
curl_close($curl);
// check result
error_log('Result ' . $res);
}
}

View file

@ -64,6 +64,7 @@ use App\Service\PromoLogger;
use App\Service\HubSelector; use App\Service\HubSelector;
use App\Service\HubDistributor; use App\Service\HubDistributor;
use App\Service\HubFilteringGeoChecker; use App\Service\HubFilteringGeoChecker;
use App\Service\JobOrderCallbackRouter;
use CrEOF\Spatial\PHP\Types\Geometry\Point; use CrEOF\Spatial\PHP\Types\Geometry\Point;
@ -90,6 +91,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
protected $hub_geofence; protected $hub_geofence;
protected $cust_distance_limit; protected $cust_distance_limit;
protected $hub_filter_enable; protected $hub_filter_enable;
protected $jo_callback;
protected $template_hash; protected $template_hash;
@ -98,7 +100,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
TranslatorInterface $translator, RiderAssignmentHandlerInterface $rah, TranslatorInterface $translator, RiderAssignmentHandlerInterface $rah,
string $country_code, WarrantyHandler $wh, RisingTideGateway $rt, string $country_code, WarrantyHandler $wh, RisingTideGateway $rt,
PromoLogger $promo_logger, HubDistributor $hub_dist, HubFilteringGeoChecker $hub_geofence, PromoLogger $promo_logger, HubDistributor $hub_dist, HubFilteringGeoChecker $hub_geofence,
string $cust_distance_limit, string $hub_filter_enabled) string $cust_distance_limit, string $hub_filter_enabled, JobOrderCallbackRouter $jo_callback)
{ {
$this->em = $em; $this->em = $em;
$this->ic = $ic; $this->ic = $ic;
@ -114,6 +116,7 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$this->hub_geofence = $hub_geofence; $this->hub_geofence = $hub_geofence;
$this->cust_distance_limit = $cust_distance_limit; $this->cust_distance_limit = $cust_distance_limit;
$this->hub_filter_enabled = $hub_filter_enabled; $this->hub_filter_enabled = $hub_filter_enabled;
$this->jo_callback = $jo_callback;
$this->loadTemplates(); $this->loadTemplates();
} }
@ -916,6 +919,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$ownertype_id = $req->request->get('ownership_type', 0); $ownertype_id = $req->request->get('ownership_type', 0);
$owner_type = $em->getRepository(OwnershipType::class)->find($ownertype_id); $owner_type = $em->getRepository(OwnershipType::class)->find($ownertype_id);
// get previous status
$old_status = $obj->getStatus();
if (empty($error_array)) if (empty($error_array))
{ {
// coordinates // coordinates
@ -984,6 +990,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// update redis hub jo count // update redis hub jo count
$this->hub_dist->incrementJoCountForHub($hub); $this->hub_dist->incrementJoCountForHub($hub);
// get current status and send JO callback
$current_status = $obj->getStatus();
$this->jo_callback->sendJOStatusCallback($obj, $old_status, $current_status);
} }
return $error_array; return $error_array;
@ -1068,6 +1078,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// get current user // get current user
$user = $this->security->getUser(); $user = $this->security->getUser();
// get previous status
$old_status = $obj->getStatus();
if (empty($error_array)) { if (empty($error_array)) {
// coordinates // coordinates
$point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat'));
@ -1140,6 +1153,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// call rider assignment handler's assignJobOrder // call rider assignment handler's assignJobOrder
$this->rah->assignJobOrder($obj, $rider); $this->rah->assignJobOrder($obj, $rider);
// get current status and send JO callback
$current_status = $obj->getStatus();
$this->jo_callback->sendJOStatusCallback($obj, $old_status, $current_status);
} }
return $error_array; return $error_array;
@ -1205,6 +1222,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$ownertype_id = $req->request->get('ownership_type', 0); $ownertype_id = $req->request->get('ownership_type', 0);
$owner_type = $em->getRepository(OwnershipType::class)->find($ownertype_id); $owner_type = $em->getRepository(OwnershipType::class)->find($ownertype_id);
// get previous status
$old_status = $obj->getStatus();
if (empty($error_array)) { if (empty($error_array)) {
// coordinates // coordinates
$point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat'));
@ -1269,6 +1289,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// validated! save the entity // validated! save the entity
$em->flush(); $em->flush();
// get current status and send JO callback
$current_status = $obj->getStatus();
$this->jo_callback->sendJOStatusCallback($obj, $old_status, $current_status);
// get rider // get rider
$rider = $obj->getRider(); $rider = $obj->getRider();
@ -1347,6 +1371,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
if (empty($obj)) if (empty($obj))
throw new NotFoundHttpException('The item does not exist'); throw new NotFoundHttpException('The item does not exist');
// get previous status
$old_status = $obj->getStatus();
$cancel_reason = $req->request->get('cancel_reason'); $cancel_reason = $req->request->get('cancel_reason');
//error_log('cancel_reason ' . $cancel_reason); //error_log('cancel_reason ' . $cancel_reason);
@ -1382,6 +1409,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
]; ];
$mclient->sendEvent($obj, $payload); $mclient->sendEvent($obj, $payload);
$mclient->sendRiderEvent($obj, $payload); $mclient->sendRiderEvent($obj, $payload);
// get current status and send JO callback
$current_status = $obj->getStatus();
$this->jo_callback->sendJOStatusCallback($obj, $old_status, $current_status);
} }
// set hub for job order // set hub for job order
@ -1465,6 +1496,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
// get previously assigned rider, if any // get previously assigned rider, if any
$old_rider = $obj->getRider(); $old_rider = $obj->getRider();
// get previous status
$old_status = $obj->getStatus();
if (empty($error_array)) if (empty($error_array))
{ {
// rider mqtt event // rider mqtt event
@ -1566,6 +1600,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$this->hub_dist->decrementJoCountForHub($old_hub); $this->hub_dist->decrementJoCountForHub($old_hub);
if ($hub != null) if ($hub != null)
$this->hub_dist->incrementJoCountForHub($hub); $this->hub_dist->incrementJoCountForHub($hub);
// get current status and send JO callback
$current_status = $obj->getStatus();
$this->jo_callback->sendJOStatusCallback($obj, $old_status, $current_status);
} }
return $error_array; return $error_array;
@ -1744,6 +1782,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$ownertype_id = $req->request->get('ownership_type', 0); $ownertype_id = $req->request->get('ownership_type', 0);
$owner_type = $em->getRepository(OwnershipType::class)->find($ownertype_id); $owner_type = $em->getRepository(OwnershipType::class)->find($ownertype_id);
// get previous status
$old_status = $obj->getStatus();
if (empty($error_array)) { if (empty($error_array)) {
// rider mqtt event // rider mqtt event
// NOTE: need to send this before saving because rider will be cleared // NOTE: need to send this before saving because rider will be cleared
@ -1845,6 +1886,10 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
]; ];
$mclient->sendEvent($obj, $payload); $mclient->sendEvent($obj, $payload);
$mclient->sendRiderEvent($obj, $payload); $mclient->sendRiderEvent($obj, $payload);
// get current status and send JO callback
$current_status = $obj->getStatus();
$this->jo_callback->sendJOStatusCallback($obj, $old_status, $current_status);
} }
return $error_array; return $error_array;
@ -3986,6 +4031,9 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
if (empty($obj)) if (empty($obj))
throw new NotFoundHttpException('The item does not exist'); throw new NotFoundHttpException('The item does not exist');
// get previous status
$old_status = $obj->getStatus();
$obj->fulfill(); $obj->fulfill();
// the event // the event
@ -4004,6 +4052,16 @@ class ResqJobOrderHandler implements JobOrderHandlerInterface
$event->setUser($user); $event->setUser($user);
$em->persist($event); $em->persist($event);
$em->flush(); $em->flush();
// get current status and send JO callback
$current_status = $obj->getStatus();
$this->jo_callback->sendJOStatusCallback($obj, $old_status, $current_status);
$data = [
'error_array' => $error_array,
];
return $data;
} }
public function sendSMSToCustomer($phone_number) public function sendSMSToCustomer($phone_number)

View file

@ -45,6 +45,20 @@
</div> </div>
</div> </div>
</div> </div>
<div class="form-group m-form__group row no-border">
<div class="col-lg-8">
<label data-field="source">
Source
</label>
<select class="form-control m-input" id="source_id" name="source_id">
<option value=""></option>
{% for source in sources %}
<option value="{{ source.getID }}"{{ source.getID == obj.getSource.getID|default(0) ? ' selected' }}>{{ source.getName }}</option>
{% endfor %}
</select>
<div class="form-control-feedback hide" data-field="source_id"></div>
</div>
</div>
<div class="form-group m-form__group row no-border"> <div class="form-group m-form__group row no-border">
<div class="col-lg-8"> <div class="col-lg-8">
<label data-field="brand"> <label data-field="brand">

View file

@ -0,0 +1,151 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">Job Order Sources</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<div class="row">
<div class="col-xl-6">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__head">
<div class="m-portlet__head-caption">
<div class="m-portlet__head-title">
<span class="m-portlet__head-icon">
<i class="la la-industry"></i>
</span>
<h3 class="m-portlet__head-text">
{% if mode == 'update' %}
Edit Job Order Source
<small>{{ jo_source.getName }}</small>
{% else %}
New Job Order Source
{% endif %}
</h3>
</div>
</div>
</div>
<form id="row-form" class="m-form m-form--fit m-form--label-align-right m-form--group-seperator-dashed" method="post" action="{{ mode == 'update' ? url('job_order_source_update_submit', {'id': jo_source.getId()}) : url('job_order_source_add_submit') }}">
<div class="m-portlet__body">
<div class="form-group m-form__group row no-border">
<label class="col-lg-3 col-form-label" data-field="code">
Code:
</label>
<div class="col-lg-9">
<input type="text" name="code" class="form-control m-input" value="{{ jo_source.getCode() }}">
<div class="form-control-feedback hide" data-field="code"></div>
</div>
</div>
<div class="form-group m-form__group row no-border">
<label class="col-lg-3 col-form-label" data-field="name">
Name:
</label>
<div class="col-lg-9">
<input type="text" name="name" class="form-control m-input" value="{{ jo_source.getName() }}">
<div class="form-control-feedback hide" data-field="name"></div>
</div>
</div>
<div class="form-group m-form__group row">
<label class="col-lg-3 col-form-label" data-field="callback_url">
Callback URL:
</label>
<div class="col-lg-9">
<input type="text" name="callback_url" class="form-control m-input" value="{{ jo_source.getCallbackURL() }}">
<div class="form-control-feedback hide" data-field="callback_url"></div>
</div>
</div>
</div>
<div class="m-portlet__foot m-portlet__foot--fit">
<div class="m-form__actions m-form__actions--solid m-form__actions--right">
<div class="row">
<div class="col-lg-12">
<button type="submit" class="btn btn-success">Submit</button>
<a href="{{ url('job_order_source_list') }}" class="btn btn-secondary">Back</a>
</div>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(function() {
$("#row-form").submit(function(e) {
var form = $(this);
e.preventDefault();
$.ajax({
method: "POST",
url: form.prop('action'),
data: form.serialize()
}).done(function(response) {
// remove all error classes
removeErrors();
swal({
title: 'Done!',
text: 'Your changes have been saved!',
type: 'success',
onClose: function() {
window.location.href = "{{ url('job_order_source_list') }}";
}
});
}).fail(function(response) {
if (response.status == 422) {
var errors = response.responseJSON.errors;
var firstfield = false;
// remove all error classes first
removeErrors();
// display errors contextually
$.each(errors, function(field, msg) {
var formfield = $("[name='" + field + "']");
var label = $("label[data-field='" + field + "']");
var msgbox = $(".form-control-feedback[data-field='" + field + "']");
// add error classes to bad fields
formfield.addClass('form-control-danger');
label.addClass('has-danger');
msgbox.html(msg).addClass('has-danger').removeClass('hide');
// check if this field comes first in DOM
var domfield = formfield.get(0);
if (!firstfield || (firstfield && firstfield.compareDocumentPosition(domfield) === 2)) {
firstfield = domfield;
}
});
// focus on first bad field
firstfield.focus();
// scroll to above that field to make it visible
$('html, body').animate({
scrollTop: $(firstfield).offset().top - 200
}, 100);
}
});
});
// remove all error classes
function removeErrors() {
$(".form-control-danger").removeClass('form-control-danger');
$("[data-field]").removeClass('has-danger');
$(".form-control-feedback[data-field]").addClass('hide');
}
});
</script>
{% endblock %}

View file

@ -0,0 +1,146 @@
{% extends 'base.html.twig' %}
{% block body %}
<!-- BEGIN: Subheader -->
<div class="m-subheader">
<div class="d-flex align-items-center">
<div class="mr-auto">
<h3 class="m-subheader__title">
Job Order Sources
</h3>
</div>
</div>
</div>
<!-- END: Subheader -->
<div class="m-content">
<!--Begin::Section-->
<div class="row">
<div class="col-xl-12">
<div class="m-portlet m-portlet--mobile">
<div class="m-portlet__body">
<div class="m-form m-form--label-align-right m--margin-top-20 m--margin-bottom-30">
<div class="row align-items-center">
<div class="col-xl-8 order-2 order-xl-1">
<div class="form-group m-form__group row align-items-center">
<div class="col-md-4">
<div class="m-input-icon m-input-icon--left">
<input type="text" class="form-control m-input m-input--solid" placeholder="Search..." id="data-rows-search">
<span class="m-input-icon__icon m-input-icon__icon--left">
<span><i class="la la-search"></i></span>
</span>
</div>
</div>
</div>
</div>
<div class="col-xl-4 order-1 order-xl-2 m--align-right">
<a href="{{ url('job_order_source_add_form') }}" class="btn btn-focus m-btn m-btn--custom m-btn--icon m-btn--air m-btn--pill">
<span>
<i class="la la-industry"></i>
<span>New Job Order Source</span>
</span>
</a>
<div class="m-separator m-separator--dashed d-xl-none"></div>
</div>
</div>
</div>
<!--begin: Datatable -->
<div id="data-rows"></div>
<!--end: Datatable -->
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
$(function() {
var options = {
data: {
type: 'remote',
source: {
read: {
url: '{{ url("job_order_source_rows") }}',
method: 'POST'
}
},
saveState: {
cookie: false,
webstorage: false
},
pageSize: 10,
serverPaging: true,
serverFiltering: true,
serverSorting: true
},
layout: {
scroll: true
},
columns: [
{
field: 'id',
title: 'ID',
width: 30
},
{
field: 'name',
title: 'Name'
},
{
field: 'Actions',
width: 110,
title: 'Actions',
sortable: false,
overflow: 'visible',
template: function (row, index, datatable) {
var actions = '';
if (row.meta.update_url != '') {
actions += '<a href="' + row.meta.update_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-accent m-btn--icon m-btn--icon-only m-btn--pill btn-edit" data-id="' + row.id + '" title="Edit"><i class="la la-edit"></i></a>';
}
if (row.meta.delete_url != '') {
actions += '<a href="' + row.meta.delete_url + '" class="m-portlet__nav-link btn m-btn m-btn--hover-danger m-btn--icon m-btn--icon-only m-btn--pill btn-delete" data-id="' + row.id + '" title="Delete"><i class="la la-trash"></i></a>';
}
return actions;
},
}
],
search: {
onEnter: false,
input: $('#data-rows-search'),
delay: 400
}
};
var table = $("#data-rows").mDatatable(options);
$(document).on('click', '.btn-delete', function(e) {
var url = $(this).prop('href');
var id = $(this).data('id');
var btn = $(this);
e.preventDefault();
swal({
title: 'Confirmation',
html: 'Are you sure you want to delete <strong>' + id + '</strong>?',
type: 'warning',
showCancelButton: true
}).then((result) => {
if (result.value) {
$.ajax({
method: "DELETE",
url: url
}).done(function(response) {
table.row(btn.parents('tr')).remove();
table.reload();
});
}
});
});
});
</script>
{% endblock %}

View file

@ -0,0 +1,54 @@
-- MySQL dump 10.19 Distrib 10.3.36-MariaDB, for Linux (x86_64)
--
-- Host: localhost Database: resq
-- ------------------------------------------------------
-- Server version 10.3.36-MariaDB
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
--
-- Table structure for table `job_order_source`
--
DROP TABLE IF EXISTS `job_order_source`;
/*!40101 SET @saved_cs_client = @@character_set_client */;
/*!40101 SET character_set_client = utf8 */;
CREATE TABLE `job_order_source` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`code` varchar(80) COLLATE utf8_unicode_ci NOT NULL,
`callback_url` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL,
`name` varchar(80) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `UNIQ_9366FA8977153098` (`code`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
/*!40101 SET character_set_client = @saved_cs_client */;
--
-- Dumping data for table `job_order_source`
--
LOCK TABLES `job_order_source` WRITE;
/*!40000 ALTER TABLE `job_order_source` DISABLE KEYS */;
INSERT INTO `job_order_source` VALUES (1,'drivehub','https://ny61i0s3rj.execute-api.ap-southeast-1.amazonaws.com/dev/callback?','DriveHub');
/*!40000 ALTER TABLE `job_order_source` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
-- Dump completed on 2023-02-21 3:08:45