diff --git a/app/internal/composer.lock b/app/internal/composer.lock index 9a32ee4f62..addaf68ede 100644 --- a/app/internal/composer.lock +++ b/app/internal/composer.lock @@ -4190,16 +4190,16 @@ }, { "name": "olcs/olcs-common", - "version": "v6.1.1", + "version": "v6.2.1", "source": { "type": "git", "url": "https://github.com/dvsa/olcs-common.git", - "reference": "d41df68d432954432893acef7820b2bfb111d719" + "reference": "ac10aa245877287d298354c5ce14639e6cdf9f21" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dvsa/olcs-common/zipball/d41df68d432954432893acef7820b2bfb111d719", - "reference": "d41df68d432954432893acef7820b2bfb111d719", + "url": "https://api.github.com/repos/dvsa/olcs-common/zipball/ac10aa245877287d298354c5ce14639e6cdf9f21", + "reference": "ac10aa245877287d298354c5ce14639e6cdf9f21", "shasum": "" }, "require": { @@ -4257,9 +4257,9 @@ "notification-url": "https://packagist.org/downloads/", "description": "Common library for the OLCS Project", "support": { - "source": "https://github.com/dvsa/olcs-common/tree/v6.1.1" + "source": "https://github.com/dvsa/olcs-common/tree/v6.2.1" }, - "time": "2024-02-29T14:13:38+00:00" + "time": "2024-03-04T13:42:22+00:00" }, { "name": "olcs/olcs-logging", @@ -4316,16 +4316,16 @@ }, { "name": "olcs/olcs-transfer", - "version": "v6.2.1", + "version": "v6.3.0", "source": { "type": "git", "url": "https://github.com/dvsa/olcs-transfer.git", - "reference": "a50522ac54115ef4f7c2ddabbda889f780ccd6e9" + "reference": "077a3c1f63fe2f08b4264234ab34641d9d7dff89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/dvsa/olcs-transfer/zipball/a50522ac54115ef4f7c2ddabbda889f780ccd6e9", - "reference": "a50522ac54115ef4f7c2ddabbda889f780ccd6e9", + "url": "https://api.github.com/repos/dvsa/olcs-transfer/zipball/077a3c1f63fe2f08b4264234ab34641d9d7dff89", + "reference": "077a3c1f63fe2f08b4264234ab34641d9d7dff89", "shasum": "" }, "require": { @@ -4366,9 +4366,9 @@ "notification-url": "https://packagist.org/downloads/", "description": "OLCS Transfer", "support": { - "source": "https://github.com/dvsa/olcs-transfer/tree/v6.2.1" + "source": "https://github.com/dvsa/olcs-transfer/tree/v6.3.0" }, - "time": "2024-02-28T16:55:36+00:00" + "time": "2024-03-01T15:58:32+00:00" }, { "name": "olcs/olcs-utils", diff --git a/app/internal/module/Olcs/src/Controller/AbstractController.php b/app/internal/module/Olcs/src/Controller/AbstractController.php index d18fdbfce6..8f0504e626 100644 --- a/app/internal/module/Olcs/src/Controller/AbstractController.php +++ b/app/internal/module/Olcs/src/Controller/AbstractController.php @@ -5,13 +5,17 @@ use Common\Controller\Traits as CommonTraits; use Common\Controller\Traits\GenericMethods; use Common\Controller\Traits\GenericRenderView; +use Common\RefData; use Common\Service\Helper\FormHelperService; use Common\Service\Script\ScriptFactory; use Common\Service\Table\TableFactory; use Common\Util\FlashMessengerTrait; +use Dvsa\Olcs\Transfer\Query\Messaging\Messages\UnreadCountByLicenceAndRoles; use Laminas\Mvc\Controller\AbstractActionController as LaminasAbstractActionController; +use Laminas\Mvc\MvcEvent; use Laminas\View\HelperPluginManager; use Olcs\Controller\Traits as OlcsTraits; +use Olcs\Logging\Log\Logger; /** * Abstract Controller diff --git a/app/internal/module/Olcs/src/Controller/AbstractInternalController.php b/app/internal/module/Olcs/src/Controller/AbstractInternalController.php index dd2a845a95..be92309abd 100644 --- a/app/internal/module/Olcs/src/Controller/AbstractInternalController.php +++ b/app/internal/module/Olcs/src/Controller/AbstractInternalController.php @@ -7,6 +7,7 @@ use Common\Controller\Plugin\Redirect; use Common\Data\Mapper\MapperInterface; use Common\Form\Form; +use Common\RefData; use Common\Service\Cqrs\Exception\NotFoundException; use Common\Service\Cqrs\Response; use Common\Service\Helper\FlashMessengerHelperService; @@ -14,6 +15,7 @@ use Common\Service\Helper\TranslationHelperService; use Common\Service\Table\TableBuilder; use Dvsa\Olcs\Transfer\Command\CommandInterface; +use Dvsa\Olcs\Transfer\Query\Messaging\Messages\UnreadCountByLicenceAndRoles; use Dvsa\Olcs\Transfer\Query\QueryInterface; use Laminas\Http\Request; use Laminas\Http\Response as HttpResponse; diff --git a/app/internal/module/Olcs/src/Listener/RouteParam/Licence.php b/app/internal/module/Olcs/src/Listener/RouteParam/Licence.php index a51e69ed5e..d62525a503 100644 --- a/app/internal/module/Olcs/src/Listener/RouteParam/Licence.php +++ b/app/internal/module/Olcs/src/Listener/RouteParam/Licence.php @@ -5,9 +5,12 @@ use Common\Exception\DataServiceException; use Common\FeatureToggle; use Common\RefData; +use Common\Service\Cqrs\Response; use Common\Service\Data\Surrender; use Common\View\Helper\PluginManagerAwareTrait as ViewHelperManagerAwareTrait; use Dvsa\Olcs\Transfer\Query\FeatureToggle\IsEnabled as IsEnabledQry; +use Dvsa\Olcs\Transfer\Query\Messaging\Messages\UnreadCountByLicenceAndRoles; +use Olcs\Logging\Log\Logger; use Psr\Container\ContainerInterface; use Laminas\EventManager\EventInterface; use Laminas\EventManager\EventManagerInterface; @@ -155,6 +158,71 @@ public function attach(EventManagerInterface $events, $priority = 1) ); } + /** + * Gets and applies the count to the 'Messages' navigation tab. This RouteParam is triggered from most other + * RouteParams (Application, Case, etc...). + * + * This has many checks and catches to prevent any errors from affecting the rest of internal as this code will + * be executed on most pages. + * + * As a result, errors will result in the counter dot appearing with a value of "E"; this is due to internal + * users may begin to rely on the visibility of the red counter dot appearing if there are new messages to be read + * and this ensures that they know that something is wrong, and that they should manually check. + * + * Any errors, although caught, displaying counter as "E" and moving on, will also result in a Logger::err(). + * + * @param int $licence + * @return void + */ + final public function fetchAndApplyUnreadConversationCountForLicenceToMessageTabs(int $licenceId, Navigation $navigationService): void + { + $query = UnreadCountByLicenceAndRoles::create([ + 'licence' => $licenceId, + 'roles' => [ + RefData::ROLE_SYSTEM_ADMIN, + RefData::ROLE_INTERNAL_ADMIN, + RefData::ROLE_INTERNAL_CASE_WORKER, + RefData::ROLE_INTERNAL_IRHP_ADMIN, + RefData::ROLE_INTERNAL_READ_ONLY, + ] + ]); + + try { + /* @var Response $response */ + $response = $this->getQueryService()->send($this->getAnnotationBuilderService()->createQuery($query)); + + if (!$response->isOk()) { + throw new \Exception( + sprintf( + 'Received non-OK response: %s -- %s', + $response->getStatusCode(), + $response->getBody() + ) + ); + } + $count = $response->getResult()['count']; + } catch (\Exception $e) { + $count = 'E'; + Logger::err( + 'Unable to get getUnreadConversationCountForLicence as non-OK response from UnreadCountByLicenceAndRoles query; defaulting to E', + [ + 'query' => [ + 'class' => get_class($query), + 'data' => $query->getArrayCopy(), + ], + 'exception' => [ + 'class' => get_class($e), + 'message' => $e->getMessage(), + 'trace' => $e->getTraceAsString(), + ], + ] + ); + } + + $navigationService->findById('conversations')->set('unreadLicenceConversationCount', $count); + $navigationService->findById('application_conversations')->set('unreadLicenceConversationCount', $count); + } + public function onLicence(EventInterface $e) { $routeParam = $e->getTarget(); @@ -186,7 +254,7 @@ public function onLicence(EventInterface $e) $licenceCategoryId = $licence['goodsOrPsv']['id'] ?? null; $navigationService = $this->getMainNavigationService(); - $this->handleMessagingTabVisibility($licence, $navigationService); + $this->handleMessagingTabVisibility($licenceId, $navigationService); if ($licenceCategoryId === RefData::LICENCE_CATEGORY_GOODS_VEHICLE) { $navigationService->findOneById('licence_bus')->setVisible(0); @@ -559,16 +627,21 @@ public function __invoke(ContainerInterface $container, $requestedName, array $o return $this; } - private function handleMessagingTabVisibility($licence, $sidebarNav) + private function handleMessagingTabVisibility(int $licenceId, Navigation $navigationService): void + { + if ($this->isMessagingFeatureToggleEnabled()) { + $this->fetchAndApplyUnreadConversationCountForLicenceToMessageTabs($licenceId, $navigationService); + } else { + $navigationService->findById('conversations')->setVisible(0); + $navigationService->findById('application_conversations')->setVisible(0); + } + } + + private function isMessagingFeatureToggleEnabled(): bool { $query = $this->getAnnotationBuilderService()->createQuery( IsEnabledQry::create(['ids' => [FeatureToggle::MESSAGING]]) ); - $isEnabled = $this->getQueryService()->send($query)->getResult()['isEnabled']; - - if (!$isEnabled) { - $sidebarNav->findById('conversations')->setVisible(0); - $sidebarNav->findById('application_conversations')->setVisible(0); - } + return (bool)$this->getQueryService()->send($query)->getResult()['isEnabled']; } } diff --git a/app/internal/module/Olcs/view/partials/horizontal-navigation.phtml b/app/internal/module/Olcs/view/partials/horizontal-navigation.phtml index a8fe25332c..1626647dd3 100644 --- a/app/internal/module/Olcs/view/partials/horizontal-navigation.phtml +++ b/app/internal/module/Olcs/view/partials/horizontal-navigation.phtml @@ -1,13 +1,20 @@ \ No newline at end of file + + diff --git a/app/internal/test/Olcs/src/Listener/RouteParam/LicenceTest.php b/app/internal/test/Olcs/src/Listener/RouteParam/LicenceTest.php index 424ad4a3ae..81ff52137e 100644 --- a/app/internal/test/Olcs/src/Listener/RouteParam/LicenceTest.php +++ b/app/internal/test/Olcs/src/Listener/RouteParam/LicenceTest.php @@ -5,6 +5,7 @@ use Common\Exception\DataServiceException; use Common\FeatureToggle; use Common\RefData; +use Common\Service\Cqrs\Response; use Common\Service\Data\Surrender; use Psr\Container\ContainerInterface; use Dvsa\Olcs\Transfer\Query\FeatureToggle\IsEnabled; @@ -67,12 +68,16 @@ protected function onLicenceSetup($licenceId, $licenceData) $mockViewHelperManager = m::mock(HelperPluginManager::class); $this->sut->setViewHelperManager($mockViewHelperManager); - $mockAnnotationBuilder->shouldReceive('createQuery')->once()->andReturnUsing( - function ($dto) use ($licenceId) { - $this->assertInstanceOf(\Dvsa\Olcs\Transfer\Query\Licence\Licence::class, $dto); - $this->assertSame(['id' => $licenceId], $dto->getArrayCopy()); - return 'QUERY'; - } + $mockAnnotationBuilder + ->shouldReceive('createQuery') + ->with(m::type(\Dvsa\Olcs\Transfer\Query\Licence\Licence::class)) + ->once() + ->andReturnUsing( + function ($dto) use ($licenceId) { + $this->assertInstanceOf(\Dvsa\Olcs\Transfer\Query\Licence\Licence::class, $dto); + $this->assertSame(['id' => $licenceId], $dto->getArrayCopy()); + return 'QUERY'; + } ); $mockQueryService->shouldReceive('send')->with('QUERY')->once()->andReturn($mockResult); @@ -113,7 +118,7 @@ function ($dto) use ($licenceId) { ->getMock() ); - $mockAnnotationBuilder->shouldReceive('createQuery')->once()->andReturnUsing( + $mockAnnotationBuilder->shouldReceive('createQuery')->with(m::type(IsEnabled::class))->once()->andReturnUsing( function ($dto) use ($licenceId) { $this->assertInstanceOf(IsEnabled::class, $dto); $this->assertSame(['ids' => [FeatureToggle::MESSAGING]], $dto->getArrayCopy()); @@ -121,8 +126,8 @@ function ($dto) use ($licenceId) { } ); $mockFtQueryResult = m::mock(); - $mockFtQueryResult->expects('getResult')->once()->andReturn(['isEnabled' => true]); - $mockQueryService->shouldReceive('send')->with('FT_QUERY')->once()->andReturn($mockFtQueryResult); + $mockFtQueryResult->allows('getResult')->andReturn(['isEnabled' => false]); + $mockQueryService->shouldReceive('send')->with('FT_QUERY')->once()->andReturn($mockFtQueryResult)->byDefault(); } } @@ -175,7 +180,7 @@ public function testOnLicenceWithValidGoodsLicence() $this->mockHideButton($mockSidebar, 'licence-decisions-reset-to-valid'); $this->sut->setNavigationService($mockSidebar); - $mainNav = m::mock(); + $mainNav = m::mock(Navigation::class); $mainNav ->shouldReceive('findOneById') ->with('licence_bus') @@ -211,6 +216,9 @@ public function testOnLicenceWithValidGoodsLicence() $this->sut->setMainNavigationService($mainNav); + $this->mockHideButton($this->sut->getMainNavigationService(),'conversations'); + $this->mockHideButton($this->sut->getMainNavigationService(), 'application_conversations'); + $routeParam = new RouteParam(); $routeParam->setValue($licenceId); @@ -254,6 +262,9 @@ public function testOnLicenceWithValidPsvLicence() $this->sut->setNavigationService($mockSidebar); $this->mockMainNavigation($licence['goodsOrPsv']['id']); + $this->mockHideButton($this->sut->getMainNavigationService(),'conversations'); + $this->mockHideButton($this->sut->getMainNavigationService(), 'application_conversations'); + $routeParam = new RouteParam(); $routeParam->setValue($licenceId); @@ -302,6 +313,10 @@ public function testOnLicenceWithTerminatedPsvLicence() $this->mockHideButton($mockSidebar, 'licence-decisions-reset-to-valid'); $this->sut->setNavigationService($mockSidebar); $this->mockMainNavigation($licence['goodsOrPsv']['id']); + + $this->mockHideButton($this->sut->getMainNavigationService(),'conversations'); + $this->mockHideButton($this->sut->getMainNavigationService(), 'application_conversations'); + $routeParam = new RouteParam(); $routeParam->setValue($licenceId); @@ -353,6 +368,9 @@ public function testOnLicenceWithSurrenderedGoodsLicence() $this->mockMainNavigation($licence['goodsOrPsv']['id']); + $this->mockHideButton($this->sut->getMainNavigationService(),'conversations'); + $this->mockHideButton($this->sut->getMainNavigationService(), 'application_conversations'); + $routeParam = new RouteParam(); $routeParam->setValue($licenceId); @@ -404,6 +422,9 @@ public function testOnLicenceWithNotSubmittedPsvSpecialRestricted() $this->mockMainNavigation($licence['goodsOrPsv']['id']); + $this->mockHideButton($this->sut->getMainNavigationService(),'conversations'); + $this->mockHideButton($this->sut->getMainNavigationService(), 'application_conversations'); + $routeParam = new RouteParam(); $routeParam->setValue($licenceId); @@ -459,6 +480,9 @@ public function testOnLicenceWithValidGoodsLicenceAndPendingChange() $this->mockMainNavigation($licence['goodsOrPsv']['id']); + $this->mockHideButton($this->sut->getMainNavigationService(),'conversations'); + $this->mockHideButton($this->sut->getMainNavigationService(), 'application_conversations'); + $routeParam = new RouteParam(); $routeParam->setValue($licenceId); @@ -513,6 +537,9 @@ public function testOnLicenceWithValidPsvLicenceAndPendingChange() $this->mockMainNavigation($licence['goodsOrPsv']['id']); + $this->mockHideButton($this->sut->getMainNavigationService(),'conversations'); + $this->mockHideButton($this->sut->getMainNavigationService(), 'application_conversations'); + $routeParam = new RouteParam(); $routeParam->setValue($licenceId); @@ -568,6 +595,9 @@ public function testOnLicenceWithRevokedLicence() $this->mockMainNavigation($licence['goodsOrPsv']['id']); + $this->mockHideButton($this->sut->getMainNavigationService(),'conversations'); + $this->mockHideButton($this->sut->getMainNavigationService(), 'application_conversations'); + $routeParam = new RouteParam(); $routeParam->setValue($licenceId); @@ -661,6 +691,9 @@ public function testOnLicenceSurrenderDigitallySigned() $this->signatureType = RefData::SIGNATURE_TYPE_DIGITAL_SIGNATURE; $this->mockMainNavigation($licence['goodsOrPsv']['id'], true); + $this->mockHideButton($this->sut->getMainNavigationService(),'conversations'); + $this->mockHideButton($this->sut->getMainNavigationService(), 'application_conversations'); + $mockSurrenderService = m::mock(Surrender::class); $mockSurrenderService->shouldReceive('fetchSurrenderData')->with(4)->times(1)->andReturn([ 'signatureType' => ['id' => $this->signatureType] @@ -725,6 +758,9 @@ public function testOnLicenceSurrenderedPhysicallySigned() $this->signatureType = RefData::SIGNATURE_TYPE_PHYSICAL_SIGNATURE; $this->mockMainNavigation($licence['goodsOrPsv']['id'], true); + $this->mockHideButton($this->sut->getMainNavigationService(),'conversations'); + $this->mockHideButton($this->sut->getMainNavigationService(), 'application_conversations'); + $mockSurrenderService = m::mock(Surrender::class); $mockSurrenderService->shouldReceive('fetchSurrenderData')->with(4)->times(1)->andReturn([ 'signatureType' => ['id' => $this->signatureType] @@ -859,6 +895,9 @@ public function testSurrenderServiceFails() $this->signatureType = RefData::SIGNATURE_TYPE_DIGITAL_SIGNATURE; $this->mockMainNavigation($licence['goodsOrPsv']['id'], true); + $this->mockHideButton($this->sut->getMainNavigationService(),'conversations'); + $this->mockHideButton($this->sut->getMainNavigationService(), 'application_conversations'); + $mockSurrenderService = m::mock(Surrender::class); $mockSurrenderService->shouldReceive('fetchSurrenderData')->with(4)->times(1)->andThrow( new DataServiceException('TEST') @@ -872,4 +911,100 @@ public function testSurrenderServiceFails() $this->sut->onLicence($event); } + + public function testMessagingTabsVisibleWithCountOnToggleEnabled() + { + $licenceId = 4; + $licence = [ + 'id' => $licenceId, + 'licNo' => 'L2347137', + 'licenceType' => [ + 'id' => RefData::LICENCE_TYPE_STANDARD_NATIONAL + ], + 'status' => [ + 'id' => RefData::LICENCE_STATUS_VALID + ], + 'goodsOrPsv' => [ + 'id' => RefData::LICENCE_CATEGORY_PSV + ], + 'vehicleType' => [ + 'id' => RefData::APP_VEHICLE_TYPE_HGV, + ], + 'continuationMarker' => 'CONTINUATION_MARKER', + 'organisation' => 'ORGANISATION', + 'cases' => 'CASES', + 'licenceStatusRules' => [], + 'latestNote' => ['comment' => 'latest note', 'priority' => 'Y'], + 'canHaveInspectionRequest' => true, + ]; + + $this->onLicenceSetup($licenceId, $licence); + + $mockFtQueryResult = m::mock(); + $mockFtQueryResult->expects('getResult')->once()->andReturn(['isEnabled' => true]); + $this->sut->getQueryService()->shouldReceive('send')->once('FT_QUERY')->once()->andReturn($mockFtQueryResult); + + $this->sut->getAnnotationBuilderService() + ->shouldReceive('createQuery') + ->with(m::type(\Dvsa\Olcs\Transfer\Query\Messaging\Messages\UnreadCountByLicenceAndRoles::class)) + ->once() + ->andReturnUsing( + function ($dto) use ($licenceId) { + $this->assertSame([ + 'licence' => $licenceId, + 'roles' => [ + RefData::ROLE_SYSTEM_ADMIN, + RefData::ROLE_INTERNAL_ADMIN, + RefData::ROLE_INTERNAL_CASE_WORKER, + RefData::ROLE_INTERNAL_IRHP_ADMIN, + RefData::ROLE_INTERNAL_READ_ONLY, + ], + ], $dto->getArrayCopy()); + return 'UNREAD_COUNT_QUERY'; + } + ); + + $mockCountResult = m::mock(Response::class); + $mockCountResult->shouldReceive('isOk')->once()->andReturn(true); + $mockCountResult->shouldReceive('getResult')->once()->andReturn(['count' => $count = 1]); + $this->sut->getQueryService()->shouldReceive('send')->with('UNREAD_COUNT_QUERY')->once()->andReturn($mockCountResult); + + $mockSidebar = m::mock(); + $this->mockHideButton($mockSidebar, 'licence-decisions-undo-surrender'); + $this->mockHideButton($mockSidebar, 'licence-decisions-undo-terminate'); + $this->mockHideButton($mockSidebar, 'licence-decisions-reset-to-valid'); + $this->sut->setNavigationService($mockSidebar); + $this->mockMainNavigation($licence['goodsOrPsv']['id']); + + $this->sut->getMainNavigationService() + ->expects('findById') + ->with('conversations') + ->once() + ->andReturn( + m::namedMock('conversations') + ->shouldReceive('set') + ->with('unreadLicenceConversationCount', $count) + ->once() + ->getMock() + ); + + $this->sut->getMainNavigationService() + ->expects('findById') + ->with('application_conversations') + ->once() + ->andReturn( + m::namedMock('application_conversations') + ->shouldReceive('set') + ->with('unreadLicenceConversationCount', $count) + ->once() + ->getMock() + ); + + $routeParam = new RouteParam(); + $routeParam->setValue($licenceId); + + $event = new Event(null, $routeParam); + + $this->sut->onLicence($event); + } }