mbu-dev-shared-components 0.0.51.post3__tar.gz → 0.0.52__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/.pylintrc +3 -1
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/PKG-INFO +2 -1
- mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/solteqtand/app_handler.py +336 -0
- mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/solteqtand/db_handler.py +187 -0
- mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/utils/__init__.py +0 -0
- mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/utils/file_handler.py +33 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components.egg-info/PKG-INFO +2 -1
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components.egg-info/SOURCES.txt +4 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components.egg-info/requires.txt +1 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/pyproject.toml +1 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/.github/workflows/pylint.yml +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/.gitignore +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/LICENSE +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/README.md +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/getorganized/__init__.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/getorganized/auth.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/getorganized/cases.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/getorganized/contacts.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/getorganized/documents.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/getorganized/objects.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/google/__init__.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/google/api/__init__.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/google/api/auth.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/google/workspace/__init__.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/google/workspace/alerts.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/msoffice365/__init__.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/msoffice365/excel/__init__.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/msoffice365/excel/excel_reader.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/msoffice365/sharepoint_api/__init__.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/msoffice365/sharepoint_api/files.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/os2forms/__init__.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/os2forms/documents.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/os2forms/forms.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/sap/__init__.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/sap/create_invoice.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3/mbu_dev_shared_components/utils → mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/solteqtand}/__init__.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/utils/db_stored_procedure_executor.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/utils/fernet_encryptor.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/utils/json_handler.py +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components.egg-info/dependency_links.txt +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components.egg-info/top_level.txt +0 -0
- {mbu_dev_shared_components-0.0.51.post3 → mbu_dev_shared_components-0.0.52}/setup.cfg +0 -0
|
@@ -5,4 +5,6 @@ disable =
|
|
|
5
5
|
R0913, # Too many arguments
|
|
6
6
|
R0914, # Too many local variables
|
|
7
7
|
W0718, # Catching too general exception Exception (broad-exception-caught)
|
|
8
|
-
R0903, # Too few public methods
|
|
8
|
+
R0903, # Too few public methods
|
|
9
|
+
R0916, # Too many boolean expressions in if statement
|
|
10
|
+
R0917 # Too many positional arguments
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mbu_dev_shared_components
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.52
|
|
4
4
|
Summary: Shared components to use in RPA projects
|
|
5
5
|
Author-email: MBU <rpa@mbu.aarhus.dk>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -15,6 +15,7 @@ Requires-Dist: requests_ntlm>=1.2.0
|
|
|
15
15
|
Requires-Dist: python-dateutil==2.9.*
|
|
16
16
|
Requires-Dist: cryptography>=43.0.0
|
|
17
17
|
Requires-Dist: office365-rest-python-client
|
|
18
|
+
Requires-Dist: uiautomation
|
|
18
19
|
|
|
19
20
|
# mbu-dev-shared-components
|
|
20
21
|
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module contains the `SolteqTandApp` class, which automates interactions with the SolteqTand application using the UIAutomation library.
|
|
3
|
+
"""
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
import uiautomation as auto
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class SolteqTandApp:
|
|
10
|
+
"""
|
|
11
|
+
A class to automate interactions with the SolteqTand application.
|
|
12
|
+
"""
|
|
13
|
+
def __init__(self, app_path, username, password, ssn):
|
|
14
|
+
"""
|
|
15
|
+
Initializes the SolteqTandApp object.
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
app_path (str): Path to the application.
|
|
19
|
+
username (str): Username for login.
|
|
20
|
+
password (str): Password for login.
|
|
21
|
+
ssn (str): SSN for lookup.
|
|
22
|
+
"""
|
|
23
|
+
self.app_path = app_path
|
|
24
|
+
self.username = username
|
|
25
|
+
self.password = password
|
|
26
|
+
self.ssn = ssn
|
|
27
|
+
self.app_window = None
|
|
28
|
+
|
|
29
|
+
def find_element_by_property(self, control, control_type=None, automation_id=None, name=None, class_name=None) -> auto.Control:
|
|
30
|
+
"""
|
|
31
|
+
Uses GetChildren to traverse through controls and find an element based on the specified properties.
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
control (Control): The root control to search from (e.g., main window or pane).
|
|
35
|
+
control_type (ControlType, optional): ControlType to search for.
|
|
36
|
+
automation_id (str, optional): AutomationId of the target element.
|
|
37
|
+
name (str, optional): Name of the target element.
|
|
38
|
+
class_name (str, optional): ClassName of the target element.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Control: The found element or None if no match is found.
|
|
42
|
+
"""
|
|
43
|
+
children = control.GetChildren()
|
|
44
|
+
|
|
45
|
+
for child in children:
|
|
46
|
+
if (control_type is None or child.ControlType == control_type) and \
|
|
47
|
+
(automation_id is None or child.AutomationId == automation_id) and \
|
|
48
|
+
(name is None or child.Name == name) and \
|
|
49
|
+
(class_name is None or child.ClassName == class_name):
|
|
50
|
+
return child
|
|
51
|
+
|
|
52
|
+
found = self.find_element_by_property(child, control_type, automation_id, name, class_name)
|
|
53
|
+
if found:
|
|
54
|
+
return found
|
|
55
|
+
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
def wait_for_control(self, control_type, search_params, search_depth=1, timeout=30):
|
|
59
|
+
"""
|
|
60
|
+
Waits for a given control type to become available with the specified search parameters.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
control_type: The type of control, e.g., auto.WindowControl, auto.ButtonControl, etc.
|
|
64
|
+
search_params (dict): Search parameters used to identify the control.
|
|
65
|
+
The keys must match the properties used in the control type, e.g., 'AutomationId', 'Name'.
|
|
66
|
+
search_depth (int): How deep to search in the user interface.
|
|
67
|
+
timeout (int): How long to wait, in seconds.
|
|
68
|
+
|
|
69
|
+
Returns:
|
|
70
|
+
Control: The control object if found, otherwise None.
|
|
71
|
+
"""
|
|
72
|
+
end_time = time.time() + timeout
|
|
73
|
+
while time.time() < end_time:
|
|
74
|
+
control = control_type(searchDepth=search_depth, **search_params)
|
|
75
|
+
if control.Exists(0, 0):
|
|
76
|
+
return control
|
|
77
|
+
time.sleep(0.5)
|
|
78
|
+
raise TimeoutError(f"Control with parameters {search_params} was not found within the timeout period.")
|
|
79
|
+
|
|
80
|
+
def start_application(self):
|
|
81
|
+
"""
|
|
82
|
+
Starts the application using the specified path.
|
|
83
|
+
"""
|
|
84
|
+
os.startfile(self.app_path)
|
|
85
|
+
|
|
86
|
+
def login(self):
|
|
87
|
+
"""
|
|
88
|
+
Logs into the application by entering the username and password.
|
|
89
|
+
Checks if the login window is open and ready.
|
|
90
|
+
Checks if the main window is opened and ready.
|
|
91
|
+
"""
|
|
92
|
+
self.app_window = self.wait_for_control(
|
|
93
|
+
auto.WindowControl,
|
|
94
|
+
{'AutomationId': 'FormLogin'},
|
|
95
|
+
search_depth=3,
|
|
96
|
+
timeout=60
|
|
97
|
+
)
|
|
98
|
+
self.app_window.SetFocus()
|
|
99
|
+
|
|
100
|
+
username_box = self.app_window.EditControl(AutomationId="TextLogin")
|
|
101
|
+
username_box.SendKeys(text=self.username)
|
|
102
|
+
|
|
103
|
+
password_box = self.app_window.EditControl(AutomationId="TextPwd")
|
|
104
|
+
password_box.SendKeys(text=self.password)
|
|
105
|
+
|
|
106
|
+
login_button = self.app_window.PaneControl(AutomationId="ButtonLogin")
|
|
107
|
+
login_button.SetFocus()
|
|
108
|
+
login_button.SendKeys('{ENTER}')
|
|
109
|
+
|
|
110
|
+
self.app_window = self.wait_for_control(
|
|
111
|
+
auto.WindowControl,
|
|
112
|
+
{'AutomationId': 'FormFront'},
|
|
113
|
+
search_depth=2,
|
|
114
|
+
timeout=60
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
def open_patient(self):
|
|
118
|
+
"""
|
|
119
|
+
When the main window is open, presses Ctrl + O to open the 'Open Patient' window,
|
|
120
|
+
searches for the SSN, and opens the patient.
|
|
121
|
+
"""
|
|
122
|
+
self.app_window.SetFocus()
|
|
123
|
+
self.app_window.SendKeys('{Ctrl}o', waitTime=0)
|
|
124
|
+
|
|
125
|
+
open_patient_window = self.wait_for_control(
|
|
126
|
+
auto.WindowControl,
|
|
127
|
+
{'AutomationId': 'FormOpenPatient'},
|
|
128
|
+
search_depth=2
|
|
129
|
+
)
|
|
130
|
+
open_patient_window.SetFocus()
|
|
131
|
+
|
|
132
|
+
ssn_input = open_patient_window.EditControl(AutomationId="TextBoxCpr")
|
|
133
|
+
search_button = open_patient_window.PaneControl(AutomationId="ButtonOk")
|
|
134
|
+
|
|
135
|
+
ssn_input.SendKeys(text=self.ssn)
|
|
136
|
+
search_button.SetFocus()
|
|
137
|
+
search_button.SendKeys('{ENTER}')
|
|
138
|
+
|
|
139
|
+
self.app_window = self.wait_for_control(
|
|
140
|
+
auto.WindowControl,
|
|
141
|
+
{'AutomationId': 'FormPatient'}
|
|
142
|
+
)
|
|
143
|
+
self.app_window.Maximize()
|
|
144
|
+
|
|
145
|
+
def open_sub_tab(self, sub_tab_name: str):
|
|
146
|
+
"""
|
|
147
|
+
Opens a specific sub-tab in the patient's main card.
|
|
148
|
+
|
|
149
|
+
Args:
|
|
150
|
+
sub_tab_name (str): The name of the sub-tab to open (e.g., "Dokumenter").
|
|
151
|
+
"""
|
|
152
|
+
sub_tab_button = self.app_window.TabItemControl(Name=sub_tab_name)
|
|
153
|
+
is_sub_tab_selected = sub_tab_button.GetPattern(10010).IsSelected
|
|
154
|
+
|
|
155
|
+
if not is_sub_tab_selected:
|
|
156
|
+
sub_tab_button.SetFocus()
|
|
157
|
+
sub_tab_button.SendKeys('{ENTER}')
|
|
158
|
+
|
|
159
|
+
def open_tab(self, tab_name: str):
|
|
160
|
+
"""
|
|
161
|
+
Opens a specific tab in the patient's main card.
|
|
162
|
+
|
|
163
|
+
Args:
|
|
164
|
+
tab_name (str): The name of the tab to open (e.g., "Frit valg").
|
|
165
|
+
"""
|
|
166
|
+
match tab_name:
|
|
167
|
+
case "Stamkort":
|
|
168
|
+
tab_name_modified = "S&tamkort"
|
|
169
|
+
case "Fritvalg":
|
|
170
|
+
tab_name_modified = "F&ritvalg"
|
|
171
|
+
case "Journal":
|
|
172
|
+
tab_name_modified = "&Journal"
|
|
173
|
+
|
|
174
|
+
tab_button = self.find_element_by_property(
|
|
175
|
+
control=self.app_window,
|
|
176
|
+
control_type=auto.ControlType.TabItemControl,
|
|
177
|
+
name=tab_name_modified
|
|
178
|
+
)
|
|
179
|
+
is_tab_selected = tab_button.GetPattern(10010).IsSelected
|
|
180
|
+
|
|
181
|
+
if not is_tab_selected:
|
|
182
|
+
tab_button.SetFocus()
|
|
183
|
+
tab_button.SendKeys('{ENTER}')
|
|
184
|
+
|
|
185
|
+
def create_document(self, document_full_path: str = None, document_type: str = None):
|
|
186
|
+
"""
|
|
187
|
+
Creates a new document under the 'Dokumenter' tab.
|
|
188
|
+
|
|
189
|
+
Args:
|
|
190
|
+
document_full_path (str, optional): The full path of the document to upload.
|
|
191
|
+
document_type (str, optional): The type of document to select from the dropdown.
|
|
192
|
+
"""
|
|
193
|
+
self.open_tab("Stamkort")
|
|
194
|
+
self.open_sub_tab("Dokumenter")
|
|
195
|
+
|
|
196
|
+
document_list = self.find_element_by_property(
|
|
197
|
+
control=self.app_window,
|
|
198
|
+
control_type=auto.ControlType.ListControl,
|
|
199
|
+
automation_id="cleverListView1"
|
|
200
|
+
)
|
|
201
|
+
|
|
202
|
+
list_items = self.find_element_by_property(
|
|
203
|
+
control=document_list,
|
|
204
|
+
control_type=auto.ControlType.ListItemControl
|
|
205
|
+
)
|
|
206
|
+
list_items.RightClick(simulateMove=False, waitTime=0)
|
|
207
|
+
|
|
208
|
+
document_list_menu = self.wait_for_control(
|
|
209
|
+
auto.MenuControl,
|
|
210
|
+
{'Name': 'Kontekst'},
|
|
211
|
+
search_depth=2
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
menu_create_document = self.find_element_by_property(
|
|
215
|
+
control=document_list_menu,
|
|
216
|
+
control_type=auto.ControlType.MenuItemControl,
|
|
217
|
+
name="Opret"
|
|
218
|
+
)
|
|
219
|
+
menu_create_document.Click(simulateMove=False, waitTime=0)
|
|
220
|
+
|
|
221
|
+
create_document_window = self.wait_for_control(
|
|
222
|
+
auto.WindowControl,
|
|
223
|
+
{'AutomationId': 'UploadFile'},
|
|
224
|
+
search_depth=2
|
|
225
|
+
)
|
|
226
|
+
file_path_textbox = self.find_element_by_property(
|
|
227
|
+
control=create_document_window,
|
|
228
|
+
control_type=auto.ControlType.EditControl,
|
|
229
|
+
automation_id="textBoxLocalFilePath"
|
|
230
|
+
)
|
|
231
|
+
legacy_pattern = file_path_textbox.GetLegacyIAccessiblePattern()
|
|
232
|
+
legacy_pattern.SetValue(document_full_path)
|
|
233
|
+
|
|
234
|
+
if document_type:
|
|
235
|
+
document_type_drop_down = self.find_element_by_property(
|
|
236
|
+
control=create_document_window,
|
|
237
|
+
control_type=auto.ControlType.ButtonControl,
|
|
238
|
+
name="Åbn"
|
|
239
|
+
)
|
|
240
|
+
document_type_drop_down.Click(simulateMove=False, waitTime=0)
|
|
241
|
+
|
|
242
|
+
document_type_button = self.find_element_by_property(
|
|
243
|
+
control=create_document_window,
|
|
244
|
+
control_type=auto.ControlType.ListItemControl,
|
|
245
|
+
name=document_type
|
|
246
|
+
)
|
|
247
|
+
document_type_button.Click(simulateMove=False, waitTime=0)
|
|
248
|
+
|
|
249
|
+
button_create_document = self.find_element_by_property(
|
|
250
|
+
control=create_document_window,
|
|
251
|
+
control_type=auto.ControlType.PaneControl,
|
|
252
|
+
automation_id="buttonOpen"
|
|
253
|
+
)
|
|
254
|
+
button_create_document.Click(simulateMove=False, waitTime=0)
|
|
255
|
+
|
|
256
|
+
def create_event(self, event_message: str, patient_clinic: str):
|
|
257
|
+
"""
|
|
258
|
+
Creates an event for the given patient.
|
|
259
|
+
|
|
260
|
+
Args:
|
|
261
|
+
event_title (str): The title of the event to create.
|
|
262
|
+
patient_clinic (str): The clinic associated with the patient.
|
|
263
|
+
"""
|
|
264
|
+
self.open_tab("Stamkort")
|
|
265
|
+
|
|
266
|
+
menu_funktioner = self.app_window.MenuItemControl(Name="Funktioner")
|
|
267
|
+
menu_funktioner.Click(simulateMove=False, waitTime=0)
|
|
268
|
+
|
|
269
|
+
henvis_patient = self.app_window.Control(
|
|
270
|
+
Name="Henvis patient",
|
|
271
|
+
ControlType=auto.ControlType.MenuItemControl
|
|
272
|
+
)
|
|
273
|
+
henvis_patient.Click(simulateMove=False, waitTime=0)
|
|
274
|
+
|
|
275
|
+
clinic_list = self.wait_for_control(
|
|
276
|
+
auto.WindowControl,
|
|
277
|
+
{"AutomationId": "FormFindClinics"},
|
|
278
|
+
search_depth=2
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
clinic_list_items = clinic_list.ListControl(AutomationId="ListClinics")
|
|
282
|
+
clinic_list_item = clinic_list_items.Control(
|
|
283
|
+
Name=patient_clinic,
|
|
284
|
+
ControlType=auto.ControlType.ListItemControl
|
|
285
|
+
)
|
|
286
|
+
clinic_list_item.GetPattern(10017).ScrollIntoView()
|
|
287
|
+
clinic_list_item.SetFocus()
|
|
288
|
+
clinic_list_item.DoubleClick(simulateMove=False, waitTime=0)
|
|
289
|
+
|
|
290
|
+
message_window = self.wait_for_control(
|
|
291
|
+
auto.WindowControl,
|
|
292
|
+
{"AutomationId": "VBInputBox"},
|
|
293
|
+
search_depth=2
|
|
294
|
+
)
|
|
295
|
+
message_textbox = message_window.EditControl(AutmationId="TextBox")
|
|
296
|
+
message_textbox_legacy_pattern = message_textbox.GetLegacyIAccessiblePattern()
|
|
297
|
+
message_textbox_legacy_pattern.SetValue(event_message)
|
|
298
|
+
message_textbox.SendKeys('{ENTER}')
|
|
299
|
+
|
|
300
|
+
self.wait_for_control(
|
|
301
|
+
self.app_window.TextControl,
|
|
302
|
+
{'RegexName': '^Henvisning.*$'},
|
|
303
|
+
search_depth=2
|
|
304
|
+
)
|
|
305
|
+
|
|
306
|
+
message_button = self.app_window.ButtonControl(Name="OK")
|
|
307
|
+
message_button.Click(simulateMove=False, waitTime=0)
|
|
308
|
+
|
|
309
|
+
def create_journal_note(self, note_message: str, checkmark_in_complete: bool):
|
|
310
|
+
"""
|
|
311
|
+
Creates a journal note for the given patient.
|
|
312
|
+
|
|
313
|
+
Args:
|
|
314
|
+
note_message (str): The note message.
|
|
315
|
+
checkmark_in_complete (bool): Checks the checkmark in 'Afslut'.
|
|
316
|
+
"""
|
|
317
|
+
self.open_tab("Journal")
|
|
318
|
+
|
|
319
|
+
self.wait_for_control(
|
|
320
|
+
auto.DocumentControl,
|
|
321
|
+
{"AutomationId": "RichTextBoxInput"},
|
|
322
|
+
search_depth=19
|
|
323
|
+
)
|
|
324
|
+
|
|
325
|
+
input_box = self.app_window.DocumentControl(AutomationId="RichTextBoxInput")
|
|
326
|
+
input_box_value_pattern = input_box.GetValuePattern()
|
|
327
|
+
input_box_value_pattern.SetValue(value=note_message, waitTime=0)
|
|
328
|
+
|
|
329
|
+
if checkmark_in_complete:
|
|
330
|
+
checkbox = self.app_window.CheckBoxControl(AutomationId="CheckBoxAssignCompletionStatus")
|
|
331
|
+
checkbox.SetFocus()
|
|
332
|
+
checkbox.Click(simulateMove=False, waitTime=0)
|
|
333
|
+
|
|
334
|
+
save_button = self.app_window.PaneControl(AutomationId="buttonSave")
|
|
335
|
+
save_button.SetFocus()
|
|
336
|
+
save_button.Click(simulateMove=False, waitTime=0)
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module defines the SolteqTandDatabase class, which provides
|
|
3
|
+
an interface to interact with the Solteq Tand database.
|
|
4
|
+
"""
|
|
5
|
+
import pyodbc
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class SolteqTandDatabase:
|
|
9
|
+
"""Handles database operations related to the Solteq Tand system."""
|
|
10
|
+
|
|
11
|
+
def __init__(self, conn_str: str, ssn: str):
|
|
12
|
+
"""
|
|
13
|
+
Initializes the SolteqTandDatabase instance.
|
|
14
|
+
|
|
15
|
+
Args:
|
|
16
|
+
conn_str (str): Connection string to the Solteq Tand database.
|
|
17
|
+
ssn (str): Social Security Number (CPR) for identifying the patient.
|
|
18
|
+
"""
|
|
19
|
+
self.connection_string = conn_str
|
|
20
|
+
self.ssn = ssn
|
|
21
|
+
|
|
22
|
+
def _execute_query(self, query: str, params: tuple):
|
|
23
|
+
"""
|
|
24
|
+
Executes a query with the provided parameters and returns the result.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
query (str): SQL query to execute.
|
|
28
|
+
params (tuple): Parameters to include in the SQL query.
|
|
29
|
+
|
|
30
|
+
Returns:
|
|
31
|
+
list: A list of rows returned by the query, where each row is a dictionary.
|
|
32
|
+
"""
|
|
33
|
+
conn = pyodbc.connect(self.connection_string)
|
|
34
|
+
cursor = conn.cursor()
|
|
35
|
+
cursor.execute(query, params)
|
|
36
|
+
rows = cursor.fetchall()
|
|
37
|
+
columns = [column[0] for column in cursor.description]
|
|
38
|
+
result = [dict(zip(columns, row)) for row in rows]
|
|
39
|
+
return result
|
|
40
|
+
|
|
41
|
+
def check_if_document_exists(self, filename: str):
|
|
42
|
+
"""
|
|
43
|
+
Checks if a document with the given filename exists for the specified patient.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
filename (str): Name of the file to search for.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
list: A list of matching document records.
|
|
50
|
+
"""
|
|
51
|
+
query = """
|
|
52
|
+
WITH LatestActiveDocuments AS (
|
|
53
|
+
SELECT
|
|
54
|
+
ds.DocumentId,
|
|
55
|
+
ds.entityId,
|
|
56
|
+
ds.OriginalFilename,
|
|
57
|
+
ds.UniqueFilename,
|
|
58
|
+
ds.DocumentType,
|
|
59
|
+
ds.DocumentDescription,
|
|
60
|
+
ds.Priviledged,
|
|
61
|
+
ds.ContentType,
|
|
62
|
+
dss.Document_HistoryId,
|
|
63
|
+
dss.DocumentStoreStatusId,
|
|
64
|
+
dss.SentToNemSMS,
|
|
65
|
+
dss.Documented AS [DocumentCreatedDate],
|
|
66
|
+
dss.Decided AS [DocumentLastEditedDate],
|
|
67
|
+
ROW_NUMBER() OVER (
|
|
68
|
+
PARTITION BY ds.DocumentId
|
|
69
|
+
ORDER BY dss.Document_HistoryId DESC
|
|
70
|
+
) AS rn
|
|
71
|
+
FROM [tmtdata_prod].[dbo].[DocumentStore] ds
|
|
72
|
+
JOIN DocumentStoreStatus dss ON ds.DocumentId = dss.DocumentId
|
|
73
|
+
)
|
|
74
|
+
SELECT ds.DocumentId,
|
|
75
|
+
ds.entityId,
|
|
76
|
+
ds.OriginalFilename,
|
|
77
|
+
ds.UniqueFilename,
|
|
78
|
+
ds.DocumentCreatedDate,
|
|
79
|
+
ds.DocumentLastEditedDate,
|
|
80
|
+
ds.SentToNemSMS,
|
|
81
|
+
p.cpr
|
|
82
|
+
FROM [tmtdata_prod].[dbo].[PATIENT] p
|
|
83
|
+
JOIN LatestActiveDocuments ds ON ds.entityId = p.patientId
|
|
84
|
+
WHERE ds.rn = 1
|
|
85
|
+
AND ds.DocumentStoreStatusId = 1
|
|
86
|
+
AND p.cpr = ?
|
|
87
|
+
AND ds.OriginalFilename = ?
|
|
88
|
+
"""
|
|
89
|
+
return self._execute_query(query, (self.ssn, filename))
|
|
90
|
+
|
|
91
|
+
def check_extern_dentist(self):
|
|
92
|
+
"""
|
|
93
|
+
Checks if the patient is associated with an external dentist.
|
|
94
|
+
(This method is a placeholder and needs to be implemented based on specific requirements.)
|
|
95
|
+
"""
|
|
96
|
+
query = """
|
|
97
|
+
SELECT [patientId]
|
|
98
|
+
,[cpr]
|
|
99
|
+
,[privateClinicId]
|
|
100
|
+
,[c.contractorId]
|
|
101
|
+
,[c.isPrimary]
|
|
102
|
+
,[c.name]
|
|
103
|
+
,[c.streetAddress]
|
|
104
|
+
,[c.zip]
|
|
105
|
+
,[c.phoneNumber]
|
|
106
|
+
FROM [tmtdata_prod].[dbo].[PATIENT] p
|
|
107
|
+
JOIN [CLINIC] c on c.clinicId = p.privateClinicId
|
|
108
|
+
WHERE cpr = ?
|
|
109
|
+
"""
|
|
110
|
+
return self._execute_query(query, (self.ssn))
|
|
111
|
+
|
|
112
|
+
def check_if_booking_exists(self):
|
|
113
|
+
"""
|
|
114
|
+
Checks if any booking exists for the specified patient.
|
|
115
|
+
|
|
116
|
+
Returns:
|
|
117
|
+
list: A list of booking records for the patient.
|
|
118
|
+
"""
|
|
119
|
+
query = """
|
|
120
|
+
SELECT b.StartTime,
|
|
121
|
+
b.EndTime,
|
|
122
|
+
b.PatientNotified,
|
|
123
|
+
b.PatientNotifiedVia,
|
|
124
|
+
b.BookingText,
|
|
125
|
+
b.Warnings,
|
|
126
|
+
b.CreatedDateTime,
|
|
127
|
+
b.LastModifiedDateTime,
|
|
128
|
+
bt.Description,
|
|
129
|
+
bt.PrinterFriendlyText
|
|
130
|
+
FROM [tmtdata_prod].[dbo].[BOOKING] b
|
|
131
|
+
JOIN PATIENT p on p.patientId = b.patientId
|
|
132
|
+
JOIN BOOKINGTYPE bt on bt.BookingTypeID = b.BookingTypeID
|
|
133
|
+
WHERE p.cpr = ?
|
|
134
|
+
"""
|
|
135
|
+
return self._execute_query(query, (self.ssn))
|
|
136
|
+
|
|
137
|
+
def check_if_event_exists(self, event_name: str, event_message: str):
|
|
138
|
+
"""
|
|
139
|
+
Checks if a specific event exists for the patient based on the event name and message.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
event_name (str): The name of the event (clinic name).
|
|
143
|
+
event_message (str): The event message or state.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
list: A list of matching event records for the patient.
|
|
147
|
+
"""
|
|
148
|
+
query = """
|
|
149
|
+
SELECT e.[eventId],
|
|
150
|
+
e.[type],
|
|
151
|
+
e.[currentStateText],
|
|
152
|
+
e.[currentStateDate],
|
|
153
|
+
e.[timestamp],
|
|
154
|
+
e.[clinicId],
|
|
155
|
+
c.name,
|
|
156
|
+
e.[entityId],
|
|
157
|
+
e.[eventTriggerDate],
|
|
158
|
+
p.cpr
|
|
159
|
+
FROM [EVENT] e
|
|
160
|
+
JOIN [PATIENT] p ON p.patientId = e.entityId
|
|
161
|
+
JOIN [CLINIC] c ON c.clinicId = e.clinicId
|
|
162
|
+
WHERE p.cpr = ?
|
|
163
|
+
AND c.name = ?
|
|
164
|
+
AND e.currentStateText = ?
|
|
165
|
+
"""
|
|
166
|
+
return self._execute_query(query, (self.ssn, event_name, event_message))
|
|
167
|
+
|
|
168
|
+
def get_primary_dental_clinic(self):
|
|
169
|
+
"""
|
|
170
|
+
Fetches the primary dental clinic details for the specified patient.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
dict: A dictionary containing patient and clinic details.
|
|
174
|
+
"""
|
|
175
|
+
query = """
|
|
176
|
+
SELECT p.cpr,
|
|
177
|
+
p.patientId,
|
|
178
|
+
p.firstName,
|
|
179
|
+
p.lastName,
|
|
180
|
+
p.preferredDentalClinicId,
|
|
181
|
+
p.isPreferredDentalClinicLocked,
|
|
182
|
+
c.name AS preferredDentalClinicName
|
|
183
|
+
FROM [tmtdata_prod].[dbo].[PATIENT] p
|
|
184
|
+
JOIN [CLINIC] c ON c.clinicId = p.preferredDentalClinicId
|
|
185
|
+
WHERE p.cpr = ?
|
|
186
|
+
"""
|
|
187
|
+
return self._execute_query(query, (self.ssn))
|
|
File without changes
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"""Module that inculdes helper functions for file handling."""
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def rename_file(current_path, new_name):
|
|
6
|
+
"""
|
|
7
|
+
Renames a file from the given current path to a new name.
|
|
8
|
+
|
|
9
|
+
Parameters:
|
|
10
|
+
current_path (str): The full path of the file that you want to rename.
|
|
11
|
+
new_name (str): The new name for the file (without the directory path).
|
|
12
|
+
|
|
13
|
+
Returns:
|
|
14
|
+
None
|
|
15
|
+
"""
|
|
16
|
+
if not os.path.isfile(current_path):
|
|
17
|
+
print(f"The file '{current_path}' does not exist.")
|
|
18
|
+
return
|
|
19
|
+
|
|
20
|
+
directory = os.path.dirname(current_path)
|
|
21
|
+
|
|
22
|
+
new_path = os.path.join(directory, new_name)
|
|
23
|
+
|
|
24
|
+
try:
|
|
25
|
+
os.rename(current_path, new_path)
|
|
26
|
+
print(f"File renamed to '{new_name}' successfully.")
|
|
27
|
+
|
|
28
|
+
except FileNotFoundError:
|
|
29
|
+
print(f"Error: The file '{current_path}' does not exist.")
|
|
30
|
+
except PermissionError:
|
|
31
|
+
print(f"Error: You do not have permission to rename '{current_path}'.")
|
|
32
|
+
except OSError as e:
|
|
33
|
+
print(f"OS error occurred: {e}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: mbu_dev_shared_components
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.52
|
|
4
4
|
Summary: Shared components to use in RPA projects
|
|
5
5
|
Author-email: MBU <rpa@mbu.aarhus.dk>
|
|
6
6
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -15,6 +15,7 @@ Requires-Dist: requests_ntlm>=1.2.0
|
|
|
15
15
|
Requires-Dist: python-dateutil==2.9.*
|
|
16
16
|
Requires-Dist: cryptography>=43.0.0
|
|
17
17
|
Requires-Dist: office365-rest-python-client
|
|
18
|
+
Requires-Dist: uiautomation
|
|
18
19
|
|
|
19
20
|
# mbu-dev-shared-components
|
|
20
21
|
|
|
@@ -30,7 +30,11 @@ mbu_dev_shared_components/os2forms/documents.py
|
|
|
30
30
|
mbu_dev_shared_components/os2forms/forms.py
|
|
31
31
|
mbu_dev_shared_components/sap/__init__.py
|
|
32
32
|
mbu_dev_shared_components/sap/create_invoice.py
|
|
33
|
+
mbu_dev_shared_components/solteqtand/__init__.py
|
|
34
|
+
mbu_dev_shared_components/solteqtand/app_handler.py
|
|
35
|
+
mbu_dev_shared_components/solteqtand/db_handler.py
|
|
33
36
|
mbu_dev_shared_components/utils/__init__.py
|
|
34
37
|
mbu_dev_shared_components/utils/db_stored_procedure_executor.py
|
|
35
38
|
mbu_dev_shared_components/utils/fernet_encryptor.py
|
|
39
|
+
mbu_dev_shared_components/utils/file_handler.py
|
|
36
40
|
mbu_dev_shared_components/utils/json_handler.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|