diff --git a/config/routes/job_order.yaml b/config/routes/job_order.yaml index 14375bcb..05cb08c8 100644 --- a/config/routes/job_order.yaml +++ b/config/routes/job_order.yaml @@ -170,3 +170,8 @@ jo_open_edit_submit: path: /job-order/{id}/open-edit controller: App\Controller\JobOrderController::openEditSubmit methods: [POST] + +jo_reject_hub: + path: /job-order/{id}/reject-hub + controller: App\Controller\JobOrderController::rejectHubSubmit + methods: [POST] diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index 6641e247..3a5a8efa 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -14,6 +14,7 @@ use App\Ramcar\ModeOfPayment; use App\Ramcar\TransactionOrigin; use App\Ramcar\JOEventType; use App\Ramcar\FacilitatedType; +use App\Ramcar\JORejectionReason; use App\Entity\JobOrder; use App\Entity\BatteryManufacturer; @@ -25,6 +26,7 @@ use App\Entity\Promo; use App\Entity\Rider; use App\Entity\Battery; use App\Entity\JOEvent; +use App\Entity\JORejection; use App\Service\InvoiceCreator; use App\Service\MapTools; @@ -899,6 +901,12 @@ class JobOrderController extends BaseController $this->fillDropdownParameters($params); $this->fillFormTags($params); + // get rejections + $rejections = $obj->getHubRejections(); + + // get rejection reasons + $params['rejection_reasons'] = JORejectionReason::getCollection(); + // get closest hubs $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); @@ -930,6 +938,19 @@ class JobOrderController extends BaseController // counters $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + foreach ($rejections as $robj) + { + if ($robj->getHub()->getID() === $hub_id) + { + $hub['flag_rejected'] = true; + break; + } + } $params['hubs'][] = $hub; } @@ -1505,6 +1526,12 @@ class JobOrderController extends BaseController $this->fillDropdownParameters($params); $this->fillFormTags($params); + // get rejections + $rejections = $obj->getHubRejections(); + + // get rejection reasons + $params['rejection_reasons'] = JORejectionReason::getCollection(); + // get closest hubs $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 50, date("H:i:s")); @@ -1538,6 +1565,19 @@ class JobOrderController extends BaseController $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); + // check for rejection + $hub['flag_rejected'] = false; + $hub_id = $hub['hub']->getID(); + + foreach ($rejections as $robj) + { + if ($robj->getHub()->getID() === $hub_id) + { + $hub['flag_rejected'] = true; + break; + } + } + $params['hubs'][] = $hub; } @@ -2540,4 +2580,103 @@ class JobOrderController extends BaseController // redirect to list return $this->redirectToRoute('jo_assign'); } + + public function rejectHubSubmit(Request $req, ValidatorInterface $validator, $id) + { + $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); + + // get object data + $em = $this->getDoctrine()->getManager(); + $jo = $em->getRepository(JobOrder::class)->find($id); + $processor = $jo->getProcessedBy(); + $user = $this->getUser(); + + // check if we're the one processing, return error otherwise + if ($processor == null) + throw $this->createAccessDeniedException('Not the processor'); + + if ($processor != null && $processor->getID() != $user->getID()) + throw $this->createAccessDeniedException('Not the processor'); + + // initialize error list + $error_array = []; + + // make sure job order exists + if (empty($jo)) + throw $this->createNotFoundException('The item does not exist'); + + // check if hub is set + if (empty($req->request->get('hub'))) + { + $error_array['hub'] = 'No hub selected.'; + } + else + { + // get hub + $hub = $em->getRepository(Hub::class)->find($req->request->get('hub')); + + if (empty($hub)) + { + $error_array['hub'] = 'Invalid hub specified.'; + } + } + + // check if this hub has already been rejected on this job order + $robj = $em->getRepository(JORejection::class)->findOneBy([ + 'job_order' => $jo, + 'hub' => $hub + ]); + + if (!empty($robj)) + $error_array['hub'] = 'This hub has already been rejected for the current job order.'; + + // check if reason is set + if (empty($req->request->get('reason'))) + $error_array['reason'] = 'No reason selected.'; + else if (!JORejectionReason::validate($req->request->get('reason'))) + $error_array['reason'] = 'Invalid reason specified.'; + + if (empty($error_array)) + { + // coordinates + $obj = new JORejection(); + + // set and save values + $obj->setDateCreate(new DateTime()) + ->setHub($hub) + ->setUser($this->getUser()) + ->setJobOrder($jo) + ->setReason($req->request->get('reason')) + ->setRemarks($req->request->get('remarks')) + ->setContactPerson($req->request->get('contact_person')); + + // validate + $errors = $validator->validate($obj); + + // 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($obj); + $em->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!', + 'request' => $req->request->all() + ]); + } } diff --git a/src/Ramcar/JORejectionReason.php b/src/Ramcar/JORejectionReason.php new file mode 100644 index 00000000..a07042e6 --- /dev/null +++ b/src/Ramcar/JORejectionReason.php @@ -0,0 +1,26 @@ + 'ADMINISTRATIVE', + 'no_stock_sales' => 'NO STOCK - SALES', + 'no_stock_service' => 'NO STOCK - SERVICE', + 'line_no_answer' => 'LINE NO ANSWER', + 'line_busy' => 'LINE BUSY', + 'no_rider_available' => 'NO RIDER - AVAILABLE', + 'no_rider_in_transit' => 'NO RIDER - IN TRANSIT', + 'refusal' => 'REFUSAL', + ]; +} diff --git a/templates/job-order/form.html.twig b/templates/job-order/form.html.twig index 1d7b2d27..4d654fa4 100644 --- a/templates/job-order/form.html.twig +++ b/templates/job-order/form.html.twig @@ -584,7 +584,11 @@ {{ hub.jo_count }} {{ hub.hub.getContactNumbers|replace({"\n": ', '}) }} - + {% if hub.flag_rejected %} + + {% else %} + + {% endif %} {% endfor %} @@ -823,6 +827,68 @@ + + {% if mode in ['update-processing', 'update-reassign-hub'] %} + + + {% endif %} {% endblock %} {% block scripts %} @@ -1014,10 +1080,10 @@ $(function() { }); // 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'); + function removeErrors(formSelector = '') { + $(formSelector + " .form-control-danger").removeClass('form-control-danger'); + $(formSelector + " [data-field]").removeClass('has-danger'); + $(formSelector + " .form-control-feedback[data-field]").addClass('hide'); } // store selected vehicle data @@ -1474,10 +1540,92 @@ $(function() { var ticketTable = $("#data-tickets").mDatatable(ticketOptions); {% endif %} - // reject job order - $(".button-reject").click(function(e) { - return false; - }); + {% if mode in ['update-processing', 'update-reassign-hub'] %} + // reject hub + $(".btn-reject").click(function(e) { + var hubID = $(this).data('hub-id'); + var hubName = $(this).data('hub-name'); + var hubBranch = $(this).data('hub-branch'); + + $("#hub-reject-id").val(hubID); + $("#hub-reject-name").val(hubName); + $("#hub-reject-branch").val(hubBranch); + $("#modal-rejection").modal('show'); + + return false; + }); + + // focus on reason field + $("#modal-rejection").on('shown.bs.modal', function() { + $("#hub-reject-reason").focus(); + }); + + // reset reject form on close modal + $("#modal-rejection").on('hidden.bs.modal', function() { + $("#hub-reject-contact-person, #hub-reject-remarks").val(""); + $("#hub-reject-reason").prop('selectedIndex', 0); + }); + + // submit hub rejection + $("#hub-reject-form").submit(function(e) { + var form = $(this); + var btnSubmit = form.find('[type="submit"]'); + + // disable the button + btnSubmit.prop('disabled', true).html(' Please wait'); + + e.preventDefault(); + + $.ajax({ + method: "POST", + url: form.prop('action'), + data: form.serialize() + }).done(function(response) { + // set button state + var btnReject = $("#hubs-table").find("tr[data-id='" + $("#hub-reject-id").val() + "']").find('.btn-reject'); + btnReject.removeClass('btn-reject btn-info').addClass('btn-danger btn-disabled').prop('disabled', true).html('Rejected'); + + // remove all error classes + removeErrors("#hub-reject-form"); + btnSubmit.prop('disabled', false).html('Reject'); + + // hide modal + $("#modal-rejection").modal('hide'); + }).fail(function(response) { + if (response.status == 422) { + form_in_process = false; + var errors = response.responseJSON.errors; + var firstfield = false; + + // remove all error classes first + removeErrors("#hub-reject-form"); + btnSubmit.prop('disabled', false).html('Reject'); + + // display errors contextually + $.each(errors, function(field, msg) { + var formfield = form.find("[name='" + field + "'], [data-name='" + field + "']"); + var label = form.find("label[data-field='" + field + "']"); + var msgbox = form.find(".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(); + } + }); + }); + {% endif %} // cancel job order $(".btn-cancel-job-order").click(function(e) {