From c0ecc1114c49e75f954bedcc4a42977cc5f32eff Mon Sep 17 00:00:00 2001 From: Ramon Gutierrez Date: Wed, 31 Jan 2018 02:31:04 +0800 Subject: [PATCH] Initial commit for assigning screens for job orders --- config/acl.yaml | 2 + config/menu.yaml | 4 + config/routes/job_order.yaml | 48 +++- public/assets/css/style.css | 17 ++ src/Controller/JobOrderController.php | 210 ++++++++++++++++-- src/Entity/JobOrder.php | 2 +- src/Ramcar/JOStatus.php | 2 + templates/job-order/form.html.twig | 305 ++++++++++++++++++++------ templates/job-order/list.html.twig | 4 +- 9 files changed, 497 insertions(+), 97 deletions(-) diff --git a/config/acl.yaml b/config/acl.yaml index a1702a18..38e9228d 100644 --- a/config/acl.yaml +++ b/config/acl.yaml @@ -185,3 +185,5 @@ access_keys: label: Incoming - id: jo_proc.list label: Processing + - id: jo_assign.list + label: Assigning diff --git a/config/menu.yaml b/config/menu.yaml index a5d0edb0..f7f1477b 100644 --- a/config/menu.yaml +++ b/config/menu.yaml @@ -89,4 +89,8 @@ main_menu: acl: jo_proc.list label: Processing parent: joborder + - id: jo_assign + acl: jo_assign.list + label: Assigning + parent: joborder diff --git a/config/routes/job_order.yaml b/config/routes/job_order.yaml index 1ba355a2..d20a8563 100644 --- a/config/routes/job_order.yaml +++ b/config/routes/job_order.yaml @@ -3,21 +3,25 @@ jo_in: controller: App\Controller\JobOrderController::incomingForm methods: [GET] -jo_proc: - path: /job-order/processing - controller: App\Controller\JobOrderController::processingList - methods: [GET] - -jo_proc_rows: - path: /job-order/processing-rows - controller: App\Controller\JobOrderController::processingRows - methods: [POST] - jo_in_submit: path: /job-order/incoming controller: App\Controller\JobOrderController::incomingSubmit methods: [POST] +jo_proc: + path: /job-order/processing + controller: App\Controller\JobOrderController::listRows + methods: [GET] + defaults: + tier: "proc" + +jo_proc_rows: + path: /job-order/processing-rows + controller: App\Controller\JobOrderController::getRows + methods: [POST] + defaults: + tier: "proc" + jo_proc_form: path: /job-order/processing/{id} controller: App\Controller\JobOrderController::processingForm @@ -27,3 +31,27 @@ jo_proc_submit: path: /job-order/processing/{id} controller: App\Controller\JobOrderController::processingSubmit methods: [POST] + +jo_assign: + path: /job-order/assigning + controller: App\Controller\JobOrderController::listRows + methods: [GET] + defaults: + tier: "assign" + +jo_assign_rows: + path: /job-order/assigning-rows + controller: App\Controller\JobOrderController::getRows + methods: [POST] + defaults: + tier: "assign" + +jo_assign_form: + path: /job-order/assigning/{id} + controller: App\Controller\JobOrderController::assigningForm + methods: [GET] + +jo_assign_submit: + path: /job-order/assigning/{id} + controller: App\Controller\JobOrderController::assigningSubmit + methods: [POST] \ No newline at end of file diff --git a/public/assets/css/style.css b/public/assets/css/style.css index 41ae33cb..fbb7b214 100644 --- a/public/assets/css/style.css +++ b/public/assets/css/style.css @@ -12,6 +12,19 @@ span.has-danger, border-color: #f4516c; } +.table-frame { + margin-bottom: 1rem; +} + +.table-frame.form-control-danger { + border-style: solid; + border-width: 1px; +} + +.table-frame > .table { + margin-bottom: 0; +} + .hide { display: none !important; } @@ -104,6 +117,10 @@ span.has-danger, font-weight: 400; } +.table-vcenter td { + vertical-align: middle; +} + .placeholder-row td { background-color: #fff !important; padding: 32px 0 !important; diff --git a/src/Controller/JobOrderController.php b/src/Controller/JobOrderController.php index d6cb9550..c3f56caa 100644 --- a/src/Controller/JobOrderController.php +++ b/src/Controller/JobOrderController.php @@ -32,6 +32,8 @@ class JobOrderController extends BaseController $params = $this->initParameters('jo_in'); $params['obj'] = new JobOrder(); $params['mode'] = 'create'; + $params['submit_url'] = $this->generateUrl('jo_in_submit'); + $params['return_url'] = $this->generateUrl('jo_in'); $em = $this->getDoctrine()->getManager(); @@ -122,19 +124,60 @@ class JobOrderController extends BaseController ]); } - public function processingList() + protected function checkTier($tier) { - $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); + // check specified tier + switch ($tier) { + case 'proc': + $tier_key = 'jo_proc'; + $tier_name = 'Processing'; + $rows_route = 'jo_proc_rows'; + $edit_route = 'jo_proc_form'; + $jo_status = JOStatus::PENDING; + break; + case 'assign': + $tier_key = 'jo_assign'; + $tier_name = 'Assigning'; + $rows_route = 'jo_assign_rows'; + $edit_route = 'jo_assign_form'; + $jo_status = JOStatus::RIDER_ASSIGN; + break; + default: + $exception = $this->createAccessDeniedException('No access.'); + throw $exception; + } - $params = $this->initParameters('jo_proc'); + // check acl + $this->denyAccessUnlessGranted($tier_key . '.list', null, 'No access.'); + + // return params if allowed access + return [ + 'key' => $tier_key, + 'name' => $tier_name, + 'rows_route' => $rows_route, + 'edit_route' => $edit_route, + 'jo_status' => $jo_status + ]; + } + + public function listRows($tier) + { + // check which job order tier is being called for and confirm access + $tier_params = $this->checkTier($tier); + + $params = $this->initParameters($tier_params['key']); + + $params['tier_name'] = $tier_params['name']; + $params['rows_route'] = $tier_params['rows_route']; // response return $this->render('job-order/list.html.twig', $params); } - public function processingRows(Request $req) + public function getRows(Request $req, $tier) { - $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); + // check which job order tier is being called for and confirm access + $tier_params = $this->checkTier($tier); // get query builder $qb = $this->getDoctrine() @@ -149,7 +192,7 @@ class JobOrderController extends BaseController ->join('q.cus_vehicle', 'cv') ->join('q.customer', 'c'); - $this->setQueryFilters($datatable, $tquery, $qb); + $this->setQueryFilters($datatable, $tquery, $qb, $tier_params['jo_status']); $total = $tquery->getQuery() ->getSingleScalarResult(); @@ -176,7 +219,7 @@ class JobOrderController extends BaseController ->addSelect('c.first_name as customer_name') ->addSelect('c.last_name as cust_last_name'); - $this->setQueryFilters($datatable, $query, $qb); + $this->setQueryFilters($datatable, $query, $qb, $tier_params['jo_status']); // check if sorting is present, otherwise use default if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { @@ -222,7 +265,7 @@ class JobOrderController extends BaseController $row['flag_advance'] = $orow[0]->isAdvanceOrder(); // add crud urls - $row['meta']['update_url'] = $this->generateUrl('jo_proc_form', ['id' => $row['id']]); + $row['meta']['update_url'] = $this->generateUrl($tier_params['edit_route'], ['id' => $row['id']]); $rows[] = $row; } @@ -239,7 +282,7 @@ class JobOrderController extends BaseController $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); $params = $this->initParameters('jo_proc'); - $params['mode'] = 'update'; + $params['mode'] = 'update-processing'; // get row data $em = $this->getDoctrine()->getManager(); @@ -252,8 +295,6 @@ class JobOrderController extends BaseController // get parent associations $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); $params['customers'] = $em->getRepository(Customer::class)->findAll(); - $params['outlet'] = $em->getRepository(Outlet::class)->findAll(); - $params['rider'] = $em->getRepository(Rider::class)->findAll(); $params['service_types'] = ServiceType::getCollection(); $params['statuses'] = JOStatus::getCollection(); @@ -289,6 +330,8 @@ class JobOrderController extends BaseController } $params['obj'] = $obj; + $params['submit_url'] = $this->generateUrl('jo_proc_submit', ['id' => $obj->getID()]); + $params['return_url'] = $this->generateUrl('jo_proc'); // response return $this->render('job-order/form.html.twig', $params); @@ -296,7 +339,7 @@ class JobOrderController extends BaseController public function processingSubmit(Request $req, ValidatorInterface $validator, $id) { - $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); + $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); // initialize error list $error_array = []; @@ -309,8 +352,6 @@ class JobOrderController extends BaseController if (empty($obj)) throw $this->createNotFoundException('The item does not exist'); - error_log(print_r($req->request->all(), true)); - // check if lat and lng are provided if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; @@ -371,12 +412,149 @@ class JobOrderController extends BaseController ]); } + public function assigningForm(MapTools $map_tools, $id) + { + $this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.'); + + $params = $this->initParameters('jo_assign'); + $params['mode'] = 'update-assigning'; + + // get row data + $em = $this->getDoctrine()->getManager(); + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this row exists + if (empty($obj)) + throw $this->createNotFoundException('The item does not exist'); + + // get parent associations + $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); + $params['customers'] = $em->getRepository(Customer::class)->findAll(); + $params['service_types'] = ServiceType::getCollection(); + $params['statuses'] = JOStatus::getCollection(); + + // get closest outlets + $outlets = $map_tools->getClosestOutlets($obj->getCoordinates(), 10, date("H:i:s")); + + $params['outlets'] = []; + + // format duration and distance into friendly time + foreach ($outlets as $outlet) { + // duration + $seconds = $outlet['duration']; + + if (!empty($seconds) && $seconds > 0) { + $hours = floor($seconds / 3600); + $minutes = ceil(($seconds / 60) % 60); + + $outlet['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); + } else { + $outlet['duration'] = false; + } + + // distance + $meters = $outlet['distance']; + + if (!empty($meters) && $meters > 0) { + $outlet['distance'] = round($meters / 1000) . " km"; + } else { + $outlet['distance'] = false; + } + + $params['outlets'][] = $outlet; + } + + $params['obj'] = $obj; + $params['submit_url'] = $this->generateUrl('jo_assign_submit', ['id' => $obj->getID()]); + $params['return_url'] = $this->generateUrl('jo_assign'); + + // response + return $this->render('job-order/form.html.twig', $params); + } + + public function assigningSubmit(Request $req, ValidatorInterface $validator, $id) + { + $this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.'); + + // initialize error list + $error_array = []; + + // get object data + $em = $this->getDoctrine()->getManager(); + $obj = $em->getRepository(JobOrder::class)->find($id); + + // make sure this object exists + if (empty($obj)) + throw $this->createNotFoundException('The item does not exist'); + + // check if lat and lng are provided + if (empty($req->request->get('coord_lng')) || empty($req->request->get('coord_lat'))) { + $error_array['coordinates'] = 'No map coordinates provided. Please click on a location on the map.'; + } + + // check if rider is set + if (empty($req->request->get('rider'))) { + $error_array['rider'] = 'No rider selected.'; + } else { + // get rider + $rider = $em->getRepository(Rider::class)->find($req->request->get('rider')); + + if (empty($rider)) { + $error_array['rider'] = 'Invalid rider specified.'; + } + } + + if (empty($error_array)) { + // coordinates + $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); + + // set and save values + $obj->setDateSchedule(DateTime::createFromFormat("d M Y h:i A", $req->request->get('date_schedule_date') . " " . $req->request->get('date_schedule_time'))) + ->setCoordinates($point) + ->setAdvanceOrder($req->request->get('flag_advance') ?? false) + ->setServiceType($req->request->get('service_type')) + ->setSource('web') + ->setStatus($req->request->get('status')) + ->setDeliveryInstructions($req->request->get('delivery_instructions')) + ->setAgentNotes($req->request->get('agent_notes')) + ->setDeliveryAddress($req->request->get('delivery_address')) + ->setRider($rider); + + // 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->flush(); + + // return successful response + return $this->json([ + 'success' => 'Changes have been saved!' + ]); + } + + // TODO: re-enable search, figure out how to group the orWhere filters into one, so can execute that plus the pending filter // check if datatable filter is present and append to query - protected function setQueryFilters($datatable, &$query, $qb) { + protected function setQueryFilters($datatable, &$query, $qb, $status) { + error_log($status); $query->where('q.status = :status') - ->setParameter('status', 'pending'); + ->setParameter('status', $status); // get only pending rows /* diff --git a/src/Entity/JobOrder.php b/src/Entity/JobOrder.php index 1b30ef9c..65b0b246 100644 --- a/src/Entity/JobOrder.php +++ b/src/Entity/JobOrder.php @@ -111,7 +111,7 @@ class JobOrder // status of the job order /** - * @ORM\Column(type="string", length=10) + * @ORM\Column(type="string", length=15) */ protected $status; diff --git a/src/Ramcar/JOStatus.php b/src/Ramcar/JOStatus.php index 3b5440e3..1aec469b 100644 --- a/src/Ramcar/JOStatus.php +++ b/src/Ramcar/JOStatus.php @@ -5,6 +5,7 @@ namespace App\Ramcar; class JOStatus { const PENDING = 'pending'; + const RIDER_ASSIGN = 'rider_assign'; const ASSIGNED = 'assigned'; const IN_PROGRESS = 'in_progress'; const CANCELLED = 'cancelled'; @@ -12,6 +13,7 @@ class JOStatus const COLLECTION = [ 'pending' => 'Pending', + 'rider_assign' => 'For Rider Assignment', 'assigned' => 'Assigned', 'in_progress' => 'In Progress', 'cancelled' => 'Cancelled', diff --git a/templates/job-order/form.html.twig b/templates/job-order/form.html.twig index a7a3ff22..329ad149 100644 --- a/templates/job-order/form.html.twig +++ b/templates/job-order/form.html.twig @@ -22,9 +22,12 @@

