diff --git a/app/api/module/Api/config/command-map.config.php b/app/api/module/Api/config/command-map.config.php index 27a4e26dd3..8295e33f77 100644 --- a/app/api/module/Api/config/command-map.config.php +++ b/app/api/module/Api/config/command-map.config.php @@ -374,6 +374,7 @@ TransferCommand\User\UpdatePartner::class => CommandHandler\User\UpdatePartner::class, TransferCommand\User\DeletePartner::class => CommandHandler\User\DeletePartner::class, TransferCommand\User\UpdateUserLastLoginAt::class => CommandHandler\User\UpdateUserLastLoginAt::class, + TransferCommand\User\RegisterConsultantAndOperator::class => CommandHandler\User\RegisterConsultantAndOperator::class, // Transfer - Team TransferCommand\Team\CreateTeam::class => CommandHandler\Team\CreateTeam::class, diff --git a/app/api/module/Api/config/validation-map/user.config.php b/app/api/module/Api/config/validation-map/user.config.php index 008085292b..8219ff0a33 100644 --- a/app/api/module/Api/config/validation-map/user.config.php +++ b/app/api/module/Api/config/validation-map/user.config.php @@ -31,6 +31,7 @@ CommandHandler\User\RegisterUserSelfserve::class => NoValidationRequired::class, CommandHandler\User\RegisterUserSelfserveFactory::class => NoValidationRequired::class, CommandHandler\User\RemindUsernameSelfserve::class => NoValidationRequired::class, + CommandHandler\User\RegisterConsultantAndOperator::class => NoValidationRequired::class, CommandHandler\User\UpdatePartner::class => IsInternalUser::class, CommandHandler\User\CreateUserSelfserve::class => CanManageUser::class, CommandHandler\User\CreateUserSelfServeFactory::class => CanManageUser::class, diff --git a/app/api/module/Api/src/Domain/CommandHandler/User/RegisterConsultantAndOperator.php b/app/api/module/Api/src/Domain/CommandHandler/User/RegisterConsultantAndOperator.php new file mode 100644 index 0000000000..b7c10ea88e --- /dev/null +++ b/app/api/module/Api/src/Domain/CommandHandler/User/RegisterConsultantAndOperator.php @@ -0,0 +1,34 @@ +result->merge($this->handleSideEffect(RegisterUserSelfServeCommand::create($command->getOperatorDetails()))); + + // Get the newly created user entity + $user = $this->getRepo()->fetchById($this->result->getId('user')); + + // Add the org ID of the newly created user/org to the consultant details, then register the consultant + $consultantDetails = $command->getConsultantDetails(); + $consultantDetails['organisation'] = $user->getOrganisationUsers()->first()->getOrganisation()->getId(); + + $this->result->merge($this->handleSideEffect(RegisterUserSelfServeCommand::create($consultantDetails))); + + return $this->result; + } +} diff --git a/app/api/module/Api/src/Domain/CommandHandler/User/RegisterUserSelfserve.php b/app/api/module/Api/src/Domain/CommandHandler/User/RegisterUserSelfserve.php index f2cd2da5f0..7a86c11c5e 100644 --- a/app/api/module/Api/src/Domain/CommandHandler/User/RegisterUserSelfserve.php +++ b/app/api/module/Api/src/Domain/CommandHandler/User/RegisterUserSelfserve.php @@ -60,6 +60,9 @@ public function handleCommand(CommandInterface $command) } elseif (!empty($data['organisationName'])) { // create organisation and link with it $data['organisations'] = [$this->createOrganisation($data)]; + } elseif (!empty($data['organisation'])) { + // link with the organisation + $data['organisations'] = [$this->getRepo('Organisation')->fetchById($data['organisation'])]; } if (empty($data['organisations'])) { diff --git a/app/api/test/module/Api/src/Domain/CommandHandler/User/RegisterConsultantAndOperatorTest.php b/app/api/test/module/Api/src/Domain/CommandHandler/User/RegisterConsultantAndOperatorTest.php new file mode 100644 index 0000000000..450100a18c --- /dev/null +++ b/app/api/test/module/Api/src/Domain/CommandHandler/User/RegisterConsultantAndOperatorTest.php @@ -0,0 +1,72 @@ +sut = new RegisterConsultantAndOperator(); + $this->mockRepo('User', UserRepo::class); + + $mockAuthService = m::mock(\LmcRbacMvc\Service\AuthorizationService::class); + $this->mockedSmServices['LmcRbacMvc\Service\AuthorizationService'] = $mockAuthService; + + parent::setUp(); + } + + public function testHandleCommand() + { + $operatorDetails = ['organisationName' => 'Operator Org',]; + + $command = RegisterConsultantAndOperatorCommand::create( + [ + 'operatorDetails' => $operatorDetails, + 'consultantDetails' => [] + ]); + + $operatorResult = new Result(); + $operatorResult->addId('user', 100)->addMessage('User created successfully'); + + $this->expectedSideEffect( + RegisterUserSelfServeCommand::class, + $operatorDetails, + $operatorResult + ); + + $organisationId = 200; + $organisation = m::mock(); + $organisation->shouldReceive('getId')->andReturn($organisationId); + + $organisationUser = m::mock(); + $organisationUser->shouldReceive('getOrganisation')->andReturn($organisation); + + $user = m::mock(UserEntity::class)->makePartial(); + $user->shouldReceive('getOrganisationUsers->first')->andReturn($organisationUser); + + $this->repoMap['User']->shouldReceive('fetchById')->with(100)->andReturn($user); + + $consultantDetails['organisation'] = $organisationId; + + $consultantResult = new Result(); + $consultantResult->addId('user', 101)->addMessage('User created successfully'); + + $this->expectedSideEffect( + RegisterUserSelfServeCommand::class, + $consultantDetails, + $consultantResult + ); + + $result = $this->sut->handleCommand($command); + $this->assertEquals(['User created successfully', 'User created successfully'], $result->getMessages()); + } +} diff --git a/app/selfserve/module/Olcs/config/module.config.php b/app/selfserve/module/Olcs/config/module.config.php index 1c71aaea86..2d06b7e28e 100644 --- a/app/selfserve/module/Olcs/config/module.config.php +++ b/app/selfserve/module/Olcs/config/module.config.php @@ -502,7 +502,50 @@ 'route' => '/register[/]', 'defaults' => [ 'controller' => UserRegistrationController::class, - 'action' => 'add' + 'action' => 'start' + ] + ], + 'may_terminate' => true, + 'child_routes' => [ + 'operator' => [ + 'type' => 'segment', + 'options' => [ + 'route' => 'operator[/]', + 'defaults' => [ + 'controller' => UserRegistrationController::class, + 'action' => 'add' + ] + ] + ], + 'operator-representation' => [ + 'type' => 'segment', + 'options' => [ + 'route' => 'operator-representation[/]', + 'defaults' => [ + 'controller' => UserRegistrationController::class, + 'action' => 'operatorRepresentation' + ] + ] + ], + 'register-for-operator' => [ + 'type' => 'segment', + 'options' => [ + 'route' => 'register-for-operator[/]', + 'defaults' => [ + 'controller' => UserRegistrationController::class, + 'action' => 'registerForOperator' + ] + ] + ], + 'register-consultant-account' => [ + 'type' => 'segment', + 'options' => [ + 'route' => 'register-consultant-account[/]', + 'defaults' => [ + 'controller' => UserRegistrationController::class, + 'action' => 'registerConsultantAccount' + ] + ] ] ] ], @@ -1434,7 +1477,9 @@ 'CookieSettingsCookieNamesProvider' => CookieService\SettingsCookieNamesProvider::class, 'QaIrhpApplicationViewGenerator' => QaService\ViewGenerator\IrhpApplicationViewGenerator::class, 'QaIrhpPermitApplicationViewGenerator' => QaService\ViewGenerator\IrhpPermitApplicationViewGenerator::class, - LicenceVehicleManagement::class => LicenceVehicleManagement::class + LicenceVehicleManagement::class => LicenceVehicleManagement::class, + \Olcs\Session\ConsultantRegistration::class => \Olcs\Session\ConsultantRegistration::class, + ], 'abstract_factories' => [ \Laminas\Cache\Service\StorageCacheAbstractServiceFactory::class, @@ -1668,7 +1713,7 @@ 'verify/process-response' => ['*'], 'search*' => ['*'], 'index' => ['*'], - 'user-registration' => ['*'], + 'user-registration*' => ['*'], 'user-forgot-username' => ['*'], 'cookies*' => ['*'], 'privacy-notice' => ['*'], @@ -1701,5 +1746,10 @@ 'options_default_plus_cancel' => \Permits\Form\Model\Fieldset\SubmitOrCancelApplication::class, 'options_bilateral' => \Permits\Form\Model\Fieldset\SubmitOnly::class, ] - ] + ], + 'validators' => [ + 'factories' => [ + \Olcs\Form\Validator\UniqueConsultantDetails::class => \Olcs\Form\Validator\Factory\UniqueConsultantDetailsFactory::class, + ], + ], ]; diff --git a/app/selfserve/module/Olcs/src/Controller/Factory/UserRegistrationControllerFactory.php b/app/selfserve/module/Olcs/src/Controller/Factory/UserRegistrationControllerFactory.php index c465e8bd24..5e497781f4 100644 --- a/app/selfserve/module/Olcs/src/Controller/Factory/UserRegistrationControllerFactory.php +++ b/app/selfserve/module/Olcs/src/Controller/Factory/UserRegistrationControllerFactory.php @@ -8,6 +8,7 @@ use Common\Service\Helper\UrlHelperService; use Common\Service\Script\ScriptFactory; use Dvsa\Olcs\Utils\Translation\NiTextTranslation; +use Olcs\Session\ConsultantRegistration; use Psr\Container\ContainerInterface; use Laminas\ServiceManager\Factory\FactoryInterface; use Olcs\Controller\UserRegistrationController; @@ -30,6 +31,7 @@ public function __invoke(ContainerInterface $container, $requestedName, array $o $translationHelper = $container->get(TranslationHelperService::class); $urlHelper = $container->get(UrlHelperService::class); $flashMessengerHelper = $container->get(FlashMessengerHelperService::class); + $consultantRegistrationSession = $container->get(ConsultantRegistration::class); return new UserRegistrationController( $niTextTranslationUtil, @@ -38,7 +40,8 @@ public function __invoke(ContainerInterface $container, $requestedName, array $o $scriptFactory, $translationHelper, $urlHelper, - $flashMessengerHelper + $flashMessengerHelper, + $consultantRegistrationSession ); } } diff --git a/app/selfserve/module/Olcs/src/Controller/UserRegistrationController.php b/app/selfserve/module/Olcs/src/Controller/UserRegistrationController.php index db84109c44..2117089d6f 100644 --- a/app/selfserve/module/Olcs/src/Controller/UserRegistrationController.php +++ b/app/selfserve/module/Olcs/src/Controller/UserRegistrationController.php @@ -3,18 +3,28 @@ namespace Olcs\Controller; use Common\Controller\Lva\AbstractController; +use Common\FeatureToggle; use Common\Service\Cqrs\Exception\NotFoundException; use Common\Service\Helper\FlashMessengerHelperService; use Common\Service\Helper\FormHelperService; use Common\Service\Helper\TranslationHelperService; use Common\Service\Helper\UrlHelperService; use Common\Service\Script\ScriptFactory; +use Dvsa\Olcs\Transfer\Command\User\RegisterConsultantAndOperator; use Dvsa\Olcs\Transfer\Command\User\RegisterUserSelfserve as RegisterDto; +use Dvsa\Olcs\Transfer\Query\FeatureToggle\IsEnabled as IsEnabledQry; use Dvsa\Olcs\Transfer\Query\Licence\LicenceRegisteredAddress as LicenceByNumberDto; use Dvsa\Olcs\Utils\Translation\NiTextTranslation; use Laminas\Form\Form; +use Laminas\Http\Response; use Laminas\View\Model\ViewModel; use LmcRbacMvc\Service\AuthorizationService; +use Olcs\Form\Model\Form\ExistingOperatorLicence; +use Olcs\Form\Model\Form\OperatorRepresentation; +use Olcs\Form\Model\Form\RegisterConsultantAccount; +use Olcs\Form\Model\Form\RegisterForOperator; +use Olcs\Form\Model\Form\UserRegistration; +use Olcs\Session\ConsultantRegistration; /** * User Registration Controller @@ -28,31 +38,75 @@ public function __construct( protected ScriptFactory $scriptFactory, protected TranslationHelperService $translationHelper, protected UrlHelperService $urlHelper, - protected FlashMessengerHelperService $flashMessengerHelper + protected FlashMessengerHelperService $flashMessengerHelper, + protected ConsultantRegistration $consultantRegistrationSession ) { parent::__construct($niTextTranslationUtil, $authService); } /** - * Method used for the registration form page + * Temporary method to start the user registration flow based on Transport Consultant Role feature toggle. + * + * @return mixed + */ + public function startAction() + { + if($this->handleQuery( + IsEnabledQry::create(['ids' => [FeatureToggle::TRANSPORT_CONSULTANT_ROLE]]) + )->getResult()['isEnabled']) { + // If the feature toggle is enabled, start the TC journey + return $this->forward()->dispatch(static::class, ['action' => 'addTc']); + } else { + // If the feature toggle is disabled, start the normal add journey + return $this->forward()->dispatch(static::class, ['action' => 'add']); + } + } + + /** + * Transport consultant disabled toggled journey flow. * * @return ViewModel|\Laminas\Http\Response|null */ public function addAction() { - /** @var \Common\Form\Form $form */ - $form = $this->formHelper - ->createFormWithRequest('UserRegistration', $this->getRequest()); + $form = $this->formHelper->createFormWithRequest(UserRegistration::class, $this->getRequest()); if ($this->getRequest()->isPost()) { if ($this->isButtonPressed('cancel')) { return $this->redirectToHome(); } - $postData = $this->formatPostData( - $this->params()->fromPost() - ); + $postData = $this->formatPostData($this->params()->fromPost()); + $form->setData($postData); + if ($form->isValid()) { + return $this->processUserRegistration($form->getData()); + } + } + + $this->scriptFactory->loadFile('user-registration'); + + return $this->prepareView('olcs/user-registration/index', [ + 'form' => $this->alterForm($form), + 'pageTitle' => 'user-registration.page.title' + ]); + } + + /** + * Transport consultant toggle enabled journey flow. + * + * @return ViewModel|\Laminas\Http\Response|null + */ + public function addTcAction() + { + $form = $this->formHelper->createFormWithRequest(ExistingOperatorLicence::class, $this->getRequest()); + + if ($this->getRequest()->isPost()) { + if ($this->isButtonPressed('cancel')) { + return $this->redirectToHome(); + } + + $postData = $this->formatPostData($this->params()->fromPost()); $form->setData($postData); if ($form->isValid()) { @@ -60,15 +114,144 @@ public function addAction() } } - // register page - $view = new ViewModel( - [ - 'form' => $this->alterForm($form) - ] + return $this->prepareView('olcs/user-registration/operator-registration', [ + 'form' => $this->alterForm($form), + 'pageTitle' => 'user-registration.page.title' + ]); + } + + /** + * @return Response|ViewModel + */ + public function operatorRepresentationAction() + { + $form = $this->formHelper->createFormWithRequest(OperatorRepresentation::class, $this->getRequest()); + + if ($this->getRequest()->isPost()) { + if ($this->isButtonPressed('cancel')) { + return $this->redirectToHome(); + } + + $postData = $this->formatPostData($this->params()->fromPost()); + $form->setData($postData); + + if ($form->isValid()) { + if ($postData['fields']['actingOnOperatorsBehalf'] == 'Y') { + // Move on to capture details for an operator, then consultant account + return $this->redirect()->toRoute('user-registration/register-for-operator'); + } else { + // Show the original operator registration form + return $this->redirect()->toRoute('user-registration/operator'); + } + } + } + + return $this->prepareView('olcs/user-registration/index', [ + 'form' => $form, + 'pageTitle' => 'operator-representation.page.title' // Set appropriate title if needed + ]); + } + + + /** + * @return Response|ViewModel + */ + public function registerForOperatorAction() + { + $form = $this->formHelper->createFormWithRequest(RegisterForOperator::class, $this->getRequest()); + + if ($this->getRequest()->isPost()) { + if ($this->isButtonPressed('cancel')) { + return $this->redirectToHome(); + } + + $postData = $this->formatPostData($this->params()->fromPost()); + $form->setData($postData); + + if ($form->isValid()) { + // Save the operator details in session container and move on to consultant account registration + $this->consultantRegistrationSession->setOperatorDetails($form->getData()); + return $this->redirect()->toRoute('user-registration/register-consultant-account'); + } + } + + return $this->prepareView('olcs/user-registration/index', [ + 'form' => $form, + 'pageTitle' => 'register-for-operator.form.label' + ]); + } + + /** + * @return Response|ViewModel + */ + public function registerConsultantAccountAction() + { + $form = $this->formHelper->createFormWithRequest(RegisterConsultantAccount::class, $this->getRequest()); + + if ($this->getRequest()->isPost()) { + if ($this->isButtonPressed('cancel')) { + return $this->redirectToHome(); + } + + $form->setData($this->formatPostData($this->params()->fromPost())); + + if ($form->isValid()) { + $result = $this->registerConsultantAndOperator($form->getData()); + if ($result === null) { + return $this->prepareView('olcs/user-registration/check-email-consultant', [ + 'consultantEmailAddress' => $form->get('fields')->get('emailAddress')->getValue(), + 'operatorEmailAddress' => $this->consultantRegistrationSession->getOperatorDetails()['fields']['emailAddress'], + 'pageTitle' => 'user-registration.page.check-email.title' + ]); + } + return $result; + } + } + + return $this->prepareView('olcs/user-registration/index', [ + 'form' => $form, + 'pageTitle' => 'register-consultant-account.form.label' + ]); + } + + private function registerConsultantAndOperator($consultantFormData) + { + $operatorData = $this->consultantRegistrationSession->getOperatorDetails(); + $formattedOperatorData = $this->formatSaveData($operatorData); + $formattedConsultantData = $this->formatSaveData($consultantFormData); + + $response = $this->handleCommand( + RegisterConsultantAndOperator::create( + [ + 'operatorDetails' => $formattedOperatorData, + 'consultantDetails' => $formattedConsultantData, + ] + ) ); - $view->setTemplate('olcs/user-registration/index'); - $this->scriptFactory->loadFile('user-registration'); + if ($response->isOk()) { + return null; + } + + $this->flashMessengerHelper->addErrorMessage('unknown-error'); + + $this->redirect()->toRoute('user-registration'); + } + + + /** + * @param string $template + * @param array $variables + * @return ViewModel + */ + private function prepareView(string $template, array $variables = []): ViewModel + { + $view = new ViewModel($variables); + $view->setTemplate($template); + + if (isset($variables['pageTitle'])) { + $this->placeholder()->setPlaceholder('pageTitle', $variables['pageTitle']); + } return $view; } @@ -82,18 +265,18 @@ public function addAction() */ protected function alterForm(Form $form) { - // inject link into terms agreed label - $termsAgreed = $form->get('fields')->get('termsAgreed'); + if ($form->has('fields') && $form->get('fields')->has('termsAgreed')) { + $termsAgreed = $form->get('fields')->get('termsAgreed'); - $label = $this->translationHelper->translateReplace( - $termsAgreed->getLabel(), - [ - $this->urlHelper->fromRoute('terms-and-conditions') - ] - ); - - $termsAgreed->setLabel($label); + $label = $this->translationHelper->translateReplace( + $termsAgreed->getLabel(), + [ + $this->urlHelper->fromRoute('terms-and-conditions') + ] + ); + $termsAgreed->setLabel($label); + } return $form; } @@ -146,13 +329,15 @@ private function generateContentForUserRegistration(array $formData = [], array private function processUserRegistration($formData) { if ($this->isButtonPressed('postAccount')) { - // create a user for an existing licence + // Create a user for an existing licence return $this->createUserWithLic($formData); - } elseif ('Y' === $formData['fields']['isLicenceHolder']) { - // show licence details to confirm an address + } elseif (($formData['fields']['existingOperatorLicence'] ?? null) === 'Y') { + // Show user the "Speak to your admin" page return $this->showLicence($formData); + } elseif (($formData['fields']['existingOperatorLicence'] ?? null) === 'N') { + // Move on to next step in TC Journey + $this->redirect()->toRoute('user-registration/operator-representation'); } else { - // create a user for a new org return $this->createUserWithOrg($formData); } } @@ -249,7 +434,6 @@ private function createUserWithLic($formData) private function createUserWithOrg($formData) { $hasProcessed = $this->createUser($formData); - if ($hasProcessed instanceof ViewModel) { return $hasProcessed; } diff --git a/app/selfserve/module/Olcs/src/Form/Model/Fieldset/ContinueButton.php b/app/selfserve/module/Olcs/src/Form/Model/Fieldset/ContinueButton.php new file mode 100644 index 0000000000..a461a78175 --- /dev/null +++ b/app/selfserve/module/Olcs/src/Form/Model/Fieldset/ContinueButton.php @@ -0,0 +1,24 @@ +get(ConsultantRegistration::class); + return new UniqueConsultantDetails($session, $options); + } +} diff --git a/app/selfserve/module/Olcs/src/Form/Validator/UniqueConsultantDetails.php b/app/selfserve/module/Olcs/src/Form/Validator/UniqueConsultantDetails.php new file mode 100644 index 0000000000..4911abdcab --- /dev/null +++ b/app/selfserve/module/Olcs/src/Form/Validator/UniqueConsultantDetails.php @@ -0,0 +1,39 @@ + '%value% was used for the operator administrator account. You must use a different username and email for your consultant account.' + ]; + + protected $session; + + public function __construct(ConsultantRegistration $session) + { + $this->session = $session; + parent::__construct(); + } + + public function isValid($value, $context = null) + { + $this->setValue($value); + + $operatorDetails = $this->session->getOperatorDetails(); + + if ( + ($value === $operatorDetails['fields']['loginId']) + || ($value === $operatorDetails['fields']['emailAddress']) + ) { + $this->error(self::NOT_UNIQUE); + return false; + } + return true; + } +} diff --git a/app/selfserve/module/Olcs/src/Session/ConsultantRegistration.php b/app/selfserve/module/Olcs/src/Session/ConsultantRegistration.php new file mode 100644 index 0000000000..d36047d132 --- /dev/null +++ b/app/selfserve/module/Olcs/src/Session/ConsultantRegistration.php @@ -0,0 +1,47 @@ + + */ +class ConsultantRegistration extends \Laminas\Session\Container +{ + public const SESSION_NAME = 'ConsultantRegistration'; + protected const OPERATOR_DETAILS = 'operatorDetails'; + protected const CONSULTANT_DETAILS = 'consultantDetails'; + + public function __construct() + { + parent::__construct(self::SESSION_NAME); + } + + public function setOperatorDetails(array $details): self + { + $this->offsetSet(self::OPERATOR_DETAILS, $details); + return $this; + } + + public function getOperatorDetails(): ?array + { + return $this->offsetGet(self::OPERATOR_DETAILS); + } + + public function setConsultantDetails(array $details): self + { + $this->offsetSet(self::CONSULTANT_DETAILS, $details); + return $this; + } + + public function getConsultantDetails(): ?array + { + return $this->offsetGet(self::CONSULTANT_DETAILS); + } + + public function clear(): void + { + $this->getManager()->getStorage()->clear(self::SESSION_NAME); + } +} diff --git a/app/selfserve/module/Olcs/view/olcs/user-registration/check-email-consultant.phtml b/app/selfserve/module/Olcs/view/olcs/user-registration/check-email-consultant.phtml new file mode 100644 index 0000000000..8a5fa7d23a --- /dev/null +++ b/app/selfserve/module/Olcs/view/olcs/user-registration/check-email-consultant.phtml @@ -0,0 +1,38 @@ +partial( + 'partials/page-header-simple', + [ + 'pageTitle' => $this->pageTitle(), + 'pageHeaderText' => + $this->translateReplace( + 'user-registration.page.check-email-consultant-inset.content', + [ + $this->consultantEmailAddress, + $this->operatorEmailAddress + ] + ), + 'pageHeaderTextEscape' => false, + ] +); +?> + +
+ translateReplace( + 'user-registration.page.check-email-consultant.content', + [ + $this->url('auth/login/GET') + ] + ); ?> +
+Sign in +
+ translate('markup-problems-signing-in');?> +