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