em = $em; $this->validator = $validator; $this->country_code = $country_code; $this->security = $security; $this->loadTemplates(); } // initialize form to display customer list public function initializeCustomerIndexForm() { $params['template'] = $this->getTwigTemplate('cust_list'); return $params; } // get customers public function getCustomers(Request $req) { // build query $tqb = $this->em->getRepository(Customer::class) ->createQueryBuilder('q'); $qb = $this->em->getRepository(Customer::class) ->createQueryBuilder('q'); // get datatable params $datatable = $req->request->get('datatable'); // count total records $tquery = $tqb->select('COUNT(q)'); // add filters to count query $this->setQueryFilters($datatable, $tquery); $total = $tquery->getQuery() ->getSingleScalarResult(); // get current page number $page = $datatable['pagination']['page'] ?? 1; $perpage = $datatable['pagination']['perpage']; $offset = ($page - 1) * $perpage; // add metadata $meta = [ 'page' => $page, 'perpage' => $perpage, 'pages' => ceil($total / $perpage), 'total' => $total, 'sort' => 'asc', 'field' => 'id' ]; // build query $query = $qb->select('q'); // add filters to query $this->setQueryFilters($datatable, $query); // check if sorting is present, otherwise use default if (isset($datatable['sort']['field']) && !empty($datatable['sort']['field'])) { $order = $datatable['sort']['sort'] ?? 'asc'; $query->orderBy('q.' . $datatable['sort']['field'], $order); } else { $query->orderBy('q.first_name', 'asc'); } // get rows for this page $obj_rows = $query->setFirstResult($offset) ->setMaxResults($perpage) ->getQuery() ->getResult(); // process rows $rows = []; foreach ($obj_rows as $orow) { // add row data $row['id'] = $orow->getID(); $row['title'] = $orow->getTitle(); $row['first_name'] = $orow->getFirstName(); $row['last_name'] = $orow->getLastName(); $row['customer_classification'] = CustomerClassification::getName($orow->getCustomerClassification()); $row['flag_mobile_app'] = $orow->hasMobileApp(); $row['app_mobile_number'] = $orow->hasMobileApp() && !empty($orow->getMobileSessions()) ? $orow->getMobileSessions()[0]->getPhoneNumber() : ''; $row['flag_active'] = $orow->isActive(); $row['flag_csat'] = $orow->isCSAT(); // TODO: properly add mobile numbers and plate numbers as searchable/sortable fields, use doctrine events // prepend the country code before each mobile number $mobile_number_list = []; $mobile_numbers = $orow->getMobileNumberList(); foreach ($mobile_numbers as $mobile_number) { $mobile_number_list[] = $this->country_code . $mobile_number; } $row['mobile_numbers'] = implode("
", $mobile_number_list); $row['plate_numbers'] = implode("
", $orow->getPlateNumberList()); $rows[] = $row; } $params['meta'] = $meta; $params['rows'] = $rows; return $params; } // initialize add customer form public function initializeAddCustomerForm() { $params['obj'] = new Customer(); $params['mode'] = 'create'; // get customer tags $params['customer_tags'] = $this->em->getRepository(CustomerTag::class)->findAll(); // get dropdown parameters $this->fillDropdownParameters($params); // get template to display $params['template'] = $this->getTwigTemplate('cust_add_form'); // return params return $params; } // add new customer and customer vehicle, if any public function addCustomer(Request $req) { // initialize error lists $error_array = []; $nerror_array = []; $verror_array = []; // create new row $em = $this->em; $row = new Customer(); // check if email marketing promo is checked $is_email_promo_checked = $req->request->get('flag_promo_email'); if ($is_email_promo_checked) { // check email field if (empty($req->request->get('email'))) $error_array['email'] = 'Email address required.'; } $this->setObject($row, $req); // set customer source only when new customer $row->setCreateSource(CustomerSource::ADMIN_PANEL); // custom validation for vehicles $vehicles = json_decode($req->request->get('vehicles')); if (!empty($vehicles)) { foreach ($vehicles as $vehicle) { // check if vehicle exists $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); if (empty($vobj)) { $verror_array[$vehicle->index]['vehicle'] = 'Invalid vehicle specified.'; } else { $cust_vehicle = new CustomerVehicle(); $cust_vehicle->setName($vehicle->name) ->setVehicle($vobj) ->setPlateNumber($vehicle->plate_number) ->setModelYear($vehicle->model_year) ->setColor($vehicle->color) ->setStatusCondition($vehicle->status_condition) ->setFuelType($vehicle->fuel_type) ->setActive($vehicle->flag_active) ->setCustomer($row); // if specified, check if battery exists if ($vehicle->battery) { // check if battery exists $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); if (empty($bobj)) { $verror_array[$vehicle->index]['battery'] = 'Invalid battery specified.'; } else { // check if warranty expiration was specified $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); if (!$warr_ex) $warr_ex = null; $cust_vehicle->setHasMotoliteBattery(true) ->setCurrentBattery($bobj) ->setWarrantyCode($vehicle->warranty_code) ->setWarrantyExpiration($warr_ex); } } else { $cust_vehicle->setHasMotoliteBattery(false); } $verrors = $this->validator->validate($cust_vehicle); // add errors to list foreach ($verrors as $error) { if (!isset($verror_array[$vehicle->index])) $verror_array[$vehicle->index] = []; $verror_array[$vehicle->index][$error->getPropertyPath()] = $error->getMessage(); } // add to entity if (!isset($verror_array[$vehicle->index])) { $row->addVehicle($cust_vehicle); } } } } // validate $errors = $this->validator->validate($row); // add errors to list foreach ($errors as $error) { $error_array[$error->getPropertyPath()] = $error->getMessage(); } // check if any errors were found $result = []; if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) { // return all error_arrays $result = [ 'error_array' => $error_array, 'nerror_array' => $nerror_array, 'verror_array' => $verror_array, ]; } else { // validated! save the entity $em->persist($row); $em->flush(); $result = [ 'id' => $row->getID(), ]; } return $result; } // initialize update customer form public function initializeUpdateCustomerForm($id) { $params['mode'] = 'update'; // get row data $em = $this->em; $row = $em->getRepository(Customer::class)->find($id); // make sure this row exists if (empty($row)) throw new NotFoundHttpException('The item does not exist'); // get customer tags $params['customer_tags'] = $this->em->getRepository(CustomerTag::class)->findAll(); // get dropdown parameters $this->fillDropdownParameters($params); // get template to display $params['template'] = $this->getTwigTemplate('cust_update_form'); $params['obj'] = $row; return $params; } // update customer and customer vehicle public function updateCustomer(Request $req, $id) { // get row data $em = $this->em; $cust = $em->getRepository(Customer::class)->find($id); // make sure this row exists if (empty($cust)) throw $this->createNotFoundException('The item does not exist'); $this->setObject($cust, $req); // initialize error lists $error_array = []; $nerror_array = []; $verror_array = []; // check if email marketing promo is checked $is_email_promo_checked = $req->request->get('flag_promo_email'); if ($is_email_promo_checked) { // check email field if (empty($req->request->get('email'))) $error_array['email'] = 'Email address required.'; } // TODO: validate mobile numbers // TODO: validate vehicles // custom validation for vehicles $vehicles = json_decode($req->request->get('vehicles')); $this->updateVehicles($em, $cust, $vehicles); // validate $errors = $this->validator->validate($cust); // add errors to list foreach ($errors as $error) { $error_array[$error->getPropertyPath()] = $error->getMessage(); } // check if any errors were found $result = []; if (!empty($error_array) || !empty($nerror_array) || !empty($verror_array)) { // return all error_arrays $result = [ 'error_array' => $error_array, 'nerror_array' => $nerror_array, 'verror_array' => $verror_array, ]; } else { // validated! save the entity. do a persist anyway to save child entities $em->persist($cust); $em->flush(); $result = [ 'id' => $cust->getID(), ]; } return $result; } // delete customer public function deleteCustomer(int $id) { // get row data $em = $this->em; $row = $em->getRepository(Customer::class)->find($id); if (empty($row)) throw new NotFoundHttpException('The item does not exist'); // delete this row $em->remove($row); $em->flush(); } // get customer vehicles public function getCustomerVehicles(Request $req) { // get search term $term = $req->query->get('search'); // get querybuilder $qb = $this->em ->getRepository(CustomerVehicle::class) ->createQueryBuilder('q'); /* // build expression now since we're reusing it $vehicle_label = $qb->expr()->concat( 'q.plate_number', $qb->expr()->literal(' - '), 'c.first_name', $qb->expr()->literal(' '), 'c.last_name', $qb->expr()->literal(' (+63'), 'c.phone_mobile', $qb->expr()->literal(')') ); */ // count total records $tquery = $qb->select('COUNT(q)'); // add filters to count query if (!empty($term)) { $tquery->where('q.plate_number like :search') ->setParameter('search', $term . '%'); /* $tquery->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1') ->setParameter('search', $term . '*'); */ /* $tquery->where('q.plate_number 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($vehicle_label . ' as vehicle_label') ->addSelect('c.first_name as cust_first_name') ->addSelect('c.last_name as cust_last_name'); */ // add filters if needed if (!empty($term)) { $query->where('q.plate_number like :search') ->setParameter('search', $term . '%'); /* $query->where('match_against (q.plate_number, :search \'in boolean mode\') > 0.1') ->setParameter('search', $term . '*'); */ /* $query->where('q.plate_number LIKE :filter') ->setParameter('filter', '%' . $term . '%'); */ } // get rows $query_obj = $query->orderBy('q.plate_number', 'asc') ->setFirstResult($offset) ->setMaxResults($perpage) ->getQuery(); // error_log($query_obj->getSql()); $obj_rows = $query_obj->getResult(); // build vehicles array $vehicles = []; foreach ($obj_rows as $cv) { $cust = $cv->getCustomer(); $vehicles[] = [ 'id' => $cv->getID(), 'text' => $cv->getPlateNumber() . ' ' . $cust->getFirstName() . ' ' . $cust->getLastName() . ' ('. $this->country_code . $cust->getPhoneMobile() . ')', ]; } $results = [ 'vehicles' => $vehicles, 'has_more_pages' => $has_more_pages, ]; return $results; } // get customer vehicle info public function getCustomerVehicleInfo(Request $req) { // get id $id = $req->query->get('id'); // get row data $em = $this->em; $obj = $em->getRepository(CustomerVehicle::class)->find($id); // make sure this row exists if (empty($obj)) { return null; } $customer = $obj->getCustomer(); $vehicle = $obj->getVehicle(); $battery = $obj->getCurrentBattery(); // build response $row = [ 'customer' => [ 'id' => $customer->getID(), 'first_name' => $customer->getFirstName(), 'last_name' => $customer->getLastName(), 'customer_notes' => $customer->getCustomerNotes(), 'phone_mobile' => $customer->getPhoneMobile(), 'phone_landline' => $customer->getPhoneLandline(), 'phone_office' => $customer->getPhoneOffice(), 'phone_fax' => $customer->getPhoneFax(), 'email' => $customer->getEmail(), 'flag_dpa_consent' => $customer->isDpaConsent(), 'flag_promo_sms' => $customer->isPromoSms(), 'flag_promo_email' => $customer->isPromoEmail(), 'flag_research_sms' => $customer->isResearchSms(), 'flag_research_email' => $customer->isResearchEmail(), 'customer_tags' => $customer->getCustomerTags(), 'flag_mobile_app' => $customer->hasMobileApp(), ], 'vehicle' => [ 'id' => $vehicle->getID(), 'mfg_name' => $vehicle->getManufacturer()->getName(), 'make' => $vehicle->getMake(), 'model_year_from' => $vehicle->getModelYearFrom(), 'model_year_to' => $vehicle->getModelYearTo(), 'model_year' => $obj->getModelYear(), 'color' => $obj->getColor(), 'plate_number' => $obj->getPlateNumber(), 'fuel_type' => $obj->getFuelType(), 'status_condition' => $obj->getStatusCondition(), ] ]; if (!empty($battery)) { $row['battery'] = [ 'id' => $battery->getID(), 'mfg_name' => $battery->getManufacturer()->getName(), 'model_name' => $battery->getModel()->getName(), 'size_name' => $battery->getSize()->getName(), 'prod_code' => $battery->getProductCode(), 'warranty_code' => $obj->getWarrantyCode(), 'warranty_expiration' => $obj->getWarrantyExpiration() ? $obj->getWarrantyExpiration()->format("d M Y") : "", 'has_motolite_battery' => $obj->hasMotoliteBattery(), 'is_active' => $obj->isActive() ]; } return $row; } protected function getTwigTemplate($id) { if (isset($this->template_hash[$id])) { return $this->template_hash[$id]; } return null; } protected function setObject($obj, $req) { // check for dpa access $is_dpa_checked = true; if ($this->security->isGranted('customer.dpa')) $is_dpa_checked = $req->request->get('flag_dpa_consent', false); // set and save values $obj->setTitle($req->request->get('title')) ->setFirstName($req->request->get('first_name')) ->setLastName($req->request->get('last_name')) ->setCustomerClassification($req->request->get('customer_classification')) ->setCustomerNotes($req->request->get('customer_notes')) ->setEmail($req->request->get('email')) ->setIsCSAT($req->request->get('flag_csat') ? true : false) ->setActive($req->request->get('flag_active') ? true : false) ->setPromoSms($req->request->get('flag_promo_sms', false)) ->setPromoEmail($req->request->get('flag_promo_email', false)) ->setDpaConsent($is_dpa_checked) ->setResearchSms($req->request->get('flag_research_sms', false)) ->setResearchEmail($req->request->get('flag_research_email', false)) ->clearCustomerTags();; // phone numbers $obj->setPhoneMobile($req->request->get('phone_mobile')) ->setPhoneLandline($req->request->get('phone_landline')) ->setPhoneOffice($req->request->get('phone_office')) ->setPhoneFax($req->request->get('phone_fax')); // set car club flags $customer_tags = $req->request->get('customer_tags'); if (!empty($customer_tags)) { foreach($customer_tags as $customer_tag_id) { $customer_tag = $this->em->getRepository(CustomerTag::class)->find($customer_tag_id); if (!empty($customer_tag)) $obj->addCustomerTag($customer_tag); } } } protected function fillDropdownParameters(&$params) { $em = $this->em; $params['bmfgs'] = $em->getRepository(BatteryManufacturer::class)->findAll(); $params['vmfgs'] = $em->getRepository(VehicleManufacturer::class)->findAll(); $params['classifications'] = CustomerClassification::getCollection(); $params['fuel_types'] = FuelType::getCollection(); $params['status_conditions'] = VehicleStatusCondition::getCollection(); $params['years'] = $this->generateYearOptions(); $params['batteries'] = $em->getRepository(Battery::class)->findAll(); } protected function generateYearOptions() { $start_year = 1950; return range($start_year, date("Y") + 1); } protected function loadTemplates() { $this->template_hash = []; // add all twig templates for customer to hash $this->template_hash['cust_add_form'] = 'customer/form.html.twig'; $this->template_hash['cust_update_form'] = 'customer/form.html.twig'; $this->template_hash['cust_list'] = 'customer/list.html.twig'; } protected function updateVehicles($em, Customer $cust, $vehicles) { $vehicle_ids = []; foreach ($vehicles as $vehicle) { // check if customer vehicle exists if (!empty($vehicle->id)) { $cust_vehicle = $em->getRepository(CustomerVehicle::class)->find($vehicle->id); if ($cust_vehicle == null) throw new CrudException("Could not find customer vehicle."); } // this is a new vehicle else { $cust_vehicle = new CustomerVehicle(); $cust_vehicle->setCustomer($cust); $cust->addVehicle($cust_vehicle); $em->persist($cust_vehicle); } // vehicle, because they could have changed vehicle type $vobj = $em->getRepository(Vehicle::class)->find($vehicle->vehicle); if ($vobj == null) throw new CrudException("Could not find vehicle."); // TODO: validate details $cust_vehicle->setName($vehicle->name) ->setVehicle($vobj) ->setPlateNumber($vehicle->plate_number) ->setModelYear($vehicle->model_year) ->setColor($vehicle->color) ->setStatusCondition($vehicle->status_condition) ->setFuelType($vehicle->fuel_type) ->setActive($vehicle->flag_active); // if specified, check if battery exists if ($vehicle->battery) { // check if battery exists $bobj = $em->getRepository(Battery::class)->find($vehicle->battery); if ($bobj == null) throw new CrudException("Could not find battery."); // check if warranty expiration was specified $warr_ex = DateTime::createFromFormat("d M Y", $vehicle->warranty_expiration); if (!$warr_ex) $warr_ex = null; $cust_vehicle->setHasMotoliteBattery(true) ->setCurrentBattery($bobj) ->setWarrantyCode($vehicle->warranty_code) ->setWarrantyExpiration($warr_ex); } else { $cust_vehicle->setHasMotoliteBattery(false); } // add to list of vehicles to keep $vehicle_ids[$cust_vehicle->getID()] = true; } // cleanup // delete all vehicles not in list $cvs = $cust->getVehicles(); foreach ($cvs as $cv) { if (!isset($vehicle_ids[$cv->getID()])) { $cust->removeVehicle($cv); $em->remove($cv); } } } // check if datatable filter is present and append to query protected function setQueryFilters($datatable, &$query) { if (isset($datatable['query']['data-rows-search']) && !empty($datatable['query']['data-rows-search'])) { $query->join('q.vehicles', 'cv') ->where('q.first_name LIKE :filter') ->orWhere('q.last_name LIKE :filter') ->orWhere('q.customer_classification LIKE :filter') ->orWhere('cv.plate_number LIKE :filter') ->setParameter('filter', $datatable['query']['data-rows-search'] . '%'); } } }