From 8b8893ed1d5b8c598ebcd81d195fe3819b9ec2fb Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Thu, 5 Sep 2019 16:18:02 +0100 Subject: [PATCH] Let NHS Trusts and CCGs choose own organisation All we do via support is ask which organisation they work for and manually assign their service to it. This commit makes that process self service. We think we have all the trusts and clinical commissioning groups loaded into the database now. This will make the go live process smoother for these teams. --- app/main/forms.py | 11 +++ app/main/views/agreement.py | 15 ++-- app/main/views/organisations.py | 29 ++++++ app/models/service.py | 2 +- app/navigation.py | 4 + .../add-nhs-local-organisation.html | 26 ++++++ .../views/organisations/test_organisation.py | 90 ++++++++++++++++++- tests/app/main/views/test_agreement.py | 12 ++- 8 files changed, 178 insertions(+), 11 deletions(-) create mode 100644 app/templates/views/organisations/add-nhs-local-organisation.html diff --git a/app/main/forms.py b/app/main/forms.py index 96db524be..18ea30058 100644 --- a/app/main/forms.py +++ b/app/main/forms.py @@ -591,6 +591,17 @@ class AddGPOrganisationForm(StripWhitespaceForm): field.data = '' +class AddNHSLocalOrganisationForm(StripWhitespaceForm): + + def __init__(self, *args, organisation_choices=None, **kwargs): + super().__init__(*args, **kwargs) + self.organisations.choices = organisation_choices + + organisations = RadioField( + 'Which NHS Trust or Clinical Commissioning Group do you work for?', + ) + + class OrganisationOrganisationTypeForm(StripWhitespaceForm): organisation_type = OrganisationTypeField('What type of organisation is this?') diff --git a/app/main/views/agreement.py b/app/main/views/agreement.py index 91bc6f001..b03aecd87 100644 --- a/app/main/views/agreement.py +++ b/app/main/views/agreement.py @@ -13,12 +13,15 @@ from app.utils import user_has_permissions @main.route('/services//agreement') @user_has_permissions('manage_service') def service_agreement(service_id): - if ( - current_service.organisation_type == 'nhs_gp' and not current_service.organisation - ): - return redirect( - url_for('main.add_organisation_from_gp_service', service_id=current_service.id) - ) + if not current_service.organisation: + if current_service.organisation_type == 'nhs_gp': + return redirect( + url_for('main.add_organisation_from_gp_service', service_id=current_service.id) + ) + if current_service.organisation_type == 'nhs_local': + return redirect( + url_for('main.add_organisation_from_nhs_local_service', service_id=current_service.id) + ) if current_service.organisation.crown is None: return render_template('views/agreement/service-agreement-choose.html') if current_service.organisation.agreement_signed: diff --git a/app/main/views/organisations.py b/app/main/views/organisations.py index 430bb609f..964ac07db 100644 --- a/app/main/views/organisations.py +++ b/app/main/views/organisations.py @@ -17,6 +17,7 @@ from app import ( from app.main import main from app.main.forms import ( AddGPOrganisationForm, + AddNHSLocalOrganisationForm, ConfirmPasswordForm, GoLiveNotesForm, InviteOrgUserForm, @@ -93,6 +94,34 @@ def add_organisation_from_gp_service(service_id): ) +@main.route('/services//add-nhs-local-organisation', methods=['GET', 'POST']) +@user_has_permissions('manage_service') +def add_organisation_from_nhs_local_service(service_id): + if (not current_service.organisation_type == 'nhs_local') or current_service.organisation: + abort(403) + + form = AddNHSLocalOrganisationForm(organisation_choices=[ + (organisation.id, organisation.name) + for organisation in Organisations() + if organisation.organisation_type == 'nhs_local' + ]) + + search_form = SearchByNameForm() + + if form.validate_on_submit(): + Organisation.from_id(form.organisations.data).associate_service(service_id) + return redirect(url_for( + '.service_agreement', + service_id=service_id, + )) + + return render_template( + 'views/organisations/add-nhs-local-organisation.html', + form=form, + search_form=search_form, + ) + + @main.route("/organisations/", methods=['GET']) @user_has_permissions() def organisation_dashboard(org_id): diff --git a/app/models/service.py b/app/models/service.py index 5151c0bc3..68e3d364f 100644 --- a/app/models/service.py +++ b/app/models/service.py @@ -595,7 +595,7 @@ class Service(JSONModel): def able_to_accept_agreement(self): return ( self.organisation.agreement_signed is not None - or self.organisation_type == 'nhs_gp' + or self.organisation_type in {'nhs_gp', 'nhs_local'} ) @property diff --git a/app/navigation.py b/app/navigation.py index a4cf5ff30..fe05bcd40 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -124,6 +124,7 @@ class HeaderNavigation(Navigation): 'add_data_retention', 'add_organisation', 'add_organisation_from_gp_service', + 'add_organisation_from_nhs_local_service', 'add_service', 'add_service_template', 'api_callbacks', @@ -381,6 +382,7 @@ class MainNavigation(Navigation): }, 'settings': { 'add_organisation_from_gp_service', + 'add_organisation_from_nhs_local_service', 'branding_request', 'estimate_usage', 'link_service_to_organisation', @@ -636,6 +638,7 @@ class CaseworkNavigation(Navigation): 'add_data_retention', 'add_organisation', 'add_organisation_from_gp_service', + 'add_organisation_from_nhs_local_service', 'add_service', 'add_service_template', 'api_callbacks', @@ -930,6 +933,7 @@ class OrgNavigation(Navigation): 'add_data_retention', 'add_organisation', 'add_organisation_from_gp_service', + 'add_organisation_from_nhs_local_service', 'add_service', 'add_service_template', 'api_callbacks', diff --git a/app/templates/views/organisations/add-nhs-local-organisation.html b/app/templates/views/organisations/add-nhs-local-organisation.html new file mode 100644 index 000000000..82e42fcdd --- /dev/null +++ b/app/templates/views/organisations/add-nhs-local-organisation.html @@ -0,0 +1,26 @@ +{% extends "withnav_template.html" %} +{% from "components/page-header.html" import page_header %} +{% from "components/page-footer.html" import sticky_page_footer %} +{% from "components/textbox.html" import textbox %} +{% from "components/radios.html" import radios %} +{% from "components/live-search.html" import live_search %} +{% from "components/form.html" import form_wrapper %} + +{% block per_page_title %} + Accept our data sharing and financial agreement +{% endblock %} + +{% block maincolumn_content %} + {{ page_header( + 'Accept our data sharing and financial agreement', + back_link=url_for('main.request_to_go_live', service_id=current_service.id) + ) }} + {% call form_wrapper() %} +

+ {{ form.organisations.label.text }} +

+ {{ live_search(target_selector='.multiple-choice', show=True, form=search_form, label='Search by name') }} + {{ radios(form.organisations, hide_legend=True) }} + {{ sticky_page_footer('Continue') }} + {% endcall %} +{% endblock %} diff --git a/tests/app/main/views/organisations/test_organisation.py b/tests/app/main/views/organisations/test_organisation.py index d9bab799b..edf12e244 100644 --- a/tests/app/main/views/organisations/test_organisation.py +++ b/tests/app/main/views/organisations/test_organisation.py @@ -165,7 +165,7 @@ def test_create_new_organisation_validates( ('central', None, 403), ('nhs_gp', organisation_json(organisation_type='nhs_gp'), 403), )) -def test_only_gps_can_create_own_organisations( +def test_gps_can_create_own_organisations( client_request, mocker, service_one, @@ -193,6 +193,62 @@ def test_only_gps_can_create_own_organisations( ) +@pytest.mark.parametrize('organisation_type, organisation, expected_status', ( + ('nhs_local', None, 200), + ('nhs_gp', None, 403), + ('central', None, 403), + ('nhs_local', organisation_json(organisation_type='nhs_local'), 403), +)) +def test_nhs_local_can_create_own_organisations( + client_request, + mocker, + service_one, + organisation_type, + organisation, + expected_status, +): + mocker.patch('app.organisations_client.get_service_organisation', return_value=organisation) + mocker.patch( + 'app.models.organisation.Organisations.client', + return_value=[ + organisation_json('t1', 'Trust 1', organisation_type='nhs_local'), + organisation_json('t2', 'Trust 2', organisation_type='nhs_local'), + organisation_json('gp1', 'GP 1', organisation_type='nhs_gp'), + organisation_json('c1', 'Central 1'), + ], + ) + service_one['organisation_type'] = organisation_type + + page = client_request.get( + '.add_organisation_from_nhs_local_service', + service_id=SERVICE_ONE_ID, + _expected_status=expected_status, + ) + + if expected_status == 403: + return + + assert normalize_spaces(page.select_one('main p').text) == ( + 'Which NHS Trust or Clinical Commissioning Group do you work for?' + ) + assert page.select_one('[data-module=live-search]')['data-targets'] == ( + '.multiple-choice' + ) + assert [ + ( + normalize_spaces(radio.select_one('label').text), + radio.select_one('input')['value'] + ) + for radio in page.select('.multiple-choice') + ] == [ + ('Trust 1', 't1'), + ('Trust 2', 't2'), + ] + assert normalize_spaces(page.select_one('.js-stick-at-bottom-when-scrolling button').text) == ( + 'Continue' + ) + + @pytest.mark.parametrize('data, expected_service_name', ( ( { @@ -278,6 +334,38 @@ def test_validation_of_gps_creating_organisations( assert normalize_spaces(page.select_one('.error-message').text) == expected_error +def test_nhs_local_assigns_to_selected_organisation( + client_request, + mocker, + service_one, + mock_get_organisation, + mock_update_service_organisation, +): + mocker.patch('app.organisations_client.get_service_organisation', return_value=None) + mocker.patch( + 'app.models.organisation.Organisations.client', + return_value=[ + organisation_json(ORGANISATION_ID, 'Trust 1', organisation_type='nhs_local'), + ], + ) + service_one['organisation_type'] = 'nhs_local' + + client_request.post( + '.add_organisation_from_nhs_local_service', + service_id=SERVICE_ONE_ID, + _data={ + 'organisations': ORGANISATION_ID, + }, + _expected_status=302, + _expected_redirect=url_for( + 'main.service_agreement', + service_id=SERVICE_ONE_ID, + _external=True + ) + ) + mock_update_service_organisation.assert_called_once_with(SERVICE_ONE_ID, ORGANISATION_ID) + + def test_organisation_services_shows_live_services_only( client_request, mock_get_organisation, diff --git a/tests/app/main/views/test_agreement.py b/tests/app/main/views/test_agreement.py index ee2e70bda..1f990aed5 100644 --- a/tests/app/main/views/test_agreement.py +++ b/tests/app/main/views/test_agreement.py @@ -108,22 +108,28 @@ def test_show_agreement_page( assert link['href'] == url() -def test_unknown_gps_are_redirected( +@pytest.mark.parametrize('org_type, expected_endpoint', ( + ('nhs_gp', 'main.add_organisation_from_gp_service'), + ('nhs_local', 'main.add_organisation_from_nhs_local_service'), +)) +def test_unknown_gps_and_trusts_are_redirected( client_request, mocker, fake_uuid, mock_has_jobs, service_one, + org_type, + expected_endpoint, ): mocker.patch('app.organisations_client.get_service_organisation', return_value=None) service_one['organisation_id'] = None - service_one['organisation_type'] = 'nhs_gp' + service_one['organisation_type'] = org_type client_request.get( 'main.service_agreement', service_id=SERVICE_ONE_ID, _expected_status=302, _expected_redirect=url_for( - 'main.add_organisation_from_gp_service', + expected_endpoint, service_id=SERVICE_ONE_ID, _external=True, ),