diff --git a/frontstage/controllers/party_controller.py b/frontstage/controllers/party_controller.py
index f3f215add..c619a4f41 100644
--- a/frontstage/controllers/party_controller.py
+++ b/frontstage/controllers/party_controller.py
@@ -574,6 +574,22 @@ def register_pending_surveys(payload: json, party_id: str) -> requests.Response:
return response
+def get_existing_pending_surveys(party_id: str) -> dict:
+ """
+ Returns a list of pending_surveys that already exist in the DB for this user
+ """
+ url = f"{app.config['PARTY_URL']}/party-api/v1/pending-surveys/originator/{party_id}"
+ response = requests.get(url, auth=app.config["BASIC_AUTH"])
+ try:
+ response.raise_for_status()
+ except requests.exceptions.HTTPError:
+ logger.error("Failed to get pending surveys for respondent", party_id=party_id)
+ raise ApiError(logger, response)
+
+ logger.info("Successfully retrieved pending surveys for respondent", party_id=party_id)
+ return response.json()
+
+
def get_pending_surveys_batch_number(batch_no):
"""
Gets batch number for the shared survey
diff --git a/frontstage/views/account/account_transfer_survey.py b/frontstage/views/account/account_transfer_survey.py
index a96f6aa9b..dbd49039d 100644
--- a/frontstage/views/account/account_transfer_survey.py
+++ b/frontstage/views/account/account_transfer_survey.py
@@ -12,6 +12,7 @@
from frontstage.controllers import party_controller, survey_controller
from frontstage.controllers.party_controller import (
get_business_by_id,
+ get_existing_pending_surveys,
get_respondent_enrolments,
get_surveys_to_transfer_map,
register_pending_surveys,
@@ -92,11 +93,83 @@ def transfer_survey_email_entry(session):
return render_template("surveys/surveys-transfer/recipient-email-address.html", form=form, errors=errors)
flask_session["transfer_survey_recipient_email_address"] = form.data["email_address"]
+
+ existing_pending_surveys = get_existing_pending_surveys(party_id)
+
+ if existing_pending_surveys:
+ duplicate_transfers = _get_duplicate_transfers(existing_pending_surveys, form.data["email_address"])
+ if duplicate_transfers:
+ business_survey_enrolments = get_respondent_enrolments(party_id)
+ errors = {
+ "email_address": [
+ _build_duplicate_transfer_error_message(duplicate_transfers, business_survey_enrolments)
+ ]
+ }
+ return render_template(
+ "surveys/surveys-transfer/recipient-email-address.html", form=form, errors=errors
+ )
+
return redirect(url_for("account_bp.send_transfer_instruction_get"))
return render_template("surveys/surveys-transfer/recipient-email-address.html", form=form)
+def _get_duplicate_transfers(existing_pending_surveys, email_address):
+ duplicate_transfers = []
+ for existing_pending_survey in existing_pending_surveys:
+ if (
+ _existing_pending_survey_is_in_selection(
+ flask_session["surveys_to_transfer_map"],
+ existing_pending_survey["business_id"],
+ existing_pending_survey["survey_id"],
+ )
+ and existing_pending_survey["email_address"].lower() == email_address.lower()
+ ):
+ duplicate_transfers.append(
+ {
+ "business_id": existing_pending_survey["business_id"],
+ "survey_id": existing_pending_survey["survey_id"],
+ "email_address": existing_pending_survey["email_address"].lower(),
+ }
+ )
+ return duplicate_transfers
+
+
+def _build_duplicate_transfer_error_message(duplicate_transfers, business_survey_enrolments):
+ error_message = "You have already shared or transferred the following surveys to this email address. "
+ error_message += "They have 72 hours to accept your request. "
+ error_message += "
If you have made an error then wait for the share/transfer to expire or contact us."
+ error_message += "
"
+ for transfer in duplicate_transfers:
+ business_name = next(
+ (
+ business["business_name"]
+ for business in business_survey_enrolments
+ if business["business_id"] == transfer["business_id"]
+ ),
+ None,
+ )
+ survey_name = next(
+ (
+ survey["long_name"]
+ for business in business_survey_enrolments
+ if business["business_id"] == transfer["business_id"]
+ for survey in business["survey_details"]
+ if survey["id"] == transfer["survey_id"]
+ ),
+ None,
+ )
+ error_message += "- " + business_name + " - " + survey_name + "
"
+ error_message += "
"
+ return error_message
+
+
+def _existing_pending_survey_is_in_selection(surveys_to_transfer_map, business_id, survey_id):
+ if business_id in surveys_to_transfer_map:
+ return survey_id in surveys_to_transfer_map[business_id]
+ return False
+
+
@account_bp.route("/transfer-surveys/send-instruction", methods=["GET"])
@jwt_authorization(request)
def send_transfer_instruction_get(_):
diff --git a/tests/integration/mocked_services.py b/tests/integration/mocked_services.py
index 31c6e9417..a6a50aa0c 100644
--- a/tests/integration/mocked_services.py
+++ b/tests/integration/mocked_services.py
@@ -79,6 +79,9 @@
with open("tests/test_data/survey/bricks_survey.json") as fp:
survey = json.load(fp)
+with open("tests/test_data/party/pending_surveys.json") as fp:
+ pending_surveys = json.load(fp)
+
with open("tests/test_data/survey/qbs_survey.json") as fp:
survey_eq = json.load(fp)
@@ -108,6 +111,7 @@
url_get_business_party = f"{app.config['PARTY_URL']}/party-api/v1/businesses/id/{business_party['id']}"
url_get_respondent_party = f"{app.config['PARTY_URL']}/party-api/v1/respondents/party_id/{party['id']}"
url_get_respondent_enrolments = f"{app.config['PARTY_URL']}/party-api/v1/enrolments/respondent/{party['id']}"
+url_get_existing_pending_surveys = f"{app.config['PARTY_URL']}/party-api/v1/pending-surveys/originator/{party['id']}"
url_get_case = f"{app.config['CASE_URL']}/cases/{case['id']}"
url_get_case_by_enrolment_code = f"{app.config['CASE_URL']}/cases/iac/{enrolment_code}"
url_get_case_categories = f"{app.config['CASE_URL']}/categories"
@@ -137,6 +141,7 @@
url_auth_token = f"{app.config['AUTH_URL']}/api/v1/tokens/"
url_password_change = f"{app.config['PARTY_URL']}/party-api/v1/respondents/change_password"
url_post_add_survey = f"{app.config['PARTY_URL']}/party-api/v1/respondents/add_survey"
+url_get_post_add_survey = f"{app.config['PARTY_URL']}/party-api/v1/respondents/add_survey"
url_post_case_event_uuid = f"{app.config['CASE_URL']}/cases/{case['id']}/events"
url_reset_password_request = f"{app.config['PARTY_URL']}/party-api/v1/respondents/request_password_change"
url_send_message = app.config["SECURE_MESSAGE_URL"] + "/messages"
diff --git a/tests/integration/views/account/test_transfer_surveys.py b/tests/integration/views/account/test_transfer_surveys.py
index e94a290cf..bceff5ac8 100644
--- a/tests/integration/views/account/test_transfer_surveys.py
+++ b/tests/integration/views/account/test_transfer_surveys.py
@@ -9,10 +9,13 @@
business_party,
encoded_jwt_token,
party,
+ pending_surveys,
respondent_enrolments,
respondent_party,
survey,
url_banner_api,
+ url_get_existing_pending_surveys,
+ url_get_respondent_enrolments,
url_get_respondent_party,
url_get_survey,
)
@@ -153,12 +156,40 @@ def test_transfer_survey_recipient_email_invalid(self, mock_request):
self.assertIn("Problem with the email address".encode(), response.data)
self.assertIn("Invalid email address".encode(), response.data)
+ @requests_mock.mock()
+ def test_transfer_survey_transfer_duplicates(self, mock_request):
+ mock_request.get(url_banner_api, status_code=404)
+ mock_request.get(url_get_respondent_party, status_code=200, json=respondent_party)
+ mock_request.get(url_get_business_details, status_code=200, json=[business_party])
+ mock_request.get(url_get_survey, status_code=200, json=survey)
+ mock_request.get(url_get_existing_pending_surveys, status_code=200, json=pending_surveys)
+ mock_request.get(url_get_respondent_enrolments, json=respondent_enrolments)
+ with self.app.session_transaction() as mock_session:
+ mock_session["surveys_to_transfer_map"] = {business_party["id"]: [survey["id"]]}
+ response = self.app.post(
+ "/my-account/transfer-surveys/recipient-email-address",
+ data={"email_address": "bob@here.com"},
+ follow_redirects=True,
+ )
+ self.assertEqual(response.status_code, 200)
+ self.assertIn(
+ "You have already shared or transferred the following surveys to this email address.".encode(),
+ response.data,
+ )
+ self.assertIn("They have 72 hours to accept your request.".encode(), response.data)
+ self.assertIn(
+ "
If you have made an error then wait for the share/transfer to expire or contact us.".encode(),
+ response.data,
+ )
+ self.assertIn("".encode(), response.data)
+
@requests_mock.mock()
def test_transfer_survey_transfer_instruction(self, mock_request):
mock_request.get(url_banner_api, status_code=404)
mock_request.get(url_get_respondent_party, status_code=200, json=respondent_party)
mock_request.get(url_get_business_details, status_code=200, json=[business_party])
mock_request.get(url_get_survey, status_code=200, json=survey)
+ mock_request.get(url_get_existing_pending_surveys, status_code=200, json=pending_surveys)
with self.app.session_transaction() as mock_session:
mock_session["surveys_to_transfer_map"] = {business_party["id"]: [survey["id"]]}
response = self.app.post(
diff --git a/tests/test_data/party/pending_surveys.json b/tests/test_data/party/pending_surveys.json
new file mode 100644
index 000000000..41bb886a1
--- /dev/null
+++ b/tests/test_data/party/pending_surveys.json
@@ -0,0 +1,11 @@
+[
+ {
+ "batch_no": "be34d7ca-1d06-4f6e-98e3-73b80c911d32",
+ "business_id": "be3483c3-f5c9-4b13-bdd7-244db78ff687",
+ "email_address": "bob@here.com",
+ "is_transfer": true,
+ "shared_by": "f956e8ae-6e0f-4414-b0cf-a07c1aa3e37b",
+ "survey_id": "cb8accda-6118-4d3b-85a3-149e28960c54",
+ "time_shared": "2025-02-05 16:08:58"
+ }
+]