Skip to content

Commit c541044

Browse files
committed
Merge remote-tracking branch 'origin' into feature/BCSS-21307-fobt-regression-tests-scenario-4
to reduce potential for merge conflicts.
2 parents 60295a0 + 2884cfd commit c541044

File tree

18 files changed

+909
-35
lines changed

18 files changed

+909
-35
lines changed

classes/pi_subject.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from dataclasses import dataclass, field
22
from typing import Optional
33
from datetime import date
4+
from classes.subject import Subject
45

56

67
@dataclass
@@ -69,3 +70,48 @@ def to_string(self) -> str:
6970
f"replaced_by_nhs_number = {self.replaced_nhs_number}",
7071
]
7172
return "PISubject:\n" + "\n".join(fields)
73+
74+
@staticmethod
75+
def from_subject(subject: "Subject") -> "PISubject":
76+
"""
77+
Creates a PISubject object from a Subject object.
78+
79+
Args:
80+
subject (Subject): The Subject object to convert.
81+
82+
Returns:
83+
PISubject: The populated PISubject object.
84+
85+
"""
86+
gender = subject.get_gender()
87+
if gender is not None:
88+
gender_code = gender.redefined_value
89+
else:
90+
gender_code = 0 # If None, set to "Not known gender"
91+
return PISubject(
92+
screening_subject_id=subject.screening_subject_id,
93+
nhs_number=subject.nhs_number,
94+
family_name=subject.surname,
95+
first_given_names=subject.forename,
96+
other_given_names=subject.other_names,
97+
previous_family_name=subject.previous_surname,
98+
name_prefix=subject.title,
99+
birth_date=subject.date_of_birth,
100+
death_date=subject.date_of_death,
101+
gender_code=gender_code,
102+
address_line_1=subject.address_line1,
103+
address_line_2=subject.address_line2,
104+
address_line_3=subject.address_line3,
105+
address_line_4=subject.address_line4,
106+
address_line_5=subject.address_line5,
107+
postcode=subject.postcode,
108+
gnc_code=subject.registration_code,
109+
gp_practice_code=subject.gp_practice_code,
110+
nhais_deduction_reason=subject.nhais_deduction_reason,
111+
nhais_deduction_date=subject.nhais_deduction_date,
112+
exeter_system=subject.datasource,
113+
removed_to=subject.removed_to_datasource,
114+
pi_reference=None,
115+
superseded_by_nhs_number=None,
116+
replaced_nhs_number=None,
117+
)

classes/repositories/subject_repository.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ def process_pi_subject(self, pio_id: int, pi_subject: PISubject) -> Optional[int
103103

104104
return new_contact_id
105105

106-
def update_pi_subject(self, pi_subject: PISubject) -> None:
106+
def update_pi_subject(self, pio_id: int, pi_subject: PISubject) -> None:
107107
"""
108108
Updates an existing screening subject.
109109
@@ -122,9 +122,7 @@ def update_pi_subject(self, pi_subject: PISubject) -> None:
122122
raise ValueError(
123123
"A PI Reference must be specified when updating an existing subject, for example 'SELF REFERRAL' or 'AUTOMATED TEST'"
124124
)
125-
procedure = "PKG_SSPI.p_process_pi_subject"
126-
params = [pi_subject, None, None, None, None]
127-
self.oracle_db.execute_stored_procedure(procedure, params)
125+
self.process_pi_subject(pio_id, pi_subject)
128126

129127
def get_active_gp_practice_in_hub_and_sc(
130128
self, hub_code: str, screening_centre_code: str

classes/repositories/user_repository.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ def get_role_id_for_role(self, role: "UserRoleType") -> int:
9595
df = self.general_query(role)
9696
return int(df["role_id"].iloc[0])
9797

98-
def get_org_code_for_role(self, role: "UserRoleType") -> int:
98+
def get_org_code_for_role(self, role: "UserRoleType") -> str:
9999
"""
100100
Get the ORG CODE for the role.
101101
@@ -108,4 +108,4 @@ def get_org_code_for_role(self, role: "UserRoleType") -> int:
108108
logging.debug(f"Getting ORG CODE for role: {role.user_code}")
109109

110110
df = self.general_query(role)
111-
return int(df["org_code"].iloc[0])
111+
return str(df["org_code"].iloc[0])

classes/subject.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,11 @@
1010
from classes.sdd_reason_for_change_type import SDDReasonForChangeType
1111
from classes.ss_reason_for_change_type import SSReasonForChangeType
1212
from classes.ssdd_reason_for_change_type import SSDDReasonForChangeType
13+
from classes.user import User
1314
from utils.date_time_utils import DateTimeUtils
15+
from utils.oracle.oracle import OracleDB
1416
import pandas as pd
17+
import logging
1518

1619

1720
@dataclass
@@ -1302,3 +1305,34 @@ def from_dataframe_row(row: pd.Series) -> "Subject":
13021305
}
13031306

