From daeefefeaa834acdf8baa43e6e6734ff17bf34a9 Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Wed, 4 Sep 2019 16:27:25 +0100 Subject: [PATCH] Let GP surgeries create their own organisations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We have a bunch of GP surgeries who want to go live. They don’t automatically assigned to organisations. So this means a lot of back and forth to get these organisations set up, and then the service has to re-request to go live, and… it’s painful. Instead, let’s let GPs create their own organisations, by confirming the name of their organisation before going on to letting them accept the agreement. --- app/main/views/agreement.py | 6 ++ app/main/views/organisations.py | 31 +++++++++ app/models/organisation.py | 6 ++ app/navigation.py | 4 ++ .../organisations/add-gp-organisation.html | 18 +++++ .../views/organisations/test_organisation.py | 69 +++++++++++++++++++ tests/app/main/views/test_agreement.py | 22 ++++++ 7 files changed, 156 insertions(+) create mode 100644 app/templates/views/organisations/add-gp-organisation.html diff --git a/app/main/views/agreement.py b/app/main/views/agreement.py index ce0851d30..91bc6f001 100644 --- a/app/main/views/agreement.py +++ b/app/main/views/agreement.py @@ -13,6 +13,12 @@ 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 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 6e94d3a0f..c9b790ed5 100644 --- a/app/main/views/organisations.py +++ b/app/main/views/organisations.py @@ -7,6 +7,7 @@ from werkzeug.exceptions import abort from app import ( current_organisation, + current_service, email_branding_client, letter_branding_client, org_invite_api_client, @@ -63,6 +64,36 @@ def add_organisation(): ) +@main.route('/services//add-gp-organisation', methods=['GET', 'POST']) +@user_has_permissions('manage_service') +def add_organisation_from_gp_service(service_id): + print(current_service.organisation_type) + print(current_service.organisation) + if (not current_service.organisation_type == 'nhs_gp') or current_service.organisation: + abort(403) + + form = RenameOrganisationForm() + + if form.validate_on_submit(): + Organisation.create( + form.name.data, + crown=False, + organisation_type='nhs_gp', + agreement_signed=False, + ).associate_service( + service_id + ) + return redirect(url_for( + '.service_agreement', + service_id=service_id, + )) + + return render_template( + 'views/organisations/add-gp-organisation.html', + form=form + ) + + @main.route("/organisations/", methods=['GET']) @user_has_permissions() def organisation_dashboard(org_id): diff --git a/app/models/organisation.py b/app/models/organisation.py index e617e295a..22b0635ca 100644 --- a/app/models/organisation.py +++ b/app/models/organisation.py @@ -152,6 +152,12 @@ class Organisation(JSONModel): response = organisations_client.update_organisation(self.id, **kwargs) self.__init__(response) + def associate_service(self, service_id): + organisations_client.update_service_organisation( + str(service_id), + self.id + ) + class Organisations(ModelList): client = organisations_client.get_organisations diff --git a/app/navigation.py b/app/navigation.py index d04a43998..a4cf5ff30 100644 --- a/app/navigation.py +++ b/app/navigation.py @@ -123,6 +123,7 @@ class HeaderNavigation(Navigation): 'action_blocked', 'add_data_retention', 'add_organisation', + 'add_organisation_from_gp_service', 'add_service', 'add_service_template', 'api_callbacks', @@ -379,6 +380,7 @@ class MainNavigation(Navigation): 'usage', }, 'settings': { + 'add_organisation_from_gp_service', 'branding_request', 'estimate_usage', 'link_service_to_organisation', @@ -633,6 +635,7 @@ class CaseworkNavigation(Navigation): 'action_blocked', 'add_data_retention', 'add_organisation', + 'add_organisation_from_gp_service', 'add_service', 'add_service_template', 'api_callbacks', @@ -926,6 +929,7 @@ class OrgNavigation(Navigation): 'action_blocked', 'add_data_retention', 'add_organisation', + 'add_organisation_from_gp_service', 'add_service', 'add_service_template', 'api_callbacks', diff --git a/app/templates/views/organisations/add-gp-organisation.html b/app/templates/views/organisations/add-gp-organisation.html new file mode 100644 index 000000000..d1b713719 --- /dev/null +++ b/app/templates/views/organisations/add-gp-organisation.html @@ -0,0 +1,18 @@ +{% extends "withnav_template.html" %} +{% from "components/page-header.html" import page_header %} +{% from "components/page-footer.html" import page_footer %} +{% from "components/textbox.html" import textbox %} +{% from "components/radios.html" import radios %} +{% from "components/form.html" import form_wrapper %} + +{% block per_page_title %} + What is the name of your organisation? +{% endblock %} + +{% block maincolumn_content %} + {{ page_header('What is the name of your organisation?') }} + {% call form_wrapper() %} + {{textbox(form.name)}} + {{ 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 7e3ae2de9..b9fc159ad 100644 --- a/tests/app/main/views/organisations/test_organisation.py +++ b/tests/app/main/views/organisations/test_organisation.py @@ -160,6 +160,75 @@ def test_create_new_organisation_validates( assert mock_create_organisation.called is False +@pytest.mark.parametrize('organisation_type, organisation, expected_status', ( + ('nhs_gp', None, 200), + ('central', None, 403), + ('nhs_gp', organisation_json(organisation_type='nhs_gp'), 403), +)) +def test_only_gps_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) + service_one['organisation_type'] = organisation_type + + page = client_request.get( + '.add_organisation_from_gp_service', + service_id=SERVICE_ONE_ID, + _expected_status=expected_status, + ) + + if expected_status == 403: + return + + assert page.select_one('input[type=text]')['name'] == 'name' + assert normalize_spaces( + page.select_one('label[for=name]').text + ) == ( + 'What’s your practice called?' + ) + + +def test_gps_can_name_their_organisation( + client_request, + mocker, + service_one, + mock_update_service_organisation, +): + mocker.patch('app.organisations_client.get_service_organisation', return_value=None) + service_one['organisation_type'] = 'nhs_gp' + mock_create_organisation = mocker.patch( + 'app.organisations_client.create_organisation', + return_value=organisation_json(ORGANISATION_ID), + ) + + client_request.post( + '.add_organisation_from_gp_service', + service_id=SERVICE_ONE_ID, + _data={ + 'name': 'Dr. Example', + }, + _expected_status=302, + _expected_redirect=url_for( + 'main.service_agreement', + service_id=SERVICE_ONE_ID, + _external=True, + ) + ) + + mock_create_organisation.assert_called_once_with( + name='Dr. Example', + organisation_type='nhs_gp', + agreement_signed=False, + crown=False, + ) + 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 0206e73e1..5d487c31e 100644 --- a/tests/app/main/views/test_agreement.py +++ b/tests/app/main/views/test_agreement.py @@ -108,6 +108,28 @@ def test_show_agreement_page( assert link['href'] == url() +def test_unknown_gps_are_redirected( + client_request, + mocker, + fake_uuid, + mock_has_jobs, + service_one, +): + mocker.patch('app.organisations_client.get_service_organisation', return_value=None) + service_one['organisation_id'] = None + service_one['organisation_type'] = 'nhs_gp' + client_request.get( + 'main.service_agreement', + service_id=SERVICE_ONE_ID, + _expected_status=302, + _expected_redirect=url_for( + 'main.add_organisation_from_gp_service', + service_id=SERVICE_ONE_ID, + _external=True, + ), + ) + + @pytest.mark.parametrize('crown, expected_status, expected_file_fetched, expected_file_served', ( ( True, 200, 'crown.pdf',