diff --git a/config/services.yaml b/config/services.yaml index 0498fcdd..0ba337cf 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -301,3 +301,14 @@ services: App\Service\HubFilteringGeoChecker: arguments: $geofence_flag: "%env(HUB_GEOFENCE_ENABLE)%" + + # customer id generator + App\Service\UniqueIdGenerator: + arguments: + $em: "@doctrine.orm.entity_manager" + + # service to generate id for customer and to save customer + App\Service\CustomerGeneratedIdService: + arguments: + $em: "@doctrine.orm.entity_manager" + $id_gen: "@App\\Service\\UniqueIdGenerator" diff --git a/src/Command/CreateCustomerFromWarrantyCommand.php b/src/Command/CreateCustomerFromWarrantyCommand.php index 5ffe3a46..a379bee8 100644 --- a/src/Command/CreateCustomerFromWarrantyCommand.php +++ b/src/Command/CreateCustomerFromWarrantyCommand.php @@ -19,6 +19,8 @@ use App\Entity\Vehicle; use App\Ramcar\FuelType; use App\Ramcar\VehicleStatusCondition; +use App\Service\CustomerGeneratedIdService; + use DateTime; class CreateCustomerFromWarrantyCommand extends Command @@ -33,9 +35,12 @@ class CreateCustomerFromWarrantyCommand extends Command protected $cvu_mfg_id; protected $cvu_brand_id; - public function __construct(EntityManagerInterface $em, $cvu_mfg_id, $cvu_brand_id) + protected $cust_gen_id; + + public function __construct(EntityManagerInterface $em, $cvu_mfg_id, $cvu_brand_id, CustomerGeneratedIdService $cust_gen_id) { $this->em = $em; + $this->cust_gen_id = $cust_gen_id; $this->cvu_mfg_id = $cvu_mfg_id; $this->cvu_brand_id = $cvu_brand_id; @@ -154,6 +159,7 @@ class CreateCustomerFromWarrantyCommand extends Command error_log("($total_warr) processing $w_mobile_num from warranty..."); $customers = $this->findCustomerByNumber($w_mobile_num); + $new_cust = null; if (!empty($customers)) { @@ -232,6 +238,16 @@ class CreateCustomerFromWarrantyCommand extends Command $total_cv_added++; } } + if ($new_cust != null) + { + // TODO: temporary fix on how to save customer with a generated id + // since we need to keep generating an id until we are sure that there + // are no duplicates for generated id + // when saving the customer. This is an additional check. + // This will keep generating an id until a unique id is generated + // and the customer entity can then be inserted + $cust_gen_id->saveCustomerWithGeneratedId($new_cust); + } $this->em->flush(); $this->em->clear(); } diff --git a/src/Command/CreateCustomerGeneratedIdCommand.php b/src/Command/CreateCustomerGeneratedIdCommand.php new file mode 100644 index 00000000..058711d2 --- /dev/null +++ b/src/Command/CreateCustomerGeneratedIdCommand.php @@ -0,0 +1,106 @@ +em = $em; + + parent::__construct(); + } + + protected function configure() + { + $this->setName('customer:creategeneratedid') + ->setDescription('Create customer generated id and output to file.') + ->setHelp('Create customer generated id and output to file.') + ->addArgument('output_file', InputArgument::REQUIRED, 'Output filename'); + } + + protected function execute(InputInterface $input, OutputInterface $output) + { + $em = $this->em; + $db = $em->getConnection(); + + $output_file = $input->getArgument('output_file'); + + // get all customers with no generated ids. First time to run this would mean all customers + $cust_q = $em->createQuery('SELECT c from App\Entity\Customer c where c.generated_id is null'); + + $customers = $cust_q->iterate(); + + $output_info = []; + foreach ($customers as $row) + { + $cust = $row[0]; + $output->writeln('Processing customer ' . $cust->getID()); + + $generated_id = $this->generateUniqueId(self::GENERATED_ID_LENGTH); + + // set format of csv file customer id, generated id + $output_info[] = [ + $cust->getID(), + $generated_id, + ]; + + $em->detach($row[0]); + } + + // write to output file + $this->outputCustomerGeneratedIds($output_file, $output_info); + + return 0; + } + + protected function generateUniqueId($str_length) + { + $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $rand_string = ''; + $desired_length = 10; + + $rand_string = substr(str_shuffle($charset), 0, $desired_length); + + $salt = time() . $rand_string; + + return substr(md5($salt), 0, $str_length); + } + + protected function outputCustomerGeneratedIds($output_file, $entries) + { + try + { + $fh = fopen($output_file, "w"); + } + catch (Exception $e) + { + throw new Exception('The file "' . $report_file . '" could be opened.'); + } + + foreach($entries as $row) + { + fputcsv($fh, $row); + } + + fclose($fh); + } +} diff --git a/src/Command/ImportCarClubCustomerDataCommand.php b/src/Command/ImportCarClubCustomerDataCommand.php index 5ae3b167..fa997ff4 100644 --- a/src/Command/ImportCarClubCustomerDataCommand.php +++ b/src/Command/ImportCarClubCustomerDataCommand.php @@ -15,6 +15,8 @@ use Exception; use App\Entity\Customer; use App\Entity\CustomerTag; +use App\Service\CustomerGeneratedIdService; + class ImportCarClubCustomerDataCommand extends Command { // field index in csv file @@ -26,10 +28,12 @@ class ImportCarClubCustomerDataCommand extends Command protected $em; protected $cust_tag_hash; + protected $cust_gen_id; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, CustomerGeneratedIdService $cust_gen_id) { $this->em = $em; + $this->cust_gen_id = $cust_gen_id; $this->loadCustomerTags(); parent::__construct(); @@ -173,6 +177,15 @@ class ImportCarClubCustomerDataCommand extends Command ->addCustomerTag($promo_tag); $this->em->persist($new_cust); + + // TODO: temporary fix on how to save customer with a generated id + // since we need to keep generating an id until we are sure that there + // are no duplicates for generated id + // when saving the customer. This is an additional check. + // This will keep generating an id until a unique id is generated + // and the customer entity can then be inserted + $this->cust_gen_id->saveCustomerWithGeneratedId($new_cust); + $this->em->flush(); return $new_cust; diff --git a/src/Command/ImportCarClubCustomerHubCommand.php b/src/Command/ImportCarClubCustomerHubCommand.php index 15275a1c..f474164b 100644 --- a/src/Command/ImportCarClubCustomerHubCommand.php +++ b/src/Command/ImportCarClubCustomerHubCommand.php @@ -14,6 +14,8 @@ use App\Entity\CustomerTag; use App\Entity\CarClubCustomerHub; use App\Entity\Hub; +use App\Service\CustomerGeneratedIdService; + class ImportCarClubCustomerHubCommand extends Command { // field index in csv file @@ -28,10 +30,12 @@ class ImportCarClubCustomerHubCommand extends Command protected $em; protected $cust_tag_hash; + protected $cust_gen_id; - public function __construct(EntityManagerInterface $em) + public function __construct(EntityManagerInterface $em, CustomerGeneratedIdService $cust_gen_id) { $this->em = $em; + $this->cust_gen_id = $cust_gen_id; $this->loadCustomerTags(); parent::__construct(); @@ -188,7 +192,16 @@ class ImportCarClubCustomerHubCommand extends Command ->addCustomerTag($promo_tag); $this->em->persist($new_cust); - $this->em->flush(); + + // TODO: temporary fix on how to save customer with a generated id + // since we need to keep generating an id until we are sure that there + // are no duplicates for generated id + // when saving the customer. This is an additional check. + // This will keep generating an id until a unique id is generated + // and the customer entity can then be inserted + $this->cust_gen_id->saveCustomerWithGeneratedId($new_cust); + + //$this->em->flush(); return $new_cust; } diff --git a/src/Controller/APIController.php b/src/Controller/APIController.php index b072d8e4..da2703be 100644 --- a/src/Controller/APIController.php +++ b/src/Controller/APIController.php @@ -47,6 +47,7 @@ use App\Service\HubSelector; use App\Service\HubDistributor; use App\Service\HubFilterLogger; use App\Service\HubFilteringGeoChecker; +use App\Service\CustomerGeneratedIdService; use App\Entity\MobileSession; use App\Entity\Customer; @@ -442,7 +443,7 @@ class APIController extends Controller implements LoggedController return $cust; } - public function updateInfo(Request $req, EntityManagerInterface $em) + public function updateInfo(Request $req, EntityManagerInterface $em, CustomerGeneratedIdService $cust_gen_id) { // check required parameters and api key $required_params = [ @@ -468,8 +469,26 @@ class APIController extends Controller implements LoggedController { $cust->setPrivacyPolicyMobile($mobile_policy); } - - $em->flush(); + + // need to check if it's a new customer or updated existing customer + // a new customer would not have a generated id while an existing one + // already have one. + if ($cust->getGeneratedId() == null) + { + // TODO: temporary fix on how to save customer with a generated id + // since we need to keep generating an id until we are sure that there + // are no duplicates for generated id + // when saving the customer. This is an additional check. + // This will keep generating an id until a unique id is generated + // and the customer entity can then be inserted + $cust_gen_id->saveCustomerWithGeneratedId($cust); + } + else + { + // we do a simple flush with no id generation for updated existing + // customers + $em->flush(); + } return $res->getReturnResponse(); } diff --git a/src/Controller/CAPI/CustomerController.php b/src/Controller/CAPI/CustomerController.php index 9148321e..0d89a0e9 100644 --- a/src/Controller/CAPI/CustomerController.php +++ b/src/Controller/CAPI/CustomerController.php @@ -15,6 +15,8 @@ use App\Entity\Customer; use App\Entity\CustomerVehicle; use App\Entity\Vehicle; +use App\Service\CustomerGeneratedIdService; + use Catalyst\APIBundle\Access\Generator as ACLGenerator; class CustomerController extends APIController @@ -26,7 +28,7 @@ class CustomerController extends APIController $this->acl_gen = $acl_gen; } - public function register(Request $req, EntityManagerInterface $em) + public function register(Request $req, EntityManagerInterface $em, CustomerGeneratedIdService $cust_gen_id) { $this->denyAccessUnlessGranted('customer.register', null, 'No access.'); @@ -150,6 +152,8 @@ class CustomerController extends APIController ]; } } + + $em->flush(); } else { @@ -194,9 +198,16 @@ class CustomerController extends APIController 'condition' => $condition, 'fuel_type' => $fuel_type, ]; + + // TODO: temporary fix on how to save customer with a generated id + // since we need to keep generating an id until we are sure that there + // are no duplicates for generated id + // when saving the customer. This is an additional check. + // This will keep generating an id until a unique id is generated + // and the customer entity can then be inserted + $cust_gen_id->saveCustomerWithGeneratedId($new_cust); } - $em->flush(); $em->clear(); return new APIResponse(true, $message, $data); diff --git a/src/Controller/CAPI/CustomerWarrantyController.php b/src/Controller/CAPI/CustomerWarrantyController.php index edf00e2e..bc942b22 100644 --- a/src/Controller/CAPI/CustomerWarrantyController.php +++ b/src/Controller/CAPI/CustomerWarrantyController.php @@ -15,6 +15,7 @@ use Catalyst\APIBundle\Response\APIResponse; use App\Service\RisingTideGateway; use App\Service\WarrantyAPILogger; +use App\Service\CustomerGeneratedIdService; use App\Entity\WarrantySerial; use App\Entity\Warranty; @@ -301,7 +302,7 @@ class CustomerWarrantyController extends APIController public function register($serial, EntityManagerInterface $em, Request $req, KernelInterface $kernel, RisingTideGateway $rt, TranslatorInterface $trans, - WarrantyAPILogger $logger) + WarrantyAPILogger $logger, CustomerGeneratedIdService $cust_gen_id) { error_log('HERE - register'); @@ -325,7 +326,7 @@ class CustomerWarrantyController extends APIController $username = $this->getUser()->getName(); $source = 'CAPI_USER_' . $username; - error_log('SOURCE: ' . $source); + // error_log('SOURCE: ' . $source); // TODO: maybe add vmake_id? since warranty cannot be created with no vmake // TODO: maybe also add mobile and email since customer creation won't let mobile and email be null @@ -353,7 +354,7 @@ class CustomerWarrantyController extends APIController // do actual registering $res = $this->updateWarranty($em, $rt, $trans, $req, $serial, $inv_filename, $wcard_filename, - $logger, $log_data, $user_id, $action, $source); + $logger, $log_data, $user_id, $action, $source, $cust_gen_id); // flush to db $em->flush(); @@ -395,7 +396,7 @@ class CustomerWarrantyController extends APIController } protected function updateWarranty($em, $rt, $trans, $req, $serial, $inv_filename = null, $wcard_filename = null, - $logger, $log_data, $user_id, $action, $source) + $logger, $log_data, $user_id, $action, $source, $cust_gen_id) { $plate_num = $this->cleanPlateNumber($req->request->get('plate_num')); @@ -512,7 +513,6 @@ class CustomerWarrantyController extends APIController $cust->setPrivacyPromo($priv_promo); } - error_log('update entity / database'); // create or update warranty entry $warr->setSerial($serial) @@ -556,15 +556,30 @@ class CustomerWarrantyController extends APIController $em->persist($warr); - $logger->logWarrantyInfo($log_data, '', $user_id, $action, $source); + // need to check if it's a new customer or updated existing customer + // a new customer would not have a generated id while an existing one + // already have one. + if ($cust->getGeneratedId() == null) + { + // TODO: temporary fix on how to save customer with a generated id + // since we need to keep generating an id until we are sure that there + // are no duplicates for generated id + // when saving the customer. This is an additional check. + // This will keep generating an id until a unique id is generated + // and the customer entity can then be inserted + $cust_gen_id->saveCustomerWithGeneratedId($cust); + } // TODO: check if we need to do anything else $data = []; - // send sms confirmation $this->sendSMSConfirmation($rt, $req->request->get('contact_num'), $sms_message); + $logger->logWarrantyInfo($log_data, '', $user_id, $action, $source); + + $em->flush(); + return new APIResponse(true, 'Warranty registered.', $data); } diff --git a/src/Controller/CAPI/WarrantyController.php b/src/Controller/CAPI/WarrantyController.php index 4db8051c..b2f9c5c8 100644 --- a/src/Controller/CAPI/WarrantyController.php +++ b/src/Controller/CAPI/WarrantyController.php @@ -23,6 +23,7 @@ use App\Entity\Vehicle; use App\Entity\WarrantyAPILog; use App\Service\WarrantyAPILogger; +use App\Service\CustomerGeneratedIdService; use App\Ramcar\NameValue; use App\Ramcar\WarrantyClass; @@ -146,7 +147,7 @@ class WarrantyController extends APIController return new APIResponse(true, 'Warranties found.', $data); } - public function register(Request $req, EntityManagerInterface $em, WarrantyAPILogger $logger) + public function register(Request $req, EntityManagerInterface $em, WarrantyAPILogger $logger, CustomerGeneratedIdService $cust_gen_id) { $this->denyAccessUnlessGranted('warranty.register.battery', null, 'No access.'); @@ -287,7 +288,7 @@ class WarrantyController extends APIController { $em->persist($warr); - $this->getCustomerFromMobile($em, $warr); + $this->getCustomerFromMobile($em, $warr, $cust_gen_id); $em->flush(); } @@ -638,7 +639,7 @@ class WarrantyController extends APIController return new APIResponse(true, 'Warranties found.', $data); } - protected function getCustomerFromMobile($em, $warranty) + protected function getCustomerFromMobile($em, $warranty, $cust_gen_id) { $w_mobile = $warranty->getMobileNumber(); if (empty($w_mobile)) @@ -717,6 +718,7 @@ class WarrantyController extends APIController $this->createCustomerVehicle($em, $customer, $this->getDefaultVehicle($em), $w_plate_number); } } + $em->flush(); } // customer not found else @@ -738,9 +740,17 @@ class WarrantyController extends APIController $em->persist($new_cust); $this->createCustomerVehicle($em, $new_cust, $this->getDefaultVehicle($em), $w_plate_number); + + // TODO: temporary fix on how to save customer with a generated id + // since we need to keep generating an id until we are sure that there + // are no duplicates for generated id + // when saving the customer. This is an additional check. + // This will keep generating an id until a unique id is generated + // and the customer entity can then be inserted + $cust_gen_id->saveCustomerWithGeneratedId($new_cust); } - $em->flush(); + // $em->flush(); $em->clear(); } diff --git a/src/Entity/Customer.php b/src/Entity/Customer.php index 4af84d96..a3dfa931 100644 --- a/src/Entity/Customer.php +++ b/src/Entity/Customer.php @@ -12,10 +12,15 @@ use App\Ramcar\CustomerClassification; /** * @ORM\Entity - * @ORM\Table(name="customer", indexes={ - * @ORM\Index(name="phone_mobile_idx", columns={"phone_mobile"}), - * @ORM\Index(columns={"first_name"}, flags={"fulltext"}), - * @ORM\Index(columns={"last_name"}, flags={"fulltext"}) + * @ORM\Table(name="customer", + * uniqueConstraints={ + * @ORM\UniqueConstraint(columns={"generated_id"}) + * }, + * indexes={ + * @ORM\Index(name="phone_mobile_idx", columns={"phone_mobile"}), + * @ORM\Index(columns={"first_name"}, flags={"fulltext"}), + * @ORM\Index(columns={"last_name"}, flags={"fulltext"}), + @ORM\Index(name="generated_id_idx", columns={"generated_id"}) * }) */ class Customer @@ -222,6 +227,12 @@ class Customer */ protected $car_club_customer_hub; + // random generated unique id + /** + * @ORM\Column(type="string", length=40, nullable=true) + */ + protected $generated_id; + public function __construct() { $this->numbers = new ArrayCollection(); @@ -259,6 +270,8 @@ class Customer $this->date_create = new DateTime(); $this->create_source = 'unknown'; + + $this->generated_id = ''; } public function getID() @@ -674,4 +687,16 @@ class Customer { return $this->car_club_customer_hub; } + + public function setGeneratedID($generated_id) + { + $this->generated_id = $generated_id; + return $this; + } + + public function getGeneratedID() + { + return $this->generated_id; + } + } diff --git a/src/Service/CustomerGeneratedIdService.php b/src/Service/CustomerGeneratedIdService.php new file mode 100644 index 00000000..92c85836 --- /dev/null +++ b/src/Service/CustomerGeneratedIdService.php @@ -0,0 +1,59 @@ +em = $em; + $this->id_gen = $id_gen; + } + + public function saveCustomerWithGeneratedId(Customer $cust) + { + $em = $this->em; + $id_gen = $this->id_gen; + + // need to generate id + // have to try catch the flush because of the generated id + // need to retry until we get a unique generated id + while (true) + { + try + { + $generated_id = $id_gen->generateCustomerUniqueId(40); + $cust->setGeneratedId($generated_id); + + // reopen in case we get an exception + if (!$em->isOpen()) + { + $em = $em->create( + $em->getConnection(), + $em->getConfiguration() + ); + } + + $em->flush(); + } + catch (UniqueConstraintViolationException $e) + { + error_log($e->getMessage()); + // delay one second and try again + sleep(1); + continue; + + } + break; + } + } +} diff --git a/src/Service/CustomerHandler/ResqCustomerHandler.php b/src/Service/CustomerHandler/ResqCustomerHandler.php index f8685801..7a0a4204 100644 --- a/src/Service/CustomerHandler/ResqCustomerHandler.php +++ b/src/Service/CustomerHandler/ResqCustomerHandler.php @@ -4,12 +4,15 @@ namespace App\Service\CustomerHandler; use Doctrine\ORM\EntityManagerInterface; +use Doctrine\DBAL\Exception\UniqueConstraintViolationException; + use Symfony\Component\HttpFoundation\Request; use Symfony\Component\Validator\Validator\ValidatorInterface; use Symfony\Component\HttpKernel\Exception\NotFoundHttpException; use Symfony\Component\Security\Core\Security; use App\Service\CustomerHandlerInterface; +use App\Service\CustomerGeneratedIdService; use App\Ramcar\CustomerClassification; use App\Ramcar\FuelType; @@ -34,14 +37,16 @@ class ResqCustomerHandler implements CustomerHandlerInterface protected $country_code; protected $security; protected $template_hash; + protected $cust_gen_id; public function __construct(EntityManagerInterface $em, ValidatorInterface $validator, - string $country_code, Security $security) + string $country_code, Security $security, CustomerGeneratedIdService $cust_gen_id) { $this->em = $em; $this->validator = $validator; $this->country_code = $country_code; $this->security = $security; + $this->cust_gen_id = $cust_gen_id; $this->loadTemplates(); } @@ -292,7 +297,16 @@ class ResqCustomerHandler implements CustomerHandlerInterface { // validated! save the entity $em->persist($row); - $em->flush(); + + // TODO: temporary fix on how to save customer with a generated id + // since we need to keep generating an id until we are sure that there + // are no duplicates for generated id + // when saving the customer. This is an additional check. + // This will keep generating an id until a unique id is generated + // and the customer entity can then be inserted + $this->cust_gen_id->saveCustomerWithGeneratedId($row); + + //$em->flush(); $result = [ 'id' => $row->getID(), diff --git a/src/Service/UniqueIdGenerator.php b/src/Service/UniqueIdGenerator.php new file mode 100644 index 00000000..cfca4779 --- /dev/null +++ b/src/Service/UniqueIdGenerator.php @@ -0,0 +1,50 @@ +em = $em; + } + + public function generateCustomerUniqueId($str_length) + { + $em = $this->em; + + // retry until we have no duplicate generated id + while (true) + { + // generate the id + $generated_id = $this->generateUniqueId($str_length); + + // check if generated id already exists + $cust = $em->getRepository(Customer::class)->findOneBy(['generated_id' => $generated_id]); + + if ($cust == null) + return $generated_id; + + sleep(1); + } + } + + protected function generateUniqueId($str_length) + { + $charset = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'; + $rand_string = ''; + $desired_length = 10; + + $rand_string = substr(str_shuffle($charset), 0, $desired_length); + + $salt = time() . $rand_string; + + return substr(md5($salt), 0, $str_length); + } +} diff --git a/utils/load_customer_generated_ids/load_customer_generated_ids.sql b/utils/load_customer_generated_ids/load_customer_generated_ids.sql new file mode 100644 index 00000000..dc66316c --- /dev/null +++ b/utils/load_customer_generated_ids/load_customer_generated_ids.sql @@ -0,0 +1,13 @@ +CREATE TABLE temp_table (id int(11), generated_id varchar(40)); + +LOAD DATA INFILE '/tmp/generated_ids.csv' +INTO TABLE temp_table +FIELDS TERMINATED BY ',' +ENCLOSED BY '"' +LINES TERMINATED BY '\n' +(id, generated_id); + +UPDATE customer INNER JOIN temp_table ON temp_table.id = customer.id +SET customer.generated_id = temp_table.generated_id; + +DROP TABLE temp_table;