Skip to content

Commit 59ca80e

Browse files
Inital commit - Started migration of scenario 7
1 parent 86513ae commit 59ca80e

File tree

11 files changed

+887
-11
lines changed

11 files changed

+887
-11
lines changed
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
from enum import Enum
2+
from typing import Optional
3+
4+
5+
class DeductionReasonType(Enum):
6+
"""
7+
Enum representing deduction reason types, mapped to valid value IDs, allowed values, and descriptions.
8+
Provides utility methods for lookup by description, allowed value, and valid value ID.
9+
"""
10+
11+
AFL = (307006, "AFL", "AF Enlistment (local)")
12+
AFN = (307007, "AFN", "AF Enlistment (AF)")
13+
CGA = (307008, "CGA", "Gone away")
14+
DEA = (307009, "DEA", "Death")
15+
Death = (307009, "DEA", "Death") # Alias for DEA
16+
DDR = (1, "DDR", "Deducted at Doctor's request")
17+
DIS = (307010, "DIS", "Practice dissolution")
18+
DPR = (2, "DPR", "Deducted at Patient's request")
19+
EMB = (307011, "EMB", "Embarkation")
20+
Embarkation = (307011, "EMB", "Embarkation") # Alias for EMB
21+
FP69 = (17, "FP69", "Deducted as a result of a non-respond to an FP69/")
22+
LDN = (307012, "LDN", "Logical Deletion")
23+
MH = (7, "M/H", "Mental Hospital")
24+
NIT = (307013, "NIT", "Transfer to Northern Ireland")
25+
OR = (10, "O/R", "Other")
26+
OPA = (307014, "OPA", "Address out of practice area")
27+
ORR = (307015, "ORR", "Other Reason")
28+
PAR = (13, "PAR", "Practice advise subject no longer resident")
29+
PSR = (14, "PSR", "Practice advise removal via screening system")
30+
PVR = (15, "PVR", "Practice advise removal via vaccination data")
31+
R = (26, "R", "Deducted as a result of removal to other HA")
32+
RA = (8, "R/A", "Registration/A")
33+
RC = (9, "R/C", "Registration Cancelled")
34+
RU = (27, "R/U", "Deducted as a result of return undelivered")
35+
RDI = (307016, "RDI", "Practice Request - immediate")
36+
RDR = (307017, "RDR", "Practice request")
37+
RFI = (307018, "RFI", "Residential Institute")
38+
RPR = (307019, "RPR", "Patient request")
39+
SD = (6, "S/D", "Service Dependant")
40+
SCT = (307020, "SCT", "Transferred to Scotland")
41+
SDL = (307021, "SDL", "Services Dependant (local)")
42+
SDN = (307022, "SDN", "Services Dependant (SMU)")
43+
SER = (5, "SER", "Services")
44+
TRA = (307023, "TRA", "Temporary resident not returned")
45+
46+
def __init__(
47+
self, valid_value_id: int, allowed_value: str, description: str
48+
) -> None:
49+
self._valid_value_id = valid_value_id
50+
self._allowed_value = allowed_value
51+
self._description = description
52+
53+
@property
54+
def valid_value_id(self) -> int:
55+
"""Returns the valid value ID for the deduction reason type."""
56+
return self._valid_value_id
57+
58+
@property
59+
def allowed_value(self) -> str:
60+
"""Returns the allowed value for the deduction reason type."""
61+
return self._allowed_value
62+
63+
@property
64+
def description(self) -> str:
65+
"""Returns the description for the deduction reason type."""
66+
return self._description
67+
68+
@classmethod
69+
def by_description(cls, description: str) -> Optional["DeductionReasonType"]:
70+
"""
71+
Returns the enum member matching the given description (case-sensitive).
72+
"""
73+
for member in cls:
74+
if member.description == description:
75+
return member
76+
return None
77+
78+
@classmethod
79+
def by_description_case_insensitive(
80+
cls, description: str
81+
) -> Optional["DeductionReasonType"]:
82+
"""
83+
Returns the enum member matching the given description (case-insensitive).
84+
"""
85+
desc_lower = description.lower()
86+
for member in cls:
87+
if member.description.lower() == desc_lower:
88+
return member
89+
return None
90+
91+
@classmethod
92+
def by_deduction_code(cls, code: str) -> Optional["DeductionReasonType"]:
93+
"""
94+
Returns the enum member matching the given allowed value (deduction code).
95+
"""
96+
code_upper = code.upper()
97+
for member in cls:
98+
if member.allowed_value.upper() == code_upper:
99+
return member
100+
return None
101+
102+
@classmethod
103+
def by_valid_value_id(cls, valid_value_id: int) -> Optional["DeductionReasonType"]:
104+
"""
105+
Returns the enum member matching the given valid value ID.
106+
"""
107+
for member in cls:
108+
if member.valid_value_id == valid_value_id:
109+
return member
110+
return None

classes/episode/subject_has_episode.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def by_description(cls, description: str) -> Optional["SubjectHasEpisode"]:
3232
Optional[SubjectHasEpisode]: The matching enum member, or None if not found.
3333
"""
3434
for item in cls:
35-
if item.value == description:
35+
if item.value.lower() == description.lower():
3636
return item
3737
return None
3838

classes/repositories/subject_repository.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import logging
22
from typing import Optional
33
from classes.subject.pi_subject import PISubject
4+
from classes.subject.subject import Subject
5+
from classes.user.user import User
46
from utils.oracle.oracle import OracleDB
7+
from utils.oracle.subject_selection_query_builder import SubjectSelectionQueryBuilder
58

69

710
class SubjectRepository:
@@ -193,3 +196,32 @@ def get_latest_gp_practice_for_subject(self, nhs_number: str) -> Optional[str]:
193196
if df.empty:
194197
return None
195198
return df.iloc[0]["gp_code"]
199+
200+
def get_matching_subject(
201+
self, criteria: dict, subject: Subject, user: User
202+
) -> Subject:
203+
"""
204+
Finds a subject in the DB matching the given criteria.
205+
Args:
206+
criteria (dict): The criteria you want the subject to match
207+
subject (Subject): The subject class object. Can be populated or not, depends on the criteria
208+
user (User): The user class object. Can be populated or not, depends on the criteria
209+
Returns:
210+
Subject: A populated Subject object with a subject that matches the provided criteria
211+
Raises:
212+
ValueError: If no subject matching the criteria was found.
213+
"""
214+
builder = SubjectSelectionQueryBuilder()
215+
query, bind_vars = builder.build_subject_selection_query(
216+
criteria=criteria,
217+
user=user,
218+
subject=subject,
219+
subjects_to_retrieve=1,
220+
)
221+
df = OracleDB().execute_query(query, bind_vars)
222+
try:
223+
df_row = df.iloc[0]
224+
subject = Subject().from_dataframe_row(df_row)
225+
except Exception:
226+
raise ValueError("No subject found matching the given criteria")
227+
return subject

pages/screening_practitioner_appointments/appointment_detail_page.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
from playwright.sync_api import Page, expect
22
from pages.base_page import BasePage
33
from enum import StrEnum
4+
from datetime import datetime
5+
from utils.calendar_picker import CalendarPicker
46

57

68
class AppointmentDetailPage(BasePage):
@@ -87,6 +89,23 @@ def select_reason_for_cancellation_option(self, option: str) -> None:
8789
self.reason_for_cancellation_dropdown.select_option(value=option)
8890

8991

92+
def mark_appointment_as_attended(self, date: datetime) -> None:
93+
"""
94+
Marks an appointment as attended.
95+
Args:
96+
date (datetime): The date the appointment was attended
97+
"""
98+
self.wait_for_attendance_radio(
99+
600000
100+
) # Max of 10 minute wait as appointments need to be set for future times and they are in 10 minute intervals
101+
self.check_attendance_radio()
102+
self.check_attended_check_box()
103+
self.click_calendar_button()
104+
CalendarPicker(self.page).v1_calender_picker(date)
105+
self.click_save_button()
106+
self.verify_text_visible("Record updated")
107+
108+
90109
class ReasonForCancellationOptions(StrEnum):
91110
"""Enum for cancellation reason options"""
92111

pages/screening_subject_search/advance_fobt_screening_episode_page.py

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ def __init__(self, page: Page):
1717
self.calendar_button = self.page.get_by_role("button", name="Calendar")
1818
self.test_type_dropdown = self.page.locator("#UI_EXT_TEST_TYPE_2233")
1919
self.test_type_dropdown_2 = self.page.locator("#UI_EXT_TEST_TYPE_4325")
20-
self.advance_checkbox = self.page.get_by_label("There are some events available which should only be used in exceptional circumstances. If you wish to see them, check this box")
20+
self.advance_checkbox = self.page.get_by_label(
21+
"There are some events available which should only be used in exceptional circumstances. If you wish to see them, check this box"
22+
)
2123
self.invite_for_diagnostic_test_button = self.page.get_by_role(
2224
"button", name="Invite for Diagnostic Test >>"
2325
)
@@ -42,6 +44,16 @@ def __init__(self, page: Page):
4244
self.record_contact_with_patient_button = self.page.get_by_role(
4345
"button", name="Record Contact with Patient"
4446
)
47+
self.amend_diagnosis_date_button = self.page.get_by_role(
48+
"button", name="Amend Diagnosis Date"
49+
)
50+
self.advance_checkbox_v2 = self.page.get_by_role("checkbox")
51+
self.subsequent_assessment_appointment_required_dropdown = (
52+
self.page.get_by_role("combobox")
53+
)
54+
self.subsequent_assessment_appointment_required_button = self.page.get_by_role(
55+
"button", name="Subsequent Assessment Appointment Required"
56+
)
4557

4658
def click_suitable_for_endoscopic_test_button(self) -> None:
4759
"""Click the 'Suitable for Endoscopic Test' button."""
@@ -116,3 +128,25 @@ def click_record_contact_with_patient_button(self) -> None:
116128
def check_advance_checkbox(self) -> None:
117129
"""Selects the 'Advance FOBT' checkbox"""
118130
self.advance_checkbox.check()
131+
132+
def click_amend_diagnosis_date_button(self) -> None:
133+
"""Click the 'Amend Diagnosis Date' button."""
134+
self.advance_checkbox_v2.check()
135+
self.click(self.amend_diagnosis_date_button)
136+
137+
def click_and_select_subsequent_assessment_appointment_required(
138+
self, option: str
139+
) -> None:
140+
"""
141+
Click the 'Subsequent Assessment Appointment Required' button and select an option from the dropdown.
142+
Args:
143+
option (str): The option to select from the dropdown.
144+
Must be one of:
145+
- 'Previous attendance, further assessment required'
146+
- 'Interpreter requirement not identified'
147+
- 'SC interpreter DNA'
148+
"""
149+
self.subsequent_assessment_appointment_required_dropdown.select_option(
150+
label=option
151+
)
152+
self.safe_accept_dialog(self.subsequent_assessment_appointment_required_button)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
from playwright.sync_api import Page
2+
from pages.base_page import BasePage
3+
from datetime import datetime
4+
from utils.calendar_picker import CalendarPicker
5+
6+
7+
class PatientAdvisedOfDiagnosisPage(BasePage):
8+
"""Advance FOBT Screening Episode Page locators, and methods for interacting with the page."""
9+
10+
def __init__(self, page: Page):
11+
super().__init__(page)
12+
self.page = page
13+
# Patient Advised Of Diagnosis - page locators
14+
self.diagnosis_date_calendar_icon = self.page.locator("#diagnosisAdvice span")
15+
self.reason_dropdown = self.page.locator("#reason")
16+
self.save_button = self.page.get_by_role("button", name="Save")
17+
18+
def select_diagnosis_date_and_reason(self, date: datetime, reason: str) -> None:
19+
"""
20+
Selects the diagnosis date and reason, then saves the form.
21+
Args:
22+
date (datetime): The diagnosis date to select.
23+
reason (str): The reason for the diagnosis.
24+
"""
25+
self.click(self.diagnosis_date_calendar_icon)
26+
CalendarPicker(self.page).v2_calendar_picker(date)
27+
self.reason_dropdown.select_option(label=reason)
28+
self.click(self.save_button)

pages/screening_subject_search/reopen_fobt_screening_episode_page.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,21 @@ def __init__(self, page: Page):
1212
self.reopen_to_book_an_assessment_button = self.page.get_by_role(
1313
"button", name="Reopen to book an assessment"
1414
)
15+
self.reopen_episode_for_correction_button = self.page.get_by_role(
16+
"button", name="Reopen episode for correction"
17+
)
18+
self.reopen_due_to_subject_or_patient_decision = self.page.get_by_role(
19+
"button", name="Reopen due to subject or patient decision"
20+
)
1521

1622
def click_reopen_to_book_an_assessment_button(self) -> None:
1723
"""Click the 'Reopen to book an assessment' button."""
1824
self.safe_accept_dialog(self.reopen_to_book_an_assessment_button)
25+
26+
def click_reopen_episode_for_correction_button(self) -> None:
27+
"""Click the 'Reopen episode for correction' button."""
28+
self.safe_accept_dialog(self.reopen_episode_for_correction_button)
29+
30+
def click_reopen_due_to_subject_or_patient_decision(self) -> None:
31+
"""Click the 'Reopen due to subject or patient decision' button."""
32+
self.safe_accept_dialog(self.reopen_due_to_subject_or_patient_decision)

0 commit comments

Comments
 (0)