- {% if mode == 'update' %} + {% if mode == 'update-processing' %} Processing {{ obj.getID() }} + {% elseif mode == 'update-assigning' %} + Assigning + {{ obj.getID() }} {% else %} Incoming {% endif %} @@ -32,7 +35,7 @@ -
+
{% if mode == 'create' %} @@ -47,7 +50,7 @@
{% endif %} -
+

Customer Details @@ -202,7 +205,7 @@ @@ -226,6 +229,7 @@
+
@@ -237,7 +241,6 @@
-

@@ -320,7 +323,7 @@
- {% if mode == 'update' %} + {% if mode == 'update-processing' %}
@@ -330,57 +333,195 @@
- - - - - - - - - - - - - - - - - - - - - {% for outlet in outlets %} - - - - - - - - - - - + + +
+
NameAddressNumbersOpeningClosingSales CountSales AmountService CountDistanceTravel Time
- No items to display. -
{{ outlet.outlet.getName }}{{ outlet.outlet.getAddress }}{{ outlet.outlet.getContactNumbers }}{{ outlet.outlet.getTimeOpen|date("g:i A") }}{{ outlet.outlet.getTimeClose|date("g:i A") }}0.000.000{{ outlet.distance ? outlet.distance : '-' }}{{ outlet.duration ? outlet.duration : '-' }}
+ + + + + + + + + + + + - {% endfor %} - -
NameAddressNumbersOpeningClosingSales CountSales AmountService CountDistanceTravel Time
+ + + + + No items to display. + + + + {% for outlet in outlets %} + + {{ outlet.outlet.getName }} + {{ outlet.outlet.getAddress }} + {{ outlet.outlet.getContactNumbers }} + {{ outlet.outlet.getTimeOpen|date("g:i A") }} + {{ outlet.outlet.getTimeClose|date("g:i A") }} + 0.00 + 0.00 + 0 + {{ outlet.distance ? outlet.distance : '-' }} + {{ outlet.duration ? outlet.duration : '-' }} + + {% endfor %} + + +
{% endif %} + {% if mode == 'update-assigning' %} +
+
+
+

+ Hub Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+

+ Outlet Details +

+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+
+
+
+

+ Rider Assignment +

+
+
+
+ + +
+ + + + + + + + + + + + + + + + + {% for rider in obj.getOutlet.getHub.getRiders %} + + + + + + + + + {% endfor %} + +
First NameLast NameContact No.Plate NumberStatus
+ No items to display. +
+
+
{{ rider.getFirstName }}{{ rider.getLastName }}{{ rider.getContactNumber }}{{ rider.getPlateNumber }}
+
+
+
+
+ {% endif %} +
- Cancel + {% if mode != 'create' %} + Cancel + {% endif %}
@@ -454,11 +595,11 @@ $(function() { } }); - {% if mode == 'update' %} - // check if we need to set map - var latlng = new google.maps.LatLng({{ obj.getCoordinates.getLatitude }}, {{ obj.getCoordinates.getLongitude }}); - selectPoint(map, latlng); + // check if we need to set map + var latlng = new google.maps.LatLng({{ obj.getCoordinates.getLatitude }}, {{ obj.getCoordinates.getLongitude }}); + selectPoint(map, latlng); + {% if mode == 'update-processing' %} // display outlet map var omap = new GMaps({ div: '#outlet_map', @@ -499,11 +640,16 @@ $(function() { // add invoice items to data fields['invoice_items'] = invoiceItems; - {% if mode == 'update' %} + {% if mode == 'update-processing' %} // add selected outlet to data fields['outlet'] = selectedOutlet; {% endif %} + {% if mode == 'update-assigning' %} + // add selected rider to data + fields['rider'] = selectedRider; + {% endif %} + e.preventDefault(); $.ajax({ @@ -518,7 +664,7 @@ $(function() { text: 'Your changes have been saved!', type: 'success', onClose: function() { - window.location.href = "{{ mode == 'create' ? url('jo_in') : url('jo_proc') }}"; + window.location.href = "{{ return_url }}"; } }); }).fail(function(response) { @@ -530,7 +676,7 @@ $(function() { // display errors contextually $.each(errors, function(field, msg) { - var formfield = $("[name='" + field + "']"); + var formfield = $("[name='" + field + "'], [data-name='" + field + "']"); var label = $("label[data-field='" + field + "']"); var msgbox = $(".form-control-feedback[data-field='" + field + "']"); @@ -821,29 +967,52 @@ $(function() { } }); - {% if mode == 'update' %} + {% if mode == 'update-processing' %} + var selectedOutlet = '{{ obj.getOutlet ? obj.getOutlet.getID : "" }}'; - var selectedOutlet = '{{ obj.getOutlet ? obj.getOutlet.getID : "" }}'; + $("#outlets-table tbody tr").click(function() { + var id = $(this).data('id'); - $("#outlets-table tbody tr").click(function() { - var id = $(this).data('id'); + if (id != selectedOutlet) { + // highlight this row, set outlet value + $("#outlets-table").find('.m-table__row--primary').removeClass('m-table__row--primary'); - if (id != selectedOutlet) { - // highlight this row, set outlet value - $("#outlets-table").find('.m-table__row--primary').removeClass('m-table__row--primary'); + $(this).addClass('m-table__row--primary'); - $(this).addClass('m-table__row--primary'); + // set value + selectedOutlet = id; + } else { + // unhighlight this row + $(this).removeClass('m-table__row--primary'); - // set value - selectedOutlet = id; - } else { - // unhighlight this row - $(this).removeClass('m-table__row--primary'); + // remove id value + selectedOutlet = ''; + } + }); + {% endif %} - // remove id value - selectedOutlet = ''; - } - }); + {% if mode == 'update-assigning' %} + var selectedRider = '{{ obj.getRider ? obj.getRider.getID : "" }}'; + + $("#riders-table tbody tr").click(function() { + var id = $(this).data('id'); + + if (id != selectedRider) { + // highlight this row, set outlet value + $("#riders-table").find('.m-table__row--primary').removeClass('m-table__row--primary'); + + $(this).addClass('m-table__row--primary'); + + // set value + selectedRider = id; + } else { + // unhighlight this row + $(this).removeClass('m-table__row--primary'); + + // remove id value + selectedRider = ''; + } + }); {% endif %} }); diff --git a/templates/job-order/list.html.twig b/templates/job-order/list.html.twig index a705552d..bb74ea15 100644 --- a/templates/job-order/list.html.twig +++ b/templates/job-order/list.html.twig @@ -6,7 +6,7 @@

- Job Orders (Processing) + Job Orders ({{ tier_name|capitalize }})

@@ -52,7 +52,7 @@ type: 'remote', source: { read: { - url: '{{ url("jo_proc_rows") }}', + url: '{{ url(rows_route) }}', method: 'POST' } },