13041307
return Subject(**field_map)
1308+
1309+
def populate_subject_object_from_nhs_no(self, nhs_no: str) -> "Subject":
1310+
"""
1311+
Populates a Subject object from the NHS number.
1312+
Args:
1313+
nhs_no (str): The NHS number to populate the subject from.
1314+
Returns:
1315+
Subject: A populated Subject object from the database
1316+
"""
1317+
from utils.oracle.subject_selection_query_builder import (
1318+
SubjectSelectionQueryBuilder,
1319+
)
1320+
1321+
nhs_no_criteria = {"nhs number": nhs_no}
1322+
subject = Subject()
1323+
user = User()
1324+
builder = SubjectSelectionQueryBuilder()
1325+
1326+
query, bind_vars = builder.build_subject_selection_query(
1327+
criteria=nhs_no_criteria,
1328+
user=user,
1329+
subject=subject,
1330+
subjects_to_retrieve=1,
1331+
)
1332+
1333+
logging.debug(
1334+
"[SUBJECT ASSERTIONS] Executing base query to populate subject object"
1335+
)
1336+
1337+
subject_df = OracleDB().execute_query(query, bind_vars)
1338+
return self.from_dataframe_row(subject_df.iloc[0])

classes/user.py

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from typing import Optional
22
from classes.organisation_complex import Organisation
3-
import pandas as pd
3+
from classes.user_role_type import UserRoleType
44

55

66
class User:
@@ -159,3 +159,30 @@ def from_dataframe_row(self, row) -> "User":
159159
pio_id=row["pio_id"],
160160
organisation=organisation,
161161
)
162+
163+
@staticmethod
164+
def from_user_role_type(user_role_type: "UserRoleType") -> "User":
165+
"""
166+
Creates a User object from a UserRoleType object using UserRepository methods.
167+
168+
Args:
169+
user_role_type (UserRoleType): The UserRoleType object.
170+
171+
Returns:
172+
User: The constructed User object.
173+
"""
174+
from classes.repositories.user_repository import UserRepository
175+
176+
user_repo = UserRepository()
177+
pio_id = user_repo.get_pio_id_for_role(user_role_type)
178+
role_id = user_repo.get_role_id_for_role(user_role_type)
179+
org_id = user_repo.get_org_id_for_role(user_role_type)
180+
org_code = user_repo.get_org_code_for_role(user_role_type)
181+
182+
organisation = Organisation(new_id=org_id, new_code=str(org_code))
183+
return User(
184+
user_id=pio_id,
185+
role_id=role_id,
186+
pio_id=pio_id,
187+
organisation=organisation,
188+
)
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
# Utility Guide: SSPIChangeSteps
2+
3+
The `SSPIChangeSteps` utility provides a simple interface for simulating an SSPI update to change a subject's date of birth in the BCSS system. This is particularly useful for automated testing scenarios where you need to set a subject's age to a specific value.
4+
5+
---
6+
7+
## Table of Contents
8+
9+
- [Utility Guide: SSPIChangeSteps](#utility-guide-sspichangesteps)
10+
- [Table of Contents](#table-of-contents)
11+
- [Overview](#overview)
12+
- [Example Usage](#example-usage)
13+
- [Method Reference](#method-reference)
14+
- [Implementation Details](#implementation-details)
15+
16+
---
17+
18+
## Overview
19+
20+
The main method provided by this utility is:
21+
22+
```python
23+
sspi_update_to_change_dob_received(nhs_no: str, age_to_change_to: int)
24+
```
25+
26+
This method will:
27+
28+
- Retrieve the subject by NHS number.
29+
- Calculate the correct date of birth for the specified age (taking leap years into account).
30+
- Update the subject's date of birth in the database as if it was received from an SSPI update.
31+
32+
## Example Usage
33+
34+
```python
35+
from utils.sspi_change_steps import SSPIChangeSteps
36+
37+
nhs_no = "1234567890"
38+
target_age = 75
39+
40+
SSPIChangeSteps().sspi_update_to_change_dob_received(nhs_no, target_age)
41+
```
42+
43+
This will update the subject with NHS number `1234567890` to have an age of 75.
44+
45+
---
46+
47+
## Method Reference
48+
49+
`sspi_update_to_change_dob_received`
50+
51+
```python
52+
def sspi_update_to_change_dob_received(nhs_no: str, age_to_change_to: int) -> None
53+
```
54+
55+
**Parameters:**
56+
57+
- `nhs_no` (str): The NHS number of the subject to update.
58+
- `age_to_change_to` (int): The age to change the subject's date of birth to.
59+
60+
**Description:**
61+
62+
Calculates the correct date of birth for the given age and updates the subject in the database as if the change was received from an SSPI update.
63+
64+
---
65+
66+
## Implementation Details
67+
68+
- The utility uses the `Subject` and `PISubject` classes to represent and update subject data.
69+
- The date of birth is calculated using `DateTimeUtils.calculate_birth_date_for_age`, which ensures the correct age is set, accounting for leap years.
70+
- The update is performed as the automated process user (user ID 2).

pages/screening_practitioner_appointments/appointment_detail_page.py

Lines changed: 42 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
from playwright.sync_api import Page, expect
22
from pages.base_page import BasePage
3+
from enum import StrEnum
34

45

56
class AppointmentDetailPage(BasePage):
@@ -13,6 +14,10 @@ def __init__(self, page: Page):
1314
self.attended_check_box = self.page.locator("#UI_ATTENDED")
1415
self.calendar_button = self.page.get_by_role("button", name="Calendar")
1516
self.save_button = self.page.get_by_role("button", name="Save")
17+
self.cancel_radio = self.page.get_by_role("radio", name="Cancel")
18+
self.reason_for_cancellation_dropwdown = self.page.get_by_label(
19+
"Reason for Cancellation"
20+
)
1621

1722
def check_attendance_radio(self) -> None:
1823
"""Checks the attendance radio button."""
@@ -26,9 +31,16 @@ def click_calendar_button(self) -> None:
2631
"""Clicks the calendar button."""
2732
self.click(self.calendar_button)
2833

29-
def click_save_button(self) -> None:
30-
"""Clicks the save button."""
31-
self.click(self.save_button)
34+
def click_save_button(self, accept_dialog: bool = False) -> None:
35+
"""
36+
Clicks the save button.
37+
Args:
38+
accept_dialog (bool): Whether to accept the dialog.
39+
"""
40+
if accept_dialog:
41+
self.safe_accept_dialog(self.save_button)
42+
else:
43+
self.click(self.save_button)
3244

3345
def verify_text_visible(self, text: str) -> None:
3446
"""Verifies that the specified text is visible on the page."""
@@ -60,3 +72,30 @@ def wait_for_attendance_radio(self, timeout_duration: float = 30000) -> None:
6072
timeout_duration - elapsed if timeout_duration - elapsed > 0 else 1000
6173
)
6274
)
75+
76+
def check_cancel_radio(self) -> None:
77+
"""Checks the cancel radio button."""
78+
self.cancel_radio.check()
79+
80+
def select_reason_for_cancellation_option(self, option: str) -> None:
81+
"""
82+
Selects the reason for cancellation from the dropdown.
83+
Args:
84+
option: The reason for cancellation to select.
85+
The options are in the ReasonForCancellationOptions class
86+
"""
87+
self.reason_for_cancellation_dropwdown.select_option(value=option)
88+
89+
90+
class ReasonForCancellationOptions(StrEnum):
91+
"""Enum for cancellation reason options"""
92+
93+
PATIENT_REQUESTS_DISCHARGE_FROM_SCREENING = "6008"
94+
PATIENT_UNSUITABLE_RECENTLY_SCREENED = "6007"
95+
PATIENT_UNSUITABLE_CURRENTLY_UNDERGOING_TREATMENT = "6006"
96+
PATIENT_CANCELLED_TO_CONSIDER = "6005"
97+
PATIENT_CANCELLED_MOVED_OUT_OF_AREA = "6003"
98+
SCREENING_CENTRE_CANCELLED_OTHER_REASON = "6002"
99+
CLINIC_UNAVAILABLE = "6001"
100+
PRACTITIONER_UNAVAILABLE = "6000"
101+
PATIENT_CANCELLED_OTHER_REASON = "6004"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
from playwright.sync_api import Page
2+
from pages.base_page import BasePage
3+
4+
5+
class ReopenFOBTScreeningEpisodePage(BasePage):
6+
"""Reopen FOBT Screening Episode Page locators, and methods for interacting with the page."""
7+
8+
def __init__(self, page: Page):
9+
super().__init__(page)
10+
self.page = page
11+
12+
self.reopen_to_book_an_assessment_button = self.page.get_by_role(
13+
"button", name="Reopen to book an assessment"
14+
)
15+
16+
def click_reopen_to_book_an_assessment_button(self) -> None:
17+
"""Click the 'Reopen to book an assessment' button."""
18+
self.safe_accept_dialog(self.reopen_to_book_an_assessment_button)

pages/screening_subject_search/subject_screening_summary_page.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,9 @@ def __init__(self, page: Page):
8383
self.latest_event_status_cell = self.page.locator(
8484
"td.epihdr_label:text('Latest Event Status') + td.epihdr_data"
8585
)
86+
self.reopen_fobt_screening_episode_button = self.page.get_by_role(
87+
"button", name="Reopen FOBT Screening Episode"
88+
)
8689

8790
def wait_for_page_title(self) -> None:
8891
"""Waits for the page to be the Subject Screening Summary"""
@@ -393,6 +396,10 @@ def assert_latest_event_status(self, expected_status: str) -> None:
393396
actual_status == expected_status
394397
), f"[LATEST EVENT STATUS MISMATCH] Expected '{expected_status}', but found '{actual_status}' in UI."
395398

399+
def click_reopen_fobt_screening_episode_button(self) -> None:
400+
"""Click on the 'Reopen FOBT Screening Episode' button"""
401+
self.click(self.reopen_fobt_screening_episode_button)
402+
396403

397404
class ChangeScreeningStatusOptions(Enum):
398405
"""Enum for Change Screening Status options."""

tests/regression/regression_tests/fobt_regression_tests/test_scenario_1.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,12 @@ def test_scenario_1(page: Page) -> None:
5252
"age (y/d)": "65/25",
5353
"active gp practice in hub/sc": "BCS01/BCS001",
5454
}
55-
nhs_no = CreateSubjectSteps().create_custom_subject(requirements, user_role)
55+
nhs_no = CreateSubjectSteps().create_custom_subject(requirements)
5656
if nhs_no is None:
5757
raise ValueError("NHS No is 'None'")
5858

5959
# Then Comment: NHS number
60-
logging.info(f"Created subject's NHS number: {nhs_no}")
60+
logging.info(f"[SUBJECT CREATION] Created subject's NHS number: {nhs_no}")
6161

6262
# Then my subject has been updated as follows:
6363
criteria = {

0 commit comments

Comments
 (0)