Resolve "Unique ID for Users" #1562

Closed
korina.cordero wants to merge 9 commits from 638-unique-id-for-users into master-fix
14 changed files with 401 additions and 26 deletions

View file

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

View file

@ -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();
}

View file

@ -0,0 +1,106 @@
<?php
namespace App\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Query;
use Doctrine\DBAL\DBALException;
use App\Entity\Customer;
use PDO;
class CreateCustomerGeneratedIdCommand extends Command
{
const GENERATED_ID_LENGTH = 40;
private $em;
public function __construct(EntityManagerInterface $em)
{
$this->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);
}
}

View file

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

View file

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

View file

@ -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();
}

View file

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

View file

@ -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);
}

View file

@ -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();
}

View file

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

View file

@ -0,0 +1,59 @@
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Customer;
use App\Service\UniqueIdGenerator;
class CustomerGeneratedIdService
{
protected $em;
protected $id_gen;
public function __construct(EntityManagerInterface $em, UniqueIdGenerator $id_gen)
{
$this->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;
}
}
}

View file

@ -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(),

View file

@ -0,0 +1,50 @@
<?php
namespace App\Service;
use Doctrine\ORM\EntityManagerInterface;
use App\Entity\Customer;
class UniqueIdGenerator
{
protected $em;
public function __construct(EntityManagerInterface $em)
{
$this->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);
}
}

View file

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