denyAccessUnlessGranted('jo_in.list', null, 'No access.'); // get search term $term = $req->query->get('search'); // get querybuilder $qb = $this->getDoctrine() ->getRepository(JobOrder::class) ->createQueryBuilder('q'); // build expression now since we're reusing it $jo_label = $qb->expr()->concat($qb->expr()->literal('#'), 'q.id', $qb->expr()->literal(' - '), 'c.first_name', $qb->expr()->literal(' '), 'c.last_name', $qb->expr()->literal(' (Plate No: '), 'v.plate_number', $qb->expr()->literal(')')); // count total records $tquery = $qb->select('COUNT(q)') ->join('q.customer', 'c') ->join('q.cus_vehicle', 'v'); // add filters to count query if (!empty($term)) { $tquery->where($jo_label . ' LIKE :filter') ->setParameter('filter', '%' . $term . '%'); } $total = $tquery->getQuery() ->getSingleScalarResult(); // pagination vars $page = $req->query->get('page') ?? 1; $perpage = 20; $offset = ($page - 1) * $perpage; $pages = ceil($total / $perpage); $has_more_pages = $page < $pages ? true : false; // build main query $query = $qb->select('q') ->addSelect($jo_label . ' as jo_label') ->addSelect('c.first_name as cust_first_name') ->addSelect('c.last_name as cust_last_name') ->addSelect('v.plate_number as vehicle_plate_number'); // add filters if needed if (!empty($term)) { $query->where($jo_label . ' LIKE :filter') ->setParameter('filter', '%' . $term . '%'); } // get rows $obj_rows = $query->orderBy('q.id', 'asc') ->setFirstResult($offset) ->setMaxResults($perpage) ->getQuery() ->getResult(); // build job order array $job_orders = []; foreach ($obj_rows as $jo) { $service_type = ServiceType::getName($jo[0]->getServiceType()); $job_orders[] = [ 'id' => $jo[0]->getID(), 'text' => $jo['jo_label'] . ' - ' . $service_type ]; } // response return $this->json([ 'success' => true, 'results' => $job_orders, 'pagination' => [ 'more' => $has_more_pages ] ]); } protected function fillDropdownParameters(&$params) { $em = $this->getDoctrine()->getManager(); // db loaded $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); // $params['customers'] = $em->getRepository(Customer::class)->findAll(); $params['promos'] = $em->getRepository(Promo::class)->findAll(); // name values $params['service_types'] = ServiceType::getCollection(); $params['warranty_classes'] = WarrantyClass::getCollection(); $params['modes_of_payment'] = ModeOfPayment::getCollection(); $params['statuses'] = JOStatus::getCollection(); $params['discount_apply'] = DiscountApply::getCollection(); $params['trade_in_types'] = TradeInType::getCollection(); $params['sources'] = TransactionOrigin::getCollection(); } protected function initFormTags(&$params) { // default to editing, as we have more forms editing than creating $params['ftags'] = [ 'title' => 'Job Order Form', 'vehicle_dropdown' => false, 'invoice_edit' => false, 'set_map_coordinate' => true, 'preset_vehicle' => false, 'ticket_table' => true, 'cancel_button' => true, ]; } protected function fillFormTags(&$params) { $this->initFormTags($params); switch ($params['mode']) { case 'create': $params['ftags']['vehicle_dropdown'] = true; $params['ftags']['set_map_coordinate'] = false; $params['ftags']['invoice_edit'] = true; $params['ftags']['ticket_table'] = false; $params['ftags']['cancel_button'] = false; break; case 'create_vehicle': $params['ftags']['set_map_coordinate'] = false; $params['ftags']['invoice_edit'] = true; $params['ftags']['preset_vehicle'] = true; $params['ftags']['ticket_table'] = false; $params['ftags']['cancel_button'] = false; break; case 'open_edit': $params['ftags']['invoice_edit'] = true; $params['ftags']['preset_vehicle'] = true; break; } } public function incomingForm() { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); $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(); $this->fillDropdownParameters($params); $this->fillFormTags($params); // response return $this->render('job-order/form.html.twig', $params); } public function openEditForm($id) { $this->denyAccessUnlessGranted('jo_open.edit', null, 'No access.'); $em = $this->getDoctrine()->getManager(); $jo = $em->getRepository(JobOrder::class)->find($id); $params = $this->initParameters('jo_in'); $params['obj'] = $jo; $params['mode'] = 'open_edit'; $params['submit_url'] = $this->generateUrl('jo_open_edit_submit', ['id' => $id]); $params['return_url'] = $this->generateUrl('jo_open'); $params['cvid'] = $jo->getCustomerVehicle()->getID(); $params['vid'] = $jo->getCustomerVehicle()->getVehicle()->getID(); $em = $this->getDoctrine()->getManager(); $this->fillDropdownParameters($params); $this->fillFormTags($params); // response return $this->render('job-order/form.html.twig', $params); } public function openEditSubmit(Request $req, ValidatorInterface $validator, InvoiceCreator $ic, $id) { $this->denyAccessUnlessGranted('jo_open.edit', null, 'No access.'); // get object data $em = $this->getDoctrine()->getManager(); $obj = $em->getRepository(JobOrder::class)->find($id); $user = $this->getUser(); // initialize error list $error_array = []; // 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.'; } if (empty($error_array)) { // coordinates $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); $stype = $req->request->get('service_type'); // 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($stype) ->setWarrantyClass($req->request->get('warranty_class')) ->setSource($req->request->get('source')) ->setDeliveryInstructions($req->request->get('delivery_instructions')) ->setTier1Notes($req->request->get('tier1_notes')) ->setTier2Notes($req->request->get('tier2_notes')) ->setDeliveryAddress($req->request->get('delivery_address')) ->setORName($req->request->get('or_name')) ->setPromoDetail($req->request->get('promo_detail')) ->setModeOfPayment($req->request->get('mode_of_payment')) ->setLandmark($req->request->get('landmark')); // did they change invoice? $invoice_items = $req->request->get('invoice_items'); if (!empty($invoice_items)) { // instantiate invoice criteria $criteria = new InvoiceCriteria(); $criteria->setServiceType($stype) ->setCustomerVehicle($obj->getCustomerVehicle()); $ierror = $this->invoicePromo($em, $criteria, $req->request->get('invoice_promo')); if (!$ierror) $ierror = $this->invoiceBatteries($em, $criteria, $invoice_items); if ($ierror) { $error_array['invoice'] = $ierror; } else { // generate the invoice $iobj = $ic->processCriteria($criteria); $iobj->setStatus(InvoiceStatus::DRAFT) ->setCreatedBy($this->getUser()); // validate $ierrors = $validator->validate($iobj); // add errors to list foreach ($ierrors as $error) { $error_array[$error->getPropertyPath()] = $error->getMessage(); } // remove previous invoice $old_invoice = $obj->getInvoice(); $em->remove($old_invoice); $em->flush(); // add invoice to JO $obj->setInvoice($iobj); // persist invoice $em->persist($iobj); } } // 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); } // the event $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::OPEN_EDIT) ->setUser($this->getUser()) ->setJobOrder($obj); $em->persist($event); // validated! save the entity $em->flush(); // return successful response return $this->json([ 'success' => 'Changes have been saved!' ]); } public function incomingVehicleForm($cvid) { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); $params = $this->initParameters('jo_in'); $params['mode'] = 'create_vehicle'; $params['submit_url'] = $this->generateUrl('jo_in_submit'); $params['return_url'] = $this->generateUrl('jo_in'); $params['cvid'] = $cvid; $em = $this->getDoctrine()->getManager(); // get customer vehicle $cv = $em->getRepository(CustomerVehicle::class)->find($cvid); $params['vid'] = $cv->getVehicle()->getID(); // make sure this customer vehicle exists if (empty($cv)) { $em->getConnection()->rollback(); throw $this->createNotFoundException('The job order does not exist'); } $jo = new JobOrder(); $jo->setCustomerVehicle($cv) ->setCustomer($cv->getCustomer()); $params['obj'] = $jo; $this->fillDropdownParameters($params); $this->fillFormTags($params); // response return $this->render('job-order/form.html.twig', $params); } public function incomingSubmit(Request $req, ValidatorInterface $validator, InvoiceCreator $ic) { $this->denyAccessUnlessGranted('jo_in.list', null, 'No access.'); // initialize error list $error_array = []; // create new row $em = $this->getDoctrine()->getManager(); $obj = new JobOrder(); // 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 customer vehicle is set if (empty($req->request->get('customer_vehicle'))) { $error_array['customer_vehicle'] = 'No vehicle selected.'; } else { // get customer vehicle $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($req->request->get('customer_vehicle')); if (empty($cust_vehicle)) { $error_array['customer_vehicle'] = 'Invalid vehicle specified.'; } } if (empty($error_array)) { // coordinates $point = new Point($req->request->get('coord_lng'), $req->request->get('coord_lat')); $stype = $req->request->get('service_type'); // 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) ->setCreatedBy($this->getUser()) ->setServiceType($stype) ->setWarrantyClass($req->request->get('warranty_class')) ->setCustomer($cust_vehicle->getCustomer()) ->setCustomerVehicle($cust_vehicle) ->setSource($req->request->get('source')) ->setStatus(JOStatus::PENDING) ->setDeliveryInstructions($req->request->get('delivery_instructions')) ->setTier1Notes($req->request->get('tier1_notes')) ->setTier2Notes($req->request->get('tier2_notes')) ->setDeliveryAddress($req->request->get('delivery_address')) ->setORName($req->request->get('or_name')) ->setPromoDetail($req->request->get('promo_detail')) ->setModeOfPayment($req->request->get('mode_of_payment')) ->setLandmark($req->request->get('landmark')); // check if reference JO is set and validate if (!empty($req->request->get('ref_jo'))) { // get reference JO $ref_jo = $em->getRepository(JobOrder::class)->find($req->request->get('ref_jo')); if (empty($ref_jo)) { $error_array['ref_jo'] = 'Invalid reference job order specified.'; } else { $obj->setReferenceJO($ref_jo); } } // instantiate invoice criteria $criteria = new InvoiceCriteria(); $criteria->setServiceType($stype) ->setCustomerVehicle($cust_vehicle); $ierror = $this->invoicePromo($em, $criteria, $req->request->get('invoice_promo')); $invoice_items = $req->request->get('invoice_items'); if (!$ierror && !empty($invoice_items)) $ierror = $this->invoiceBatteries($em, $criteria, $invoice_items); if ($ierror) { $error_array['invoice'] = $ierror; } else { // generate the invoice $iobj = $ic->processCriteria($criteria); $iobj->setStatus(InvoiceStatus::DRAFT) ->setCreatedBy($this->getUser()); // validate $ierrors = $validator->validate($iobj); // add errors to list foreach ($ierrors as $error) { $error_array[$error->getPropertyPath()] = $error->getMessage(); } // add invoice to JO $obj->setInvoice($iobj); // save $em->persist($iobj); } // 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); // the event $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::CREATE) ->setUser($this->getUser()) ->setJobOrder($obj); $em->persist($event); $em->flush(); // return successful response return $this->json([ 'success' => 'Changes have been saved!' ]); } protected function checkTier($tier) { // check specified tier switch ($tier) { case 'proc': $tier_key = 'jo_proc'; $tier_name = 'Dispatch'; $rows_route = 'jo_proc_rows'; $edit_route = 'jo_proc_form'; $unlock_route = 'jo_proc_unlock'; $jo_status = JOStatus::PENDING; break; case 'assign': $tier_key = 'jo_assign'; $tier_name = 'Assigning'; $rows_route = 'jo_assign_rows'; $edit_route = 'jo_assign_form'; $unlock_route = 'jo_assign_unlock'; $jo_status = JOStatus::RIDER_ASSIGN; break; case 'fulfill': $tier_key = 'jo_fulfill'; $tier_name = 'Fullfillment'; $rows_route = 'jo_fulfill_rows'; $edit_route = 'jo_fulfill_form'; $unlock_route = ''; $jo_status = [ JOStatus::ASSIGNED, JOStatus::IN_PROGRESS ]; break; case 'open': $tier_key = 'jo_open'; $tier_name = 'Open'; $rows_route = 'jo_open_rows'; $edit_route = ''; $unlock_route = ''; $jo_status = [ JOStatus::PENDING, JOStatus::RIDER_ASSIGN, JOStatus::ASSIGNED, JOStatus::IN_PROGRESS ]; break; case 'all': $tier_key = 'jo_open'; $tier_name = 'Open'; $rows_route = 'jo_open_rows'; $edit_route = 'jo_all_form'; $unlock_route = ''; $jo_status = ''; break; default: $exception = $this->createAccessDeniedException('No access.'); throw $exception; } // 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, 'unlock_route' => $unlock_route, 'jo_status' => $jo_status ]; } public function listProcessing() { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); $params = $this->initParameters('jo_proc'); $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); return $this->render('job-order/list.processing.html.twig', $params); } public function listAssigning() { $this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.'); $params = $this->initParameters('jo_assign'); $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); return $this->render('job-order/list.assigning.html.twig', $params); } public function listFulfillment() { $this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.'); $params = $this->initParameters('jo_fulfill'); $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); return $this->render('job-order/list.fulfillment.html.twig', $params); } public function listOpen() { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); $params = $this->initParameters('jo_open'); $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); $params['statuses'] = JOStatus::getCollection(); return $this->render('job-order/list.open.html.twig', $params); } public function listAll() { $this->denyAccessUnlessGranted('jo_all.list', null, 'No access.'); $params = $this->initParameters('jo_all'); $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); return $this->render('job-order/list.all.html.twig', $params); } 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']; $params['table_refresh_rate'] = $this->container->getParameter('job_order_refresh_interval'); // response return $this->render('job-order/list.html.twig', $params); } public function getRows(Request $req, $tier) { // check which job order tier is being called for and confirm access $tier_params = $this->checkTier($tier); // get current user $user = $this->getUser(); $hubs = $user->getHubs(); // get query builder $qb = $this->getDoctrine() ->getRepository(JobOrder::class) ->createQueryBuilder('q'); // get datatable params $datatable = $req->request->get('datatable'); // count total records $tquery = $qb->select('COUNT(q)'); $this->setQueryFilters($datatable, $tquery, $qb, $hubs, $tier, $tier_params['jo_status']); $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 $qb = $this->getDoctrine() ->getRepository(JobOrder::class) ->createQueryBuilder('q'); $query = $qb->select('q'); $this->setQueryFilters($datatable, $query, $qb, $hubs, $tier, $tier_params['jo_status']); // 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.date_schedule', 'asc'); } // get rows for this page $query_obj = $query->setFirstResult($offset) ->setMaxResults($perpage) ->getQuery(); error_log($query_obj->getSQL()); $obj_rows = $query_obj->getResult(); /* $obj_rows = $query->setFirstResult($offset) ->setMaxResults($perpage) ->getQuery() ->getResult(); */ $statuses = JOStatus::getCollection(); $service_types = ServiceType::getCollection(); // process rows $rows = []; foreach ($obj_rows as $orow) { // add row data $row['id'] = $orow->getID(); $row['customer_name'] = $orow->getCustomer()->getFirstName() . ' ' . $orow->getCustomer()->getLastName(); $row['delivery_address'] = $orow->getDeliveryAddress(); $row['date_schedule'] = $orow->getDateSchedule()->format("d M Y g:i A"); $row['type'] = $orow->isAdvanceOrder() ? 'Advanced Order' : 'Immediate'; $row['service_type'] = $service_types[$orow->getServiceType()]; $row['status'] = $statuses[$orow->getStatus()]; $row['flag_advance'] = $orow->isAdvanceOrder(); $row['plate_number'] = $orow->getCustomerVehicle()->getPlateNumber(); $processor = $orow->getProcessedBy(); if ($processor == null) $row['processor'] = ''; else $row['processor'] = $orow->getProcessedBy()->getFullName(); $assignor = $orow->getAssignedBy(); if ($assignor == null) $row['assignor'] = ''; else $row['assignor'] = $orow->getAssignedBy()->getFullName(); // add crud urls if ($tier == 'open') { $row['meta']['reassign_hub_url'] = $this->generateUrl('jo_open_hub_form', ['id' => $row['id']]); $row['meta']['reassign_rider_url'] = $this->generateUrl('jo_open_rider_form', ['id' => $row['id']]); $row['meta']['edit_url'] = $this->generateUrl('jo_open_edit_form', ['id' => $row['id']]); } else { $row['meta']['update_url'] = $this->generateUrl($tier_params['edit_route'], ['id' => $row['id']]); $row['meta']['pdf_url'] = $this->generateUrl('jo_pdf_form', ['id' => $row['id']]); } if ($tier_params['unlock_route'] != '') $row['meta']['unlock_url'] = $this->generateUrl($tier_params['unlock_route'], ['id' => $row['id']]); $rows[] = $row; } // response return $this->json([ 'meta' => $meta, 'data' => $rows ]); } public function processingForm(MapTools $map_tools, $id) { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); $em = $this->getDoctrine()->getManager(); // manual transaction since we're locking $em->getConnection()->beginTransaction(); try { // lock and get data $obj = $em->getRepository(JobOrder::class)->find($id, LockMode::PESSIMISTIC_READ); // make sure this job order exists if (empty($obj)) { $em->getConnection()->rollback(); throw $this->createNotFoundException('The job order does not exist'); } // check status if ($obj->getStatus() != JOStatus::PENDING) { $em->getConnection()->rollback(); throw $this->createNotFoundException('The job order does not have a pending status'); } // check if we are the processor $processor = $obj->getProcessedBy(); $user = $this->getUser(); // TODO: go back to list page and display alert / flash that says they cannot access it because they // are not the processor if ($processor != null && $processor->getID() != $user->getID()) { $em->getConnection()->rollback(); throw $this->createAccessDeniedException('Not the processor'); } // make this user be the processor $obj->setProcessedBy($user); $em->flush(); $em->getConnection()->commit(); } catch(PessimisticLockException $e) { throw $this->createAccessDeniedException('Not the processor'); } // NOTE: we are able to lock, everything should be fine now $params = $this->initParameters('jo_proc'); $params['mode'] = 'update-processing'; $params['status_cancelled'] = JOStatus::CANCELLED; $this->fillDropdownParameters($params); $this->fillFormTags($params); // get closest hubs $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 20, date("H:i:s")); $params['hubs'] = []; // format duration and distance into friendly time foreach ($hubs as $hub) { // duration $seconds = $hub['duration']; if (!empty($seconds) && $seconds > 0) { $hours = floor($seconds / 3600); $minutes = ceil(($seconds / 60) % 60); $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); } else { $hub['duration'] = false; } // distance $meters = $hub['distance']; if (!empty($meters) && $meters > 0) { $hub['distance'] = round($meters / 1000) . " km"; } else { $hub['distance'] = false; } // counters $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); $params['hubs'][] = $hub; } $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); } public function processingSubmit(Request $req, ValidatorInterface $validator, $id) { $this->denyAccessUnlessGranted('jo_proc.list', null, 'No access.'); // get object data $em = $this->getDoctrine()->getManager(); $obj = $em->getRepository(JobOrder::class)->find($id); $processor = $obj->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 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 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.'; } } 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')) ->setWarrantyClass($req->request->get('warranty_class')) ->setSource($req->request->get('source')) ->setStatus(JOStatus::RIDER_ASSIGN) ->setDeliveryInstructions($req->request->get('delivery_instructions')) ->setTier1Notes($req->request->get('tier1_notes')) ->setTier2Notes($req->request->get('tier2_notes')) ->setDeliveryAddress($req->request->get('delivery_address')) ->setHub($hub); // 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); } // the event $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::HUB_ASSIGN) ->setUser($this->getUser()) ->setJobOrder($obj); $em->persist($event); // validated! save the entity $em->flush(); // return successful response return $this->json([ 'success' => 'Changes have been saved!' ]); } public function assigningForm(MapTools $map_tools, $id) { $this->denyAccessUnlessGranted('jo_assign.list', null, 'No access.'); $em = $this->getDoctrine()->getManager(); // manual transaction since we're locking $em->getConnection()->beginTransaction(); $params = $this->initParameters('jo_assign'); $params['mode'] = 'update-assigning'; try { // get row data $obj = $em->getRepository(JobOrder::class)->find($id); // make sure this row exists if (empty($obj)) { $em->getConnection()->rollback(); throw $this->createNotFoundException('The job order does not exist'); } // check status if ($obj->getStatus() != JOStatus::RIDER_ASSIGN) { $em->getConnection()->rollback(); throw $this->createNotFoundException('The job order does not have an assigning status'); } // check if super user $user = $this->getUser(); if ($user->isSuperAdmin()) { // do nothing, just allow page to be accessed } else { // check if hub is assigned to current user $user_hubs = $this->getUser()->getHubs(); if (!in_array($obj->getHub()->getID(), $user_hubs)) { $em->getConnection()->rollback(); throw $this->createNotFoundException('The job order is not on a hub assigned to this user'); } // check if we are the assignor $assignor = $obj->getAssignedBy(); if ($assignor != null && $assignor->getID() != $user->getID()) { $em->getConnection()->rollback(); throw $this->createAccessDeniedException('Not the assignor'); } // make this user be the assignor $obj->setAssignedBy($user); $em->flush(); } $em->getConnection()->commit(); } catch (PessimisticLockException $e) { throw $this->createAccessDeniedException('Not the assignor'); } $this->fillDropdownParameters($params); $this->fillFormTags($params); $params['obj'] = $obj; $params['status_cancelled'] = JOStatus::CANCELLED; $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, MQTTCLient $mclient, $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')) ->setWarrantyClass($req->request->get('warranty_class')) ->setSource($req->request->get('source')) ->setStatus(JOStatus::ASSIGNED) ->setDeliveryInstructions($req->request->get('delivery_instructions')) ->setTier1Notes($req->request->get('tier1_notes')) ->setTier2Notes($req->request->get('tier2_notes')) ->setDeliveryAddress($req->request->get('delivery_address')) ->setAssignedBy($this->getUser()) ->setDateAssign(new DateTime()) ->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); } // set rider unavailable $rider->setAvailable(false); // the event $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::RIDER_ASSIGN) ->setUser($this->getUser()) ->setJobOrder($obj); $em->persist($event); // validated! save the entity $em->flush(); // send event to mobile app $payload = [ 'event' => 'driver_assigned' ]; $mclient->sendEvent($obj, $payload); // return successful response return $this->json([ 'success' => 'Changes have been saved!' ]); } public function fulfillmentForm(MapTools $map_tools, $id) { $this->denyAccessUnlessGranted('jo_fulfill.list', null, 'No access.'); $em = $this->getDoctrine()->getManager(); $params = $this->initParameters('jo_fulfill'); $params['mode'] = 'update-fulfillment'; // get row data $obj = $em->getRepository(JobOrder::class)->find($id); // make sure this row exists if (empty($obj)) { throw $this->createNotFoundException('The job order does not exist'); } // check status if (!in_array($obj->getStatus(), [JOStatus::ASSIGNED, JOStatus::IN_PROGRESS])) { throw $this->createNotFoundException('The job order does not have a fulfillment status'); } // check if hub is assigned to current user $user_hubs = $this->getUser()->getHubs(); if (!in_array($obj->getHub()->getID(), $user_hubs)) { throw $this->createNotFoundException('The job order is not on a hub assigned to this user'); } $this->fillDropdownParameters($params); $this->fillFormTags($params); $params['obj'] = $obj; $params['status_cancelled'] = JOStatus::CANCELLED; $params['submit_url'] = $this->generateUrl('jo_fulfill_submit', ['id' => $obj->getID()]); $params['return_url'] = $this->generateUrl('jo_fulfill'); // response return $this->render('job-order/form.html.twig', $params); } protected function updateVehicleBattery(JobOrder $jo) { // check if new battery if ($jo->getServiceType() != ServiceType::BATTERY_REPLACEMENT_NEW) return; // customer vehicle $cv = $jo->getCustomerVehicle(); if ($cv == null) return; // invoice $invoice = $jo->getInvoice(); if ($invoice == null) return; // invoice items $items = $invoice->getItems(); if (count($items) <= 0) return; // get first battery from invoice $battery = null; foreach ($items as $item) { $battery = $item->getBattery(); if ($battery != null) break; } // no battery in order if ($battery == null) return; // warranty expiration $warr = $jo->getWarrantyClass(); if ($warr == WarrantyClass::WTY_PRIVATE) $warr_months = $battery->getWarrantyPrivate(); else if ($warr == WarrantyClass::WTY_COMMERCIAL) $warr_months = $battery->getWarrantyCommercial(); else if ($warr == WarrantyClass::WTY_TNV) $warr_months = 12; $warr_date = new DateTime(); $warr_date->add(new DateInterval('P' . $warr_months . 'M')); // update customer vehicle battery $cv->setCurrentBattery($battery) ->setHasMotoliteBattery(true) ->setWarrantyExpiration($warr_date); } public function fulfillmentSubmit(Request $req, ValidatorInterface $validator, MQTTClient $mclient, $id) { $this->denyAccessUnlessGranted('jo_fulfill.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.'; } 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')) ->setWarrantyClass($req->request->get('warranty_class')) ->setSource($req->request->get('source')) ->setStatus(JOStatus::FULFILLED) ->setDeliveryInstructions($req->request->get('delivery_instructions')) ->setTier1Notes($req->request->get('tier1_notes')) ->setTier2Notes($req->request->get('tier2_notes')) ->setDeliveryAddress($req->request->get('delivery_address')) ->setDateFulfill(new DateTime()); // 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); } // set rider available $rider = $obj->getRider(); if ($rider != null) $rider->setAvailable(); // the event $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::FULFILL) ->setUser($this->getUser()) ->setJobOrder($obj); $em->persist($event); // save to customer vehicle battery record $this->updateVehicleBattery($obj); // validated! save the entity $em->flush(); // get rider $rider = $obj->getRider(); $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/assets/images/user.gif'; if ($rider->getImageFile() != null) $image_url = $req->getScheme() . '://' . $req->getHttpHost() . $req->getBasePath() . '/uploads/' . $rider->getImageFile(); // send to mqtt $payload = [ 'event' => 'fulfilled', 'jo_id' => $obj->getID(), 'driver_image' => $image_url, 'driver_name' => $rider->getFullName(), 'driver_id' => $rider->getID(), ]; $mclient->sendEvent($obj, $payload); // return successful response return $this->json([ 'success' => 'Changes have been saved!' ]); } protected function sendEvent(JobOrder $job_order, $payload) { $sessions = $job_order->getCustomer()->getMobileSessions(); if (count($sessions) == 0) return; $client = new MosquittoClient(); $client->connect('localhost', 1883); foreach ($sessions as $sess) { $phone_num = $sess->getPhoneNumber(); $channel = 'motolite.control.' . $phone_num; $client->publish($channel, json_encode($payload)); } $client->disconnect(); } public function openHubForm(MapTools $map_tools, $id) { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); $em = $this->getDoctrine()->getManager(); $params = $this->initParameters('jo_open'); $params['mode'] = 'update-reassign-hub'; // get row data $obj = $em->getRepository(JobOrder::class)->find($id); // make sure this row exists if (empty($obj)) { throw $this->createNotFoundException('The job order does not exist'); } $this->fillDropdownParameters($params); $this->fillFormTags($params); // get closest hubs $hubs = $map_tools->getClosestHubs($obj->getCoordinates(), 10, date("H:i:s")); $params['status_cancelled'] = JOStatus::CANCELLED; $params['hubs'] = []; // format duration and distance into friendly time foreach ($hubs as $hub) { // duration $seconds = $hub['duration']; if (!empty($seconds) && $seconds > 0) { $hours = floor($seconds / 3600); $minutes = ceil(($seconds / 60) % 60); $hub['duration'] = ($hours > 0 ? number_format($hours) . " hr" . ($hours > 1 ? "s" : '') . ($minutes > 0 ? ", " : '') : '') . ($minutes > 0 ? number_format($minutes) . " min" . ($minutes > 1 ? "s" : '') : ''); } else { $hub['duration'] = false; } // distance $meters = $hub['distance']; if (!empty($meters) && $meters > 0) { $hub['distance'] = round($meters / 1000) . " km"; } else { $hub['distance'] = false; } // counters $hub['rider_count'] = count($hub['hub']->getAvailableRiders()); $hub['jo_count'] = count($hub['hub']->getForAssignmentJobOrders()); $params['hubs'][] = $hub; } $params['obj'] = $obj; $params['submit_url'] = $this->generateUrl('jo_open_hub_submit', ['id' => $obj->getID()]); $params['return_url'] = $this->generateUrl('jo_open'); // response return $this->render('job-order/form.html.twig', $params); } public function openHubSubmit(Request $req, ValidatorInterface $validator, $id) { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); // get object data $em = $this->getDoctrine()->getManager(); $obj = $em->getRepository(JobOrder::class)->find($id); $user = $this->getUser(); // initialize error list $error_array = []; // 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 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.'; } } 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')) ->setWarrantyClass($req->request->get('warranty_class')) ->setSource($req->request->get('source')) ->setStatus(JOStatus::RIDER_ASSIGN) ->setDeliveryInstructions($req->request->get('delivery_instructions')) ->setTier1Notes($req->request->get('tier1_notes')) ->setTier2Notes($req->request->get('tier2_notes')) ->setDeliveryAddress($req->request->get('delivery_address')) ->setHub($hub) ->setProcessedBy($this->getUser()) ->clearRider(); // 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); } // add event $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::HUB_ASSIGN) ->setUser($this->getUser()) ->setJobOrder($obj); $em->persist($event); // validated! save the entity $em->flush(); // return successful response return $this->json([ 'success' => 'Changes have been saved!' ]); } public function openRiderForm($id) { $this->denyAccessUnlessGranted('jo_open.list', null, 'No access.'); $em = $this->getDoctrine()->getManager(); $params = $this->initParameters('jo_open'); $params['mode'] = 'update-reassign-rider'; // get row data $obj = $em->getRepository(JobOrder::class)->find($id); // make sure this row exists if (empty($obj)) { $em->getConnection()->rollback(); throw $this->createNotFoundException('The job order does not exist'); } // check status if ($obj->getStatus() == JOStatus::PENDING) { $em->getConnection()->rollback(); throw $this->createNotFoundException('The job order does not have an assigned hub'); } $this->fillDropdownParameters($params); $this->fillFormTags($params); $params['obj'] = $obj; $params['status_cancelled'] = JOStatus::CANCELLED; $params['submit_url'] = $this->generateUrl('jo_open_rider_submit', ['id' => $obj->getID()]); $params['return_url'] = $this->generateUrl('jo_open'); // response return $this->render('job-order/form.html.twig', $params); } public function openRiderSubmit(Request $req, ValidatorInterface $validator, $id) { $this->denyAccessUnlessGranted('jo_open.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')) ->setWarrantyClass($req->request->get('warranty_class')) ->setSource($req->request->get('source')) ->setStatus(JOStatus::ASSIGNED) ->setDeliveryInstructions($req->request->get('delivery_instructions')) ->setTier1Notes($req->request->get('tier1_notes')) ->setTier2Notes($req->request->get('tier2_notes')) ->setDeliveryAddress($req->request->get('delivery_address')) ->setAssignedBy($this->getUser()) ->setDateAssign(new DateTime()) ->setAssignedBy($this->getUser()) ->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); } // add event $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::RIDER_ASSIGN) ->setUser($this->getUser()) ->setJobOrder($obj); $em->persist($event); // validated! save the entity $em->flush(); // return successful response return $this->json([ 'success' => 'Changes have been saved!' ]); } public function allForm($id) { $this->denyAccessUnlessGranted('jo_all.list', null, 'No access.'); $em = $this->getDoctrine()->getManager(); $params = $this->initParameters('jo_all'); $params['mode'] = 'update-all'; // get row data $obj = $em->getRepository(JobOrder::class)->find($id); // make sure this row exists if (empty($obj)) throw $this->createNotFoundException('The job order does not exist'); $this->fillDropdownParameters($params); $this->fillFormTags($params); $params['obj'] = $obj; $params['status_cancelled'] = JOStatus::CANCELLED; $params['return_url'] = $this->generateUrl('jo_all'); $params['submit_url'] = ''; // timeline stuff (descending by time) $params['timeline'] = [ [ 'date' => date("M j"), 'time' => date("g:i A"), 'event' => "Event 4", 'color' => "#f4516c" ], [ 'date' => date("M j"), 'time' => date("g:i A"), 'event' => "Event 3", 'color' => "#34bfa3" ], [ 'date' => date("M j"), 'time' => date("g:i A"), 'event' => "Event 2", 'color' => "#716aca" ], [ 'date' => date("M j"), 'time' => date("g:i A"), 'event' => "Event 1", 'color' => "#ffb822" ], ]; // response return $this->render('job-order/form.html.twig', $params); } public function pdfForm(Request $req, $id) { $this->denyAccessUnlessGranted('jo_pdf.list', null, 'No access.'); $em = $this->getDoctrine()->getManager(); // get row data $obj = $em->getRepository(JobOrder::class)->find($id); // make sure this row exists if (empty($obj)) throw $this->createNotFoundException('The job order does not exist'); // set output filename $filename = 'job-order-' . $obj->getID() . '.pdf'; // generate the pdf $pdf = new FPDF('P', 'mm', 'letter'); $pdf->AddPage(); $pdf->SetTitle('Motolite Res-Q Job Order #' . $obj->getID()); $pdf->SetFillColor(211, 211, 211); // style defaults $margin = 10; $page_width = $pdf->GetPageWidth() - ($margin * 2); $table_col_width = $page_width / 12; $line_height = 5; $jo_line_height = 10; $table_line_height = 7; $font_face = 'Arial'; $body_font_size = 9; $header_font_size = 9; $jo_font_size = 16; $col1_x = $margin; $col2_x = 120; $label_width = 40; $val_width = 60; // insert the logo $image_path = $this->get('kernel')->getProjectDir() . '/public/assets/images/logo-resq.png'; $pdf->Image($image_path, $col1_x, 10); // insert JO number $pdf->SetFont($font_face, 'B', $jo_font_size); $pdf->SetX($col2_x); $pdf->Cell($label_width, $jo_line_height, 'JO Number:'); $pdf->SetTextColor(9, 65, 150); $pdf->Cell(0, $jo_line_height, $obj->getID()); // insert customer info $customer = $obj->getCustomer(); $pdf->SetFont($font_face, '', $body_font_size); $pdf->SetTextColor(0, 0, 0); $pdf->Ln($line_height * 7); // get current Y $y = $pdf->GetY(); $pdf->SetXY($col1_x, $y); $pdf->Cell($label_width, $line_height, 'Customer Name:'); $pdf->MultiCell($val_width, $line_height, $customer->getFirstName() . ' ' . $customer->getLastName(), 0, 'L'); // get Y after left cell $y1 = $pdf->GetY(); $pdf->SetXY($col2_x, $y); $pdf->Cell($label_width, $line_height, 'Mobile Phone:'); $pdf->MultiCell(0, $line_height, $customer->getPhoneMobile() ? '+63' . $customer->getPhoneMobile() : '', 0, 'L'); // get Y after right cell $y2 = $pdf->GetY(); // get row height $y = max($y1, $y2); $pdf->SetXY($col1_x, $y); $pdf->Cell($label_width, $line_height, 'Delivery Date:'); $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule() ? $obj->getDateSchedule()->format("m/d/Y") : '', 0, 'left'); // get Y after left cell $y1 = $pdf->GetY(); $pdf->SetXY($col2_x, $y); $pdf->Cell($label_width, $line_height, 'Landline:'); $pdf->MultiCell(0, $line_height, $customer->getPhoneLandline() ? '+63' . $customer->getPhoneLandline() : '', 0, 'L'); // get Y after right cell $y2 = $pdf->GetY(); // get row height $y = max($y1, $y2); $pdf->SetXY($col2_x, $y); $pdf->Cell($label_width, $line_height, 'Office Phone:'); $pdf->MultiCell(0, $line_height, $customer->getPhoneOffice() ? '+63' . $customer->getPhoneOffice() : '', 0, 'L'); $pdf->SetX($col2_x); $pdf->Cell($label_width, $line_height, 'Fax:'); $pdf->MultiCell($val_width, $line_height, $customer->getPhoneFax() ? '+63' . $customer->getPhoneFax() : '', 0, 'L'); // insert vehicle info $cv = $obj->getCustomerVehicle(); $vehicle = $cv->getVehicle(); $pdf->Ln(); $pdf->SetFont($font_face, 'B', $header_font_size); $pdf->Cell($label_width, $line_height, 'Vehicle Details'); $pdf->Ln($line_height * 2); // get current Y $y = $pdf->GetY(); $pdf->SetFont($font_face, '', $body_font_size); $pdf->Cell($label_width, $line_height, 'Plate Number:'); $pdf->MultiCell($val_width, $line_height, $cv ? $cv->getPlateNumber() : '', 0, 'L'); // get Y after left cell $y1 = $pdf->GetY(); $pdf->SetXY($col2_x, $y); $pdf->Cell($label_width, $line_height, 'Vehicle Color:'); $pdf->MultiCell(0, $line_height, $cv ? $cv->getColor() : '', 0, 'L'); // get Y after right cell $y2 = $pdf->GetY(); // get row height $y = max($y1, $y2); $pdf->SetXY($col1_x, $y); $pdf->Cell($label_width, $line_height, 'Brand:'); $pdf->MultiCell($val_width, $line_height, $vehicle ? $vehicle->getManufacturer()->getName() : '', 0, 'L'); // get Y after left cell $y1 = $pdf->GetY(); $pdf->SetXY($col2_x, $y); $pdf->Cell($label_width, $line_height, 'Model / Year:'); $pdf->MultiCell(0, $line_height, $cv ? $cv->getModelYear() : '', 0, 'L'); // get Y after right cell $y2 = $pdf->GetY(); // get row height $y = max($y1, $y2); $pdf->SetXY($col1_x, $y); $pdf->Cell($label_width, $line_height, 'Make:'); $pdf->MultiCell($val_width, $line_height, $vehicle ? $vehicle->getMake() : '', 0, 'L'); // insert battery info $battery = $cv->getCurrentBattery(); $pdf->Ln(); $pdf->SetFont($font_face, 'B', $header_font_size); $pdf->Cell($label_width, $line_height, 'Battery Details'); $pdf->Ln($line_height * 2); $pdf->SetFont($font_face, '', $body_font_size); // get current Y $y = $pdf->GetY(); $pdf->Cell($label_width, $line_height, 'Current Battery:'); $pdf->MultiCell($val_width, $line_height, $battery ? $battery->getManufacturer()->getName() . ' ' . $battery->getModel()->getName() . ' ' . $battery->getSize()->getName() . ' (' . $battery->getProductCode() . ')' : '', 0, 'L'); // get Y after left cell $y1 = $pdf->GetY(); $pdf->SetXY($col2_x, $y); $pdf->Cell($label_width, $line_height, 'Serial Number:'); $pdf->MultiCell(0, $line_height, $cv ? $cv->getWarrantyCode() : '', 0, 'L'); // get Y after right cell $y2 = $pdf->GetY(); // get row height $y = max($y1, $y2); $pdf->SetXY($col1_x, $y); $pdf->Cell($label_width, $line_height, 'Wty. Exp. Date:'); $pdf->MultiCell($val_width, $line_height, $cv && $cv->getWarrantyExpiration() ? $cv->getWarrantyExpiration()->format("d/m/Y") : '', 0, 'L'); // insert transaction details $pdf->Ln(); $pdf->SetFont($font_face, 'B', $header_font_size); $pdf->Cell($label_width, $line_height, 'Transaction Details'); $pdf->Ln($line_height * 2); $pdf->SetFont($font_face, '', $body_font_size); // get current Y $y = $pdf->GetY(); $pdf->Cell($label_width, $line_height, 'Warranty Class:'); $pdf->MultiCell($val_width, $line_height, WarrantyClass::getName($obj->getWarrantyClass()), 0, 'L'); // get Y after left cell $y1 = $pdf->GetY(); $pdf->SetXY($col2_x, $y); $pdf->Cell($label_width, $line_height, 'Mode of Payment:'); $pdf->MultiCell(0, $line_height, ModeOfPayment::getName($obj->getModeOfPayment()), 0, 'L'); // get Y after right cell $y2 = $pdf->GetY(); // get row height $y = max($y1, $y2); $pdf->Cell($label_width, $line_height, 'Delivery Address:'); $pdf->MultiCell($val_width, $line_height, $obj->getDeliveryAddress(), 0, 'L'); // get Y after left cell $y1 = $pdf->GetY(); $pdf->SetXY($col2_x, $y); $pdf->Cell($label_width, $line_height, 'Landmark:'); $pdf->MultiCell(0, $line_height, $obj->getLandMark(), 0, 'L'); // get Y after right cell $y2 = $pdf->GetY(); // get row height $y = max($y1, $y2); $pdf->SetXY($col1_x, $y); $pdf->Cell($label_width, $line_height, 'Dispatch Time:'); $pdf->MultiCell($val_width, $line_height, $obj->getDateSchedule()->format("g:i A"), 0, 'L'); // get Y after left cell $y1 = $pdf->GetY(); $pdf->SetXY($col2_x, $y); $pdf->Cell($label_width, $line_height, 'Dispatched By:'); $pdf->MultiCell(0, $line_height, $obj->getAssignedBy()->getFullName(), 0, 'L'); // get Y after right cell $y2 = $pdf->GetY(); // get row height $y = max($y1, $y2); // insert delivery instructions $pdf->SetY($y); $pdf->Ln(); $pdf->SetFont($font_face, 'B', $header_font_size); $pdf->Cell(0, $line_height, 'Delivery Instructions'); $pdf->Ln(); $pdf->SetFont($font_face, '', $body_font_size); $pdf->MultiCell(0, $line_height, $obj->getDeliveryInstructions(), 1, 'L'); // insert invoice details $pdf->Ln(); $pdf->SetFont($font_face, 'B', $header_font_size); $pdf->Cell($label_width, $line_height, 'Invoice Details'); $pdf->Ln(); // invoice table headers $invoice = $obj->getInvoice(); $pdf->SetFont($font_face, 'B', $header_font_size); $pdf->Cell($table_col_width * 6, $table_line_height, 'Item', 1, 0, 'L', 1); $pdf->Cell($table_col_width * 2, $table_line_height, 'Quantity', 1, 0, 'R', 1); $pdf->Cell($table_col_width * 2, $table_line_height, 'Unit Price', 1, 0, 'R', 1); $pdf->Cell($table_col_width * 2, $table_line_height, 'Amount', 1, 1, 'R', 1); $pdf->SetFont($font_face, '', $body_font_size); // build invoice items table if ($invoice && !empty($invoice->getItems())) { foreach ($invoice->getItems() as $item) { $pdf->Cell($table_col_width * 6, $table_line_height, $item->getTitle(), 1); $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getQuantity()), 1, 0, 'R'); $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice(), 2), 1, 0, 'R'); $pdf->Cell($table_col_width * 2, $table_line_height, number_format($item->getPrice() * $item->getQuantity(), 2), 1, 1, 'R'); } } else { $pdf->Cell($table_col_width * 12, 7, 'No items', 1, 1); } $pdf->Ln($line_height * 2); // get current Y $y = $pdf->GetY(); // insert invoice footer details $pdf->Cell($label_width, $line_height, 'Transaction Type:'); $pdf->MultiCell($val_width, $line_height, ServiceType::getName($obj->getServiceType()), 0, 'L'); // get Y after left cell $y1 = $pdf->GetY(); $pdf->SetXY($col2_x, $y); $pdf->SetFont($font_face, 'B'); $pdf->Cell($label_width, $line_height, 'SUBTOTAL:'); $pdf->SetFont($font_face, ''); $pdf->MultiCell(0, $line_height, number_format($invoice->getVATExclusivePrice(), 2), 0, 'R'); // get Y after right cell $y2 = $pdf->GetY(); // get row height $y = max($y1, $y2); $pdf->SetXY($col1_x, $y); $pdf->Cell($label_width, $line_height, 'OR Name:'); $pdf->MultiCell($val_width, $line_height, $obj->getORName(), 0, 'L'); // get Y after left cell $y1 = $pdf->GetY(); $pdf->SetXY($col2_x, $y); $pdf->SetFont($font_face, 'B'); $pdf->Cell($label_width, $line_height, 'TAX:'); $pdf->SetFont($font_face, ''); $pdf->MultiCell(0, $line_height, number_format($invoice->getVAT(), 2), 0, 'R'); // get Y after right cell $y2 = $pdf->GetY(); // get row height $y = max($y1, $y2); $pdf->SetXY($col1_x, $y); $pdf->Cell($label_width, $line_height, 'Emp. ID/Card No./Ref. By:'); $pdf->MultiCell($val_width, $line_height, $obj->getPromoDetail(), 0, 'L'); // get Y after left cell $y1 = $pdf->GetY(); $pdf->SetXY($col2_x, $y); $pdf->SetFont($font_face, 'B'); $pdf->Cell($label_width, $line_height, 'DISCOUNT:'); $pdf->SetFont($font_face, ''); $pdf->MultiCell(0, $line_height, number_format($invoice->getDiscount(), 2), 0, 'R'); // get Y after right cell $y2 = $pdf->GetY(); // get row height $y = max($y1, $y2); $pdf->SetXY($col1_x, $y); $pdf->Cell($label_width, $line_height, 'Discount Type:'); $pdf->MultiCell($val_width, $line_height, $invoice->getPromo() ? $invoice->getPromo()->getName() : '', 0, 'L'); $pdf->SetXY($col2_x, $y); $pdf->SetFont($font_face, 'B'); $pdf->Cell($label_width, $line_height, 'FINAL AMOUNT:'); $pdf->MultiCell(0, $line_height, number_format($invoice->getTotalPrice(), 2), 0, 'R'); $pdf->SetFont($font_face, ''); // return response return new Response($pdf->Output('I', $filename), 200, [ 'Content-Type' => 'application/pdf' ]); } public function cancelJobOrder(Request $req, MQTTClient $mclient, $id) { $this->denyAccessUnlessGranted('joborder.cancel', null, 'No access.'); $cancel_reason = $req->request->get('cancel_reason'); if (empty($cancel_reason)) { // something happened return $this->json([ 'success' => false, 'error' => 'Reason for cancellation is required.' ], 422); } // 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'); // cancel job order $obj->setStatus(JOStatus::CANCELLED) ->setDateCancel(new DateTime()) ->setCancelReason($cancel_reason); // set rider available $rider = $obj->getRider(); if ($rider != null) $rider->setAvailable(); // the event $event = new JOEvent(); $event->setDateHappen(new DateTime()) ->setTypeID(JOEventType::CANCEL) ->setUser($this->getUser()) ->setJobOrder($obj); $em->persist($event); // save $em->flush(); // send mobile app event $payload = [ 'event' => 'cancelled', 'reason' => $cancel_reason, 'jo_id' => $obj->getID(), ]; $mclient->sendEvent($obj, $payload); // return successful response return $this->json([ 'success' => 'Job order has been cancelled!' ]); } // 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, $hubs, $tier, $status) { switch ($tier) { case 'fulfill': $query->where('q.status IN (:statuses)') ->andWhere('q.hub IN (:hubs)') ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); break; case 'assign': $query->where('q.status = :status') ->andWhere('q.hub IN (:hubs)') ->setParameter('status', $status) ->setParameter('hubs', $hubs, Connection::PARAM_STR_ARRAY); break; case 'open': if (isset($datatable['query']['data-rows-search'])) { $query->innerJoin('q.cus_vehicle', 'cv') ->innerJoin('q.customer', 'c') ->where('q.status IN (:statuses)') ->andWhere('cv.plate_number like :filter or c.first_name like :filter or c.last_name like :filter or c.phone_mobile like :filter') ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY) ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); } else { $query->where('q.status IN (:statuses)') ->setParameter('statuses', $status, Connection::PARAM_STR_ARRAY); } break; case 'all': if (isset($datatable['query']['data-rows-search'])) { $query->innerJoin('q.cus_vehicle', 'cv') ->innerJoin('q.customer', 'c') ->where('cv.plate_number like :filter') ->orWhere('c.phone_mobile like :filter') ->orWhere('c.first_name like :filter or c.last_name like :filter') ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); } break; default: $query->where('q.status = :status') ->setParameter('status', $status); } // get only pending rows /* $query->where($qb->expr()->orX( $qb->expr()where('q.status', 'pending'); )); // apply filters if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { $query->where('q.delivery_address LIKE :filter') ->orWhere($qb->expr()->concat('c.first_name', $qb->expr()->literal(' '), 'c.last_name') . ' LIKE :filter') ->orWhere('cv.plate_number LIKE :filter') ->setParameter('filter', '%' . $datatable['query']['data-rows-search'] . '%'); } */ } protected function invoicePromo($em, InvoiceCriteria $criteria, $promo_id) { // return error if there's a problem, false otherwise // check service type $stype = $criteria->getServiceType(); if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW) return null; if (empty($promo_id)) return false; // check if this is a valid promo $promo = $em->getRepository(Promo::class)->find($promo_id); if (empty($promo)) return 'Invalid promo specified.'; $criteria->addPromo($promo); return false; } protected function invoiceBatteries($em, InvoiceCriteria $criteria, $items) { // check service type $stype = $criteria->getServiceType(); if ($stype != ServiceType::BATTERY_REPLACEMENT_NEW && $stype != ServiceType::BATTERY_REPLACEMENT_WARRANTY) return null; // return error if there's a problem, false otherwise if (!empty($items)) { foreach ($items as $item) { // error_log('ITEMS'); // check if this is a valid battery $battery = $em->getRepository(Battery::class)->find($item['battery']); if (empty($battery)) { $error = 'Invalid battery specified.'; return $error; } // quantity $qty = $item['quantity']; if ($qty < 1) continue; /* // add to criteria $criteria->addBattery($battery, $qty); */ // if this is a trade in, add trade in if (!empty($item['trade_in']) && TradeInType::validate($item['trade_in'])) $trade_in = $item['trade_in']; else $trade_in = null; $criteria->addEntry($battery, $trade_in, $qty); } } return null; } public function generateInvoice(Request $req, InvoiceCreator $ic) { // error_log('generating invoice...'); $error = false; $stype = $req->request->get('stype'); $items = $req->request->get('items'); $promo_id = $req->request->get('promo'); $cvid = $req->request->get('cvid'); $em = $this->getDoctrine()->getManager(); // get customer vehicle $cv = $em->getRepository(CustomerVehicle::class)->find($cvid); if ($cv == null) throw new \Exception('Could not get customer vehicle'); // instantiate invoice criteria $criteria = new InvoiceCriteria(); $criteria->setServiceType($stype) ->setCustomerVehicle($cv); /* // if it's a jumpstart or troubleshoot only, we know what to charge already if ($stype == ServiceType::JUMPSTART_TROUBLESHOOT) { $invoice = [ 'price' => 150.00, 'discount' => 0, 'trade_in' => 0, 'vat' => 0, 'total_price' => 150.00, 'items' => [] ]; $invoice['items'][] = [ 'title' => 'Troubleshooting fee', 'quantity' => 1, 'unit_price' => 150.00, 'amount' => 150.00 ]; return $this->json([ 'success' => true, 'invoice' => $invoice ]); } */ $error = $this->invoicePromo($em, $criteria, $promo_id); if (!$error) $error = $this->invoiceBatteries($em, $criteria, $items); if ($error) { // something happened return $this->json([ 'success' => false, 'error' => $error ], 422); } // generate the invoice $iobj = $ic->processCriteria($criteria); // use invoice object values in a json friendly array $invoice = [ 'discount' => number_format($iobj->getDiscount(), 2), 'trade_in' => number_format($iobj->getTradeIn(), 2), // TODO: computations not done yet for this on invoice creator 'price' => number_format($iobj->getVATExclusivePrice(), 2), 'vat' => number_format($iobj->getVAT(), 2), 'total_price' => number_format($iobj->getTotalPrice(), 2), 'items' => [] ]; foreach ($iobj->getItems() as $item) { $invoice['items'][] = [ 'title' => $item->getTitle(), 'quantity' => number_format($item->getQuantity()), // TODO: quantities are always 1, hardcoded into InvoiceCreator. no way of accepting quantities on InvoiceCriteria 'unit_price' => number_format($item->getPrice(), 2), 'amount' => number_format($item->getPrice() * $item->getQuantity(), 2) // TODO: should this calculation should be a saved value on InvoiceItem instead? ]; } // return return $this->json([ 'success' => true, 'invoice' => $invoice ]); } public function unlockProcessor($id) { $this->denyAccessUnlessGranted('jo_proc.unlock', null, 'No access.'); // clear lock $em = $this->getDoctrine()->getManager(); $jo = $em->getRepository(JobOrder::class)->find($id); if ($jo == null) return $this->redirectToRoute('jo_proc'); $jo->setProcessedBy(null); $em->flush(); // redirect to list return $this->redirectToRoute('jo_proc'); } public function unlockAssignor($id) { $this->denyAccessUnlessGranted('jo_assign.unlock', null, 'No access.'); // clear lock $em = $this->getDoctrine()->getManager(); $jo = $em->getRepository(JobOrder::class)->find($id); if ($jo == null) return $this->redirectToRoute('jo_assign'); $jo->setAssignedBy(null); $em->flush(); // redirect to list return $this->redirectToRoute('jo_assign'); } }