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