mbu-dev-shared-components 3.0.3__tar.gz → 3.0.4__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-3.0.3 → mbu_dev_shared_components-3.0.4}/PKG-INFO +1 -1
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/document.py +218 -97
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components.egg-info/PKG-INFO +1 -1
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/pyproject.toml +1 -1
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/LICENSE +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/README.md +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/database/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/database/connection.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/database/constants.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/database/logging.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/database/utility.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/getorganized/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/getorganized/auth.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/getorganized/cases.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/getorganized/contacts.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/getorganized/documents.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/getorganized/objects.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/google/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/google/api/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/google/api/auth.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/google/workspace/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/google/workspace/alerts.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/msoffice365/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/msoffice365/excel/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/msoffice365/excel/excel_reader.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/msoffice365/sharepoint_api/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/msoffice365/sharepoint_api/files.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/os2forms/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/os2forms/documents.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/os2forms/forms.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/romexis/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/romexis/db_handler.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/romexis/helper_functions.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/sap/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/sap/create_invoice.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/app_handler.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/appointment.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/base_ui.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/clinic.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/edi_portal.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/event.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/exceptions.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/handler_base.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/journal_note.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/patient.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/database/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/database/db_handler.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/utils/__init__.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/utils/db_stored_procedure_executor.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/utils/fernet_encryptor.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/utils/file_handler.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/utils/json_handler.py +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components.egg-info/SOURCES.txt +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components.egg-info/dependency_links.txt +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components.egg-info/requires.txt +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components.egg-info/top_level.txt +0 -0
- {mbu_dev_shared_components-3.0.3 → mbu_dev_shared_components-3.0.4}/setup.cfg +0 -0
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
"""Base UI-Automation helper methods for SolteqTand project."""
|
|
2
|
+
|
|
2
3
|
import os
|
|
3
4
|
import shutil
|
|
4
5
|
import time
|
|
5
6
|
import psutil
|
|
7
|
+
from psutil import NoSuchProcess, ZombieProcess, AccessDenied
|
|
6
8
|
from docx2pdf import convert
|
|
7
9
|
import uiautomation as auto
|
|
8
10
|
from datetime import datetime
|
|
@@ -17,7 +19,12 @@ class DocumentHandler(HandlerBase):
|
|
|
17
19
|
sending via Digital Post, and creating a digital-printed journal.
|
|
18
20
|
"""
|
|
19
21
|
|
|
20
|
-
def create_document(
|
|
22
|
+
def create_document(
|
|
23
|
+
self,
|
|
24
|
+
document_full_path: str = None,
|
|
25
|
+
document_type: str = None,
|
|
26
|
+
document_description: str = None,
|
|
27
|
+
):
|
|
21
28
|
"""
|
|
22
29
|
Creates a new document under the 'Dokumenter' tab.
|
|
23
30
|
|
|
@@ -31,32 +38,28 @@ class DocumentHandler(HandlerBase):
|
|
|
31
38
|
document_list = self.find_element_by_property(
|
|
32
39
|
control=self.app_window,
|
|
33
40
|
control_type=auto.ControlType.ListControl,
|
|
34
|
-
automation_id="cleverListView1"
|
|
41
|
+
automation_id="cleverListView1",
|
|
35
42
|
)
|
|
36
43
|
document_list.RightClick(simulateMove=False, waitTime=0)
|
|
37
44
|
|
|
38
45
|
document_list_menu = self.wait_for_control(
|
|
39
|
-
auto.MenuControl,
|
|
40
|
-
{'Name': 'Kontekst'},
|
|
41
|
-
search_depth=2
|
|
46
|
+
auto.MenuControl, {"Name": "Kontekst"}, search_depth=2
|
|
42
47
|
)
|
|
43
48
|
|
|
44
49
|
menu_create_document = self.find_element_by_property(
|
|
45
50
|
control=document_list_menu,
|
|
46
51
|
control_type=auto.ControlType.MenuItemControl,
|
|
47
|
-
name="Opret"
|
|
52
|
+
name="Opret",
|
|
48
53
|
)
|
|
49
54
|
menu_create_document.Click(simulateMove=False, waitTime=0)
|
|
50
55
|
|
|
51
56
|
create_document_window = self.wait_for_control(
|
|
52
|
-
auto.WindowControl,
|
|
53
|
-
{'AutomationId': 'UploadFile'},
|
|
54
|
-
search_depth=2
|
|
57
|
+
auto.WindowControl, {"AutomationId": "UploadFile"}, search_depth=2
|
|
55
58
|
)
|
|
56
59
|
file_path_textbox = self.find_element_by_property(
|
|
57
60
|
control=create_document_window,
|
|
58
61
|
control_type=auto.ControlType.EditControl,
|
|
59
|
-
automation_id="textBoxLocalFilePath"
|
|
62
|
+
automation_id="textBoxLocalFilePath",
|
|
60
63
|
)
|
|
61
64
|
legacy_pattern = file_path_textbox.GetLegacyIAccessiblePattern()
|
|
62
65
|
legacy_pattern.SetValue(document_full_path)
|
|
@@ -65,14 +68,14 @@ class DocumentHandler(HandlerBase):
|
|
|
65
68
|
document_type_drop_down = self.find_element_by_property(
|
|
66
69
|
control=create_document_window,
|
|
67
70
|
control_type=auto.ControlType.ButtonControl,
|
|
68
|
-
name="Åbn"
|
|
71
|
+
name="Åbn",
|
|
69
72
|
)
|
|
70
73
|
document_type_drop_down.Click(simulateMove=False, waitTime=0)
|
|
71
74
|
|
|
72
75
|
document_type_button = self.find_element_by_property(
|
|
73
76
|
control=create_document_window,
|
|
74
77
|
control_type=auto.ControlType.ListItemControl,
|
|
75
|
-
name=document_type
|
|
78
|
+
name=document_type,
|
|
76
79
|
)
|
|
77
80
|
document_type_button.Click(simulateMove=False, waitTime=0)
|
|
78
81
|
|
|
@@ -80,18 +83,94 @@ class DocumentHandler(HandlerBase):
|
|
|
80
83
|
description_text_field = self.find_element_by_property(
|
|
81
84
|
control=create_document_window,
|
|
82
85
|
control_type=auto.ControlType.DocumentControl,
|
|
83
|
-
automation_id="richTextBoxDescription"
|
|
86
|
+
automation_id="richTextBoxDescription",
|
|
87
|
+
)
|
|
88
|
+
value_pattern = description_text_field.GetPattern(
|
|
89
|
+
auto.PatternId.ValuePattern
|
|
84
90
|
)
|
|
85
|
-
value_pattern = description_text_field.GetPattern(auto.PatternId.ValuePattern)
|
|
86
91
|
value_pattern.SetValue(document_description)
|
|
87
92
|
|
|
88
93
|
button_create_document = self.find_element_by_property(
|
|
89
94
|
control=create_document_window,
|
|
90
95
|
control_type=auto.ControlType.PaneControl,
|
|
91
|
-
automation_id="buttonOpen"
|
|
96
|
+
automation_id="buttonOpen",
|
|
92
97
|
)
|
|
93
98
|
button_create_document.Click(simulateMove=False, waitTime=0)
|
|
94
99
|
|
|
100
|
+
def kill_process_by_name_safe(self, process_name: str) -> None:
|
|
101
|
+
"""
|
|
102
|
+
Safely kills all processes matching the given name.
|
|
103
|
+
Handles race conditions where processes may exit between enumeration and killing.
|
|
104
|
+
|
|
105
|
+
Args:
|
|
106
|
+
process_name: Name of the process to kill (e.g., 'WINWORD.EXE')
|
|
107
|
+
"""
|
|
108
|
+
print(f"Attempting to kill processes: {process_name}")
|
|
109
|
+
|
|
110
|
+
# Find all matching processes first
|
|
111
|
+
target_processes = []
|
|
112
|
+
for proc in psutil.process_iter(["pid", "name"]):
|
|
113
|
+
try:
|
|
114
|
+
if proc.info["name"] == process_name:
|
|
115
|
+
target_processes.append(proc)
|
|
116
|
+
except (NoSuchProcess, ZombieProcess, AccessDenied):
|
|
117
|
+
# Process already gone or inaccessible, skip it
|
|
118
|
+
continue
|
|
119
|
+
except Exception as e:
|
|
120
|
+
print(f"Error checking process {getattr(proc, 'pid', 'unknown')}: {e}")
|
|
121
|
+
continue
|
|
122
|
+
|
|
123
|
+
if not target_processes:
|
|
124
|
+
print(f"No processes found matching: {process_name}")
|
|
125
|
+
return
|
|
126
|
+
|
|
127
|
+
print(f"Found {len(target_processes)} process(es) matching {process_name}")
|
|
128
|
+
|
|
129
|
+
# First, try graceful termination
|
|
130
|
+
for proc in target_processes:
|
|
131
|
+
try:
|
|
132
|
+
proc.terminate()
|
|
133
|
+
print(f"Terminated {process_name} (PID: {proc.pid})")
|
|
134
|
+
except (NoSuchProcess, ZombieProcess):
|
|
135
|
+
# Process already gone - this is actually what we want
|
|
136
|
+
print(f"Process {process_name} (PID: {proc.pid}) already terminated")
|
|
137
|
+
continue
|
|
138
|
+
except AccessDenied:
|
|
139
|
+
print(f"Access denied terminating {process_name} (PID: {proc.pid})")
|
|
140
|
+
continue
|
|
141
|
+
except Exception as e:
|
|
142
|
+
print(f"Error terminating {process_name} (PID: {proc.pid}): {e}")
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
# Wait for processes to exit gracefully
|
|
146
|
+
try:
|
|
147
|
+
gone, alive = psutil.wait_procs(target_processes, timeout=3)
|
|
148
|
+
|
|
149
|
+
# Log successful terminations
|
|
150
|
+
for proc in gone:
|
|
151
|
+
print(f"{process_name} (PID: {proc.pid}) exited gracefully")
|
|
152
|
+
|
|
153
|
+
# Force kill any remaining processes
|
|
154
|
+
for proc in alive:
|
|
155
|
+
try:
|
|
156
|
+
proc.kill()
|
|
157
|
+
print(f"Force killed {process_name} (PID: {proc.pid})")
|
|
158
|
+
except (NoSuchProcess, ZombieProcess):
|
|
159
|
+
# Process already gone - this is fine
|
|
160
|
+
print(
|
|
161
|
+
f"Process {process_name} (PID: {proc.pid}) already gone during force kill"
|
|
162
|
+
)
|
|
163
|
+
continue
|
|
164
|
+
except AccessDenied:
|
|
165
|
+
print(f"Access denied killing {process_name} (PID: {proc.pid})")
|
|
166
|
+
continue
|
|
167
|
+
except Exception as e:
|
|
168
|
+
print(f"Error force killing {process_name} (PID: {proc.pid}): {e}")
|
|
169
|
+
continue
|
|
170
|
+
|
|
171
|
+
except Exception as e:
|
|
172
|
+
print(f"Error waiting for processes to terminate: {e}")
|
|
173
|
+
|
|
95
174
|
def create_document_from_template(self, metadata: dict) -> None:
|
|
96
175
|
"""
|
|
97
176
|
Under “Print/Flet patienter” → select template → merge → wait for Word to open,
|
|
@@ -103,86 +182,103 @@ class DocumentHandler(HandlerBase):
|
|
|
103
182
|
self.open_tab("Stamkort")
|
|
104
183
|
|
|
105
184
|
from_date = time.strftime("%d-%m-%Y")
|
|
106
|
-
to_date = time.strftime(
|
|
185
|
+
to_date = time.strftime(
|
|
186
|
+
"%d-%m-%Y", time.localtime(time.time() + 50 * 365 * 86400)
|
|
187
|
+
)
|
|
107
188
|
|
|
108
189
|
from_date_field = self.wait_for_control(
|
|
109
190
|
auto.PaneControl,
|
|
110
|
-
{
|
|
111
|
-
search_depth=14
|
|
191
|
+
{"AutomationId": "DateTimePickerFromDate"},
|
|
192
|
+
search_depth=14,
|
|
112
193
|
)
|
|
113
194
|
to_date_field = self.wait_for_control(
|
|
114
195
|
auto.PaneControl,
|
|
115
|
-
{
|
|
116
|
-
search_depth=14
|
|
196
|
+
{"AutomationId": "DateTimePickerToDate"},
|
|
197
|
+
search_depth=14,
|
|
117
198
|
)
|
|
118
199
|
from_date_field.SendKeys(from_date)
|
|
119
200
|
to_date_field.SendKeys(to_date)
|
|
120
201
|
|
|
121
202
|
list_bookings = self.get_list_of_appointments()
|
|
122
203
|
|
|
123
|
-
controls = list_bookings.get(
|
|
204
|
+
controls = list_bookings.get("controls") if list_bookings else None
|
|
124
205
|
if not controls or len(controls) == 0:
|
|
125
206
|
raise ValueError("No appointments found in the list.")
|
|
126
207
|
first_booking = controls[0]
|
|
127
208
|
first_booking.RightClick(simulateMove=False, waitTime=0)
|
|
128
209
|
|
|
129
210
|
pop_up_right_click_menu = self.wait_for_control(
|
|
130
|
-
auto.MenuControl,
|
|
131
|
-
{'Name': 'Kontekst'},
|
|
132
|
-
search_depth=2
|
|
211
|
+
auto.MenuControl, {"Name": "Kontekst"}, search_depth=2
|
|
133
212
|
)
|
|
134
|
-
pop_up_right_click_menu.MenuItemControl(
|
|
213
|
+
pop_up_right_click_menu.MenuItemControl(
|
|
214
|
+
Name="Print/Flet patienter"
|
|
215
|
+
).GetLegacyIAccessiblePattern().DoDefaultAction()
|
|
135
216
|
|
|
136
217
|
form_print_merge = self.wait_for_control(
|
|
137
218
|
auto.WindowControl,
|
|
138
|
-
{
|
|
139
|
-
search_depth=3
|
|
219
|
+
{"AutomationId": "FormQueryPrintOrMerge"},
|
|
220
|
+
search_depth=3,
|
|
140
221
|
)
|
|
141
|
-
form_print_merge.RadioButtonControl(
|
|
142
|
-
|
|
222
|
+
form_print_merge.RadioButtonControl(
|
|
223
|
+
AutomationId="RadioButtonMerge"
|
|
224
|
+
).GetLegacyIAccessiblePattern().DoDefaultAction()
|
|
225
|
+
form_print_merge.PaneControl(
|
|
226
|
+
AutomationId="ButtonOK"
|
|
227
|
+
).GetLegacyIAccessiblePattern().DoDefaultAction()
|
|
143
228
|
|
|
144
229
|
form_mail_merge = self.wait_for_control(
|
|
145
|
-
auto.WindowControl,
|
|
146
|
-
{'AutomationId': 'FormMailMerge'},
|
|
147
|
-
search_depth=3
|
|
230
|
+
auto.WindowControl, {"AutomationId": "FormMailMerge"}, search_depth=3
|
|
148
231
|
)
|
|
149
|
-
form_mail_merge.ComboBoxControl(AutomationId="ComboTemplet").GetPattern(
|
|
232
|
+
form_mail_merge.ComboBoxControl(AutomationId="ComboTemplet").GetPattern(
|
|
233
|
+
auto.PatternId.ExpandCollapsePattern
|
|
234
|
+
).Expand()
|
|
150
235
|
|
|
151
236
|
form_mail_merge_expanded = self.wait_for_control(
|
|
152
|
-
auto.ListControl,
|
|
153
|
-
{'ClassName': 'ComboLBox'},
|
|
154
|
-
search_depth=3
|
|
237
|
+
auto.ListControl, {"ClassName": "ComboLBox"}, search_depth=3
|
|
155
238
|
)
|
|
156
239
|
|
|
157
240
|
selection_made = False
|
|
158
241
|
for item in form_mail_merge_expanded.GetChildren():
|
|
159
|
-
if item.Name == metadata[
|
|
242
|
+
if item.Name == metadata["templateName"]:
|
|
160
243
|
print(f"Selecting '{metadata['templateName']}'")
|
|
161
244
|
item.Click(simulateMove=False, waitTime=0)
|
|
162
245
|
selection_made = True
|
|
163
246
|
break
|
|
164
247
|
|
|
165
248
|
if not selection_made:
|
|
166
|
-
raise ValueError(
|
|
249
|
+
raise ValueError(
|
|
250
|
+
f"Expected status '{metadata['templateName']}' not found in ComboBox list."
|
|
251
|
+
)
|
|
167
252
|
|
|
168
253
|
time.sleep(0.5)
|
|
169
|
-
new_value =
|
|
254
|
+
new_value = (
|
|
255
|
+
form_mail_merge.ComboBoxControl(AutomationId="ComboTemplet")
|
|
256
|
+
.GetPattern(auto.PatternId.ValuePattern)
|
|
257
|
+
.Value
|
|
258
|
+
)
|
|
170
259
|
print(f"New selected status: '{new_value}'")
|
|
171
|
-
if new_value != metadata[
|
|
172
|
-
raise ValueError(
|
|
260
|
+
if new_value != metadata["templateName"]:
|
|
261
|
+
raise ValueError(
|
|
262
|
+
f"Failed to set the correct status. Expected '{metadata['templateName']}', but got '{new_value}'."
|
|
263
|
+
)
|
|
173
264
|
|
|
174
265
|
if os.path.exists(folder_path):
|
|
175
266
|
shutil.rmtree(folder_path, ignore_errors=True)
|
|
176
267
|
|
|
177
|
-
form_mail_merge.PaneControl(
|
|
268
|
+
form_mail_merge.PaneControl(
|
|
269
|
+
AutomationId="ButtonMerge"
|
|
270
|
+
).GetLegacyIAccessiblePattern().DoDefaultAction()
|
|
178
271
|
|
|
179
272
|
word_window = self.wait_for_control(
|
|
180
|
-
auto.WindowControl,
|
|
181
|
-
{'ClassName': 'OpusApp'},
|
|
182
|
-
search_depth=2
|
|
273
|
+
auto.WindowControl, {"ClassName": "OpusApp"}, search_depth=2
|
|
183
274
|
)
|
|
184
275
|
|
|
185
|
-
def convert_docx_to_pdf(
|
|
276
|
+
def convert_docx_to_pdf(
|
|
277
|
+
source_file_path: str,
|
|
278
|
+
destination_path: str,
|
|
279
|
+
new_filename: str,
|
|
280
|
+
temp_filename: str,
|
|
281
|
+
) -> str:
|
|
186
282
|
"""
|
|
187
283
|
Converts a DOCX file to a PDF file.
|
|
188
284
|
|
|
@@ -193,17 +289,23 @@ class DocumentHandler(HandlerBase):
|
|
|
193
289
|
"""
|
|
194
290
|
try:
|
|
195
291
|
source_file_path = os.path.join(folder_path, temp_filename)
|
|
196
|
-
destination_file_path = os.path.join(
|
|
292
|
+
destination_file_path = os.path.join(
|
|
293
|
+
destination_path, new_filename + ".pdf"
|
|
294
|
+
)
|
|
197
295
|
print(f"{source_file_path=} -> {destination_file_path=}")
|
|
198
296
|
convert(source_file_path, destination_file_path)
|
|
199
297
|
timeout = 30
|
|
200
298
|
start_time = time.time()
|
|
201
299
|
while not os.path.exists(destination_file_path):
|
|
202
300
|
if time.time() - start_time > timeout:
|
|
203
|
-
raise TimeoutError(
|
|
301
|
+
raise TimeoutError(
|
|
302
|
+
"Timeout: Failed to create PDF file from Word document."
|
|
303
|
+
)
|
|
204
304
|
time.sleep(1)
|
|
205
305
|
if not os.path.exists(destination_file_path):
|
|
206
|
-
raise FileNotFoundError(
|
|
306
|
+
raise FileNotFoundError(
|
|
307
|
+
"Failed to create PDF file from Word document."
|
|
308
|
+
)
|
|
207
309
|
else:
|
|
208
310
|
print(f"PDF file created successfully: {destination_file_path}")
|
|
209
311
|
return destination_file_path
|
|
@@ -213,18 +315,12 @@ class DocumentHandler(HandlerBase):
|
|
|
213
315
|
|
|
214
316
|
path_to_converted_file = convert_docx_to_pdf(
|
|
215
317
|
source_file_path=folder_path,
|
|
216
|
-
destination_path=metadata[
|
|
217
|
-
new_filename=metadata[
|
|
218
|
-
temp_filename=word_window.Name.split(" - ")[0]
|
|
318
|
+
destination_path=metadata["destinationPath"],
|
|
319
|
+
new_filename=metadata["dischargeDocumentFilename"],
|
|
320
|
+
temp_filename=word_window.Name.split(" - ")[0],
|
|
219
321
|
)
|
|
220
322
|
|
|
221
|
-
|
|
222
|
-
for proc in psutil.process_iter(attrs=['pid', 'name']):
|
|
223
|
-
if proc.info['name'] == process_name:
|
|
224
|
-
proc.kill()
|
|
225
|
-
print(f"Killed process: {process_name} (PID: {proc.info['pid']})")
|
|
226
|
-
|
|
227
|
-
kill_process_by_name("WINWORD.EXE")
|
|
323
|
+
self.kill_process_by_name_safe("WINWORD.EXE")
|
|
228
324
|
self.open_sub_tab("Dokumenter")
|
|
229
325
|
self.create_document(document_full_path=path_to_converted_file)
|
|
230
326
|
except Exception as e:
|
|
@@ -245,13 +341,19 @@ class DocumentHandler(HandlerBase):
|
|
|
245
341
|
document_store = self.wait_for_control(
|
|
246
342
|
self.app_window.PaneControl,
|
|
247
343
|
{"AutomationId": "TabPagePatientDocumentStore"},
|
|
248
|
-
search_depth=7
|
|
344
|
+
search_depth=7,
|
|
345
|
+
)
|
|
346
|
+
list_control = (
|
|
347
|
+
document_store.PaneControl(
|
|
348
|
+
AutomationId="ControlPatientRecordDocumentStore1"
|
|
349
|
+
)
|
|
350
|
+
.PaneControl(AutomationId="ctrlDocumentStore")
|
|
351
|
+
.ListControl(AutomationId="cleverListView1")
|
|
249
352
|
)
|
|
250
|
-
list_control = document_store.PaneControl(AutomationId="ControlPatientRecordDocumentStore1").PaneControl(AutomationId="ctrlDocumentStore").ListControl(AutomationId="cleverListView1")
|
|
251
353
|
for item in list_control.GetChildren():
|
|
252
354
|
print(f"Item: {item}, AutomationId: {item.AutomationId}")
|
|
253
355
|
|
|
254
|
-
target_filename = metadata[
|
|
356
|
+
target_filename = metadata["documentTitle"]
|
|
255
357
|
matching_row = None
|
|
256
358
|
latest_created = None
|
|
257
359
|
|
|
@@ -275,7 +377,9 @@ class DocumentHandler(HandlerBase):
|
|
|
275
377
|
if len(children) >= 4:
|
|
276
378
|
try:
|
|
277
379
|
date_text_created = children[3].Name.strip()
|
|
278
|
-
created_obj = datetime.strptime(
|
|
380
|
+
created_obj = datetime.strptime(
|
|
381
|
+
date_text_created, "%d-%m-%Y %H:%M"
|
|
382
|
+
)
|
|
279
383
|
except Exception as e:
|
|
280
384
|
print(f"Error parsing date: {e}")
|
|
281
385
|
created_obj = None
|
|
@@ -288,7 +392,9 @@ class DocumentHandler(HandlerBase):
|
|
|
288
392
|
matching_row = item
|
|
289
393
|
|
|
290
394
|
if matching_row is None:
|
|
291
|
-
raise ValueError(
|
|
395
|
+
raise ValueError(
|
|
396
|
+
f"Discharge document '{target_filename}' not found in the document list."
|
|
397
|
+
)
|
|
292
398
|
|
|
293
399
|
children = matching_row.GetChildren()
|
|
294
400
|
child9_date = None
|
|
@@ -302,52 +408,61 @@ class DocumentHandler(HandlerBase):
|
|
|
302
408
|
if child9_date is None:
|
|
303
409
|
matching_row.RightClick(simulateMove=False, waitTime=0)
|
|
304
410
|
else:
|
|
305
|
-
raise ValueError(
|
|
411
|
+
raise ValueError(
|
|
412
|
+
"Send to digitalpost date is not None, which is unexpected."
|
|
413
|
+
)
|
|
306
414
|
|
|
307
415
|
right_click_menu = self.wait_for_control(
|
|
308
|
-
auto.MenuControl,
|
|
309
|
-
{"Name": "Kontekst"},
|
|
310
|
-
search_depth=2
|
|
416
|
+
auto.MenuControl, {"Name": "Kontekst"}, search_depth=2
|
|
311
417
|
)
|
|
312
|
-
right_click_menu.MenuItemControl(
|
|
418
|
+
right_click_menu.MenuItemControl(
|
|
419
|
+
Name="Send til digital postkasse"
|
|
420
|
+
).GetLegacyIAccessiblePattern().DoDefaultAction()
|
|
313
421
|
|
|
314
422
|
digital_message_window = self.wait_for_control(
|
|
315
423
|
auto.WindowControl,
|
|
316
|
-
{
|
|
317
|
-
search_depth=2
|
|
424
|
+
{"AutomationId": "ToolContextWrapperUI"},
|
|
425
|
+
search_depth=2,
|
|
426
|
+
)
|
|
427
|
+
digital_message_window_group = (
|
|
428
|
+
digital_message_window.PaneControl(AutomationId="viewPortPanel")
|
|
429
|
+
.PaneControl(AutomationId="SendNemSMSMessageControl")
|
|
430
|
+
.GroupControl(AutomationId="groupBoxMain")
|
|
431
|
+
)
|
|
432
|
+
digital_message_window_group.EditControl(
|
|
433
|
+
AutomationId="textBoxSubject"
|
|
434
|
+
).GetPattern(auto.PatternId.ValuePattern).SetValue(
|
|
435
|
+
metadata["digitalPostSubject"]
|
|
318
436
|
)
|
|
319
|
-
digital_message_window_group = digital_message_window.PaneControl(AutomationId="viewPortPanel").PaneControl(AutomationId="SendNemSMSMessageControl").GroupControl(AutomationId="groupBoxMain")
|
|
320
|
-
digital_message_window_group.EditControl(AutomationId="textBoxSubject").GetPattern(auto.PatternId.ValuePattern).SetValue(metadata['digitalPostSubject'])
|
|
321
437
|
|
|
322
|
-
is_discharge_document_attachment =
|
|
438
|
+
is_discharge_document_attachment = (
|
|
439
|
+
digital_message_window_group.PaneControl(AutomationId="panel2")
|
|
440
|
+
.ListControl(AutomationId="listBoxAttachment")
|
|
441
|
+
.ListItemControl(Name=metadata["documentTitle"])
|
|
442
|
+
)
|
|
323
443
|
if is_discharge_document_attachment is None:
|
|
324
|
-
raise ValueError(
|
|
444
|
+
raise ValueError(
|
|
445
|
+
f"Discharge document '{metadata['documentTitle']}' not found in the attachment list."
|
|
446
|
+
)
|
|
325
447
|
|
|
326
448
|
self.wait_for_control(
|
|
327
|
-
auto.PaneControl,
|
|
328
|
-
{'AutomationId': '&Send'},
|
|
329
|
-
search_depth=4,
|
|
330
|
-
timeout=5
|
|
449
|
+
auto.PaneControl, {"AutomationId": "&Send"}, search_depth=4, timeout=5
|
|
331
450
|
).Click(simulateMove=False, waitTime=0)
|
|
332
451
|
|
|
333
452
|
try:
|
|
334
453
|
self.wait_for_control(
|
|
335
454
|
auto.TextControl,
|
|
336
|
-
{
|
|
455
|
+
{"Name": "Kan ikke sende Digital Post uden modtager."},
|
|
337
456
|
search_depth=4,
|
|
338
|
-
timeout=5
|
|
457
|
+
timeout=5,
|
|
339
458
|
)
|
|
340
459
|
|
|
341
460
|
self.wait_for_control(
|
|
342
|
-
auto.ButtonControl,
|
|
343
|
-
{'Name': 'OK'},
|
|
344
|
-
search_depth=4
|
|
461
|
+
auto.ButtonControl, {"Name": "OK"}, search_depth=4
|
|
345
462
|
).Click(simulateMove=False, waitTime=0)
|
|
346
463
|
|
|
347
464
|
self.wait_for_control(
|
|
348
|
-
auto.PaneControl,
|
|
349
|
-
{'Name': 'Annuller'},
|
|
350
|
-
search_depth=5
|
|
465
|
+
auto.PaneControl, {"Name": "Annuller"}, search_depth=5
|
|
351
466
|
).Click(simulateMove=False, waitTime=0)
|
|
352
467
|
raise ValueError("Cannot send Digital Post without a recipient.")
|
|
353
468
|
except TimeoutError:
|
|
@@ -368,33 +483,39 @@ class DocumentHandler(HandlerBase):
|
|
|
368
483
|
menu_fil_button = self.find_element_by_property(
|
|
369
484
|
control=self.app_window,
|
|
370
485
|
control_type=auto.ControlType.MenuItemControl,
|
|
371
|
-
name="Fil"
|
|
486
|
+
name="Fil",
|
|
372
487
|
)
|
|
373
488
|
menu_fil_button.Click(simulateMove=False, waitTime=0)
|
|
374
489
|
print_journal_button = self.find_element_by_property(
|
|
375
490
|
control=self.app_window,
|
|
376
491
|
control_type=auto.ControlType.MenuItemControl,
|
|
377
|
-
name="Udskriv journal"
|
|
492
|
+
name="Udskriv journal",
|
|
378
493
|
)
|
|
379
494
|
print_journal_button.Click(simulateMove=False, waitTime=0)
|
|
380
495
|
|
|
381
496
|
print_journal_window = self.wait_for_control(
|
|
382
|
-
auto.WindowControl,
|
|
383
|
-
{'AutomationId': 'JournalPrintForm'},
|
|
384
|
-
search_depth=3
|
|
497
|
+
auto.WindowControl, {"AutomationId": "JournalPrintForm"}, search_depth=3
|
|
385
498
|
)
|
|
386
499
|
|
|
387
|
-
stamkort_toggle_state =
|
|
500
|
+
stamkort_toggle_state = (
|
|
501
|
+
print_journal_window.CheckBoxControl(AutomationId="datacardCheckbox")
|
|
502
|
+
.GetPattern(auto.PatternId.TogglePattern)
|
|
503
|
+
.ToggleState
|
|
504
|
+
)
|
|
388
505
|
if stamkort_toggle_state == 1:
|
|
389
|
-
print_journal_window.CheckBoxControl(
|
|
506
|
+
print_journal_window.CheckBoxControl(
|
|
507
|
+
AutomationId="datacardCheckbox"
|
|
508
|
+
).GetPattern(auto.PatternId.TogglePattern).Toggle()
|
|
390
509
|
|
|
391
|
-
print_journal_window.PaneControl(AutomationId="printButton").Click(
|
|
510
|
+
print_journal_window.PaneControl(AutomationId="printButton").Click(
|
|
511
|
+
simulateMove=False, waitTime=0
|
|
512
|
+
)
|
|
392
513
|
|
|
393
514
|
journal_pdf_window = self.wait_for_control(
|
|
394
515
|
auto.WindowControl,
|
|
395
|
-
{
|
|
516
|
+
{"ClassName": "AcrobatSDIWindow"},
|
|
396
517
|
search_depth=2,
|
|
397
|
-
timeout=60
|
|
518
|
+
timeout=60,
|
|
398
519
|
)
|
|
399
520
|
journal_pdf_window.Name.split(" - ")[0]
|
|
400
521
|
journal_pdf_window.GetWindowPattern().Close()
|
|
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
|
|
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
|