mbu-dev-shared-components 3.0.1__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.
Files changed (59) hide show
  1. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/PKG-INFO +1 -1
  2. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/msoffice365/sharepoint_api/files.py +15 -8
  3. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/document.py +218 -97
  4. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components.egg-info/PKG-INFO +1 -1
  5. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/pyproject.toml +1 -1
  6. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/LICENSE +0 -0
  7. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/README.md +0 -0
  8. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/database/__init__.py +0 -0
  9. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/database/connection.py +0 -0
  10. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/database/constants.py +0 -0
  11. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/database/logging.py +0 -0
  12. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/database/utility.py +0 -0
  13. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/getorganized/__init__.py +0 -0
  14. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/getorganized/auth.py +0 -0
  15. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/getorganized/cases.py +0 -0
  16. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/getorganized/contacts.py +0 -0
  17. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/getorganized/documents.py +0 -0
  18. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/getorganized/objects.py +0 -0
  19. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/google/__init__.py +0 -0
  20. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/google/api/__init__.py +0 -0
  21. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/google/api/auth.py +0 -0
  22. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/google/workspace/__init__.py +0 -0
  23. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/google/workspace/alerts.py +0 -0
  24. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/msoffice365/__init__.py +0 -0
  25. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/msoffice365/excel/__init__.py +0 -0
  26. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/msoffice365/excel/excel_reader.py +0 -0
  27. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/msoffice365/sharepoint_api/__init__.py +0 -0
  28. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/os2forms/__init__.py +0 -0
  29. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/os2forms/documents.py +0 -0
  30. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/os2forms/forms.py +0 -0
  31. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/romexis/__init__.py +0 -0
  32. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/romexis/db_handler.py +0 -0
  33. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/romexis/helper_functions.py +0 -0
  34. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/sap/__init__.py +0 -0
  35. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/sap/create_invoice.py +0 -0
  36. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/__init__.py +0 -0
  37. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/__init__.py +0 -0
  38. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/app_handler.py +0 -0
  39. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/appointment.py +0 -0
  40. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/base_ui.py +0 -0
  41. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/clinic.py +0 -0
  42. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/edi_portal.py +0 -0
  43. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/event.py +0 -0
  44. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/exceptions.py +0 -0
  45. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/handler_base.py +0 -0
  46. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/journal_note.py +0 -0
  47. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/application/patient.py +0 -0
  48. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/database/__init__.py +0 -0
  49. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/solteqtand/database/db_handler.py +0 -0
  50. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/utils/__init__.py +0 -0
  51. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/utils/db_stored_procedure_executor.py +0 -0
  52. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/utils/fernet_encryptor.py +0 -0
  53. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/utils/file_handler.py +0 -0
  54. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components/utils/json_handler.py +0 -0
  55. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components.egg-info/SOURCES.txt +0 -0
  56. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components.egg-info/dependency_links.txt +0 -0
  57. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components.egg-info/requires.txt +0 -0
  58. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/mbu_dev_shared_components.egg-info/top_level.txt +0 -0
  59. {mbu_dev_shared_components-3.0.1 → mbu_dev_shared_components-3.0.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbu_dev_shared_components
3
- Version: 3.0.1
3
+ Version: 3.0.4
4
4
  Summary: Shared components to use in RPA projects
5
5
  Author-email: MBU <rpa@mbu.aarhus.dk>
6
6
  License-Expression: MIT
@@ -39,7 +39,7 @@ from pathlib import PurePath
39
39
 
40
40
  from io import BytesIO
41
41
 
42
- from typing import Optional, List, Dict, Any
42
+ from typing import Optional, List, Dict, Any, Union
43
43
 
44
44
  from openpyxl.styles import Font, Alignment
45
45
  from openpyxl import load_workbook
@@ -268,16 +268,22 @@ class Sharepoint:
268
268
  folder_name: str = "",
269
269
  excel_file_name: str = "",
270
270
  sheet_name: str = "",
271
- new_row: Dict = None,
271
+ new_rows: Union[Dict, List[Dict]] = None,
272
272
  ) -> None:
273
273
  """
274
- • Appends a row to an existing Excel file.
274
+ • Appends one or more rows to an existing Excel file.
275
275
  • Sorts and formats based on provided parameters.
276
276
  """
277
277
 
278
+ # Ensure new_rows is a list of dicts
279
+ if isinstance(new_rows, dict):
280
+ new_rows = [new_rows]
281
+
282
+ elif not isinstance(new_rows, list) or not all(isinstance(r, dict) for r in new_rows):
283
+ raise TypeError("new_rows must be a dict or a list of dicts.")
284
+
278
285
  # 1. Pull file
279
286
  binary_file = self.fetch_file_using_open_binary(excel_file_name, folder_name)
280
-
281
287
  if binary_file is None:
282
288
  raise FileNotFoundError(f"File '{excel_file_name}' not found in folder '{folder_name}'.")
283
289
 
@@ -306,8 +312,11 @@ class Sharepoint:
306
312
  if all(cell is None for cell in row_values):
307
313
  ws.delete_rows(row_idx)
308
314
 
309
- # 3. Append new row to sheet
310
- ws.append([new_row.get(header.value, "") for header in ws[1]])
315
+ # 3. Append each new row
316
+ headers = [header.value for header in ws[1]]
317
+
318
+ for row_dict in new_rows:
319
+ ws.append([row_dict.get(header, "") for header in headers])
311
320
 
312
321
  # 4. Save and upload
313
322
  temp_stream = BytesIO()
@@ -318,8 +327,6 @@ class Sharepoint:
318
327
 
319
328
  self.upload_file_from_bytes(temp_stream.getvalue(), excel_file_name, folder_name)
320
329
 
321
- print(f"✔ Added row + sorted '{sheet_name}' in '{excel_file_name}'.")
322
-
323
330
  def format_and_sort_excel_file(
324
331
  self,
325
332
  folder_name: str,
@@ -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(self, document_full_path: str = None, document_type: str = None, document_description: str = None):
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("%d-%m-%Y", time.localtime(time.time() + 50 * 365 * 86400))
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
- {'AutomationId': 'DateTimePickerFromDate'},
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
- {'AutomationId': 'DateTimePickerToDate'},
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('controls') if list_bookings else None
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(Name="Print/Flet patienter").GetLegacyIAccessiblePattern().DoDefaultAction()
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
- {'AutomationId': 'FormQueryPrintOrMerge'},
139
- search_depth=3
219
+ {"AutomationId": "FormQueryPrintOrMerge"},
220
+ search_depth=3,
140
221
  )
141
- form_print_merge.RadioButtonControl(AutomationId="RadioButtonMerge").GetLegacyIAccessiblePattern().DoDefaultAction()
142
- form_print_merge.PaneControl(AutomationId="ButtonOK").GetLegacyIAccessiblePattern().DoDefaultAction()
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(auto.PatternId.ExpandCollapsePattern).Expand()
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['templateName']:
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(f"Expected status '{metadata['templateName']}' not found in ComboBox list.")
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 = form_mail_merge.ComboBoxControl(AutomationId="ComboTemplet").GetPattern(auto.PatternId.ValuePattern).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['templateName']:
172
- raise ValueError(f"Failed to set the correct status. Expected '{metadata['templateName']}', but got '{new_value}'.")
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(AutomationId="ButtonMerge").GetLegacyIAccessiblePattern().DoDefaultAction()
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(source_file_path: str, destination_path: str, new_filename: str, temp_filename: str) -> str:
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(destination_path, new_filename + ".pdf")
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("Timeout: Failed to create PDF file from Word document.")
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("Failed to create PDF file from Word document.")
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['destinationPath'],
217
- new_filename=metadata['dischargeDocumentFilename'],
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
- def kill_process_by_name(process_name):
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['documentTitle']
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(date_text_created, "%d-%m-%Y %H:%M")
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(f"Discharge document '{target_filename}' not found in the document list.")
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("Send to digitalpost date is not None, which is unexpected.")
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(Name="Send til digital postkasse").GetLegacyIAccessiblePattern().DoDefaultAction()
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
- {'AutomationId': 'ToolContextWrapperUI'},
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 = digital_message_window_group.PaneControl(AutomationId="panel2").ListControl(AutomationId="listBoxAttachment").ListItemControl(Name=metadata['documentTitle'])
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(f"Discharge document '{metadata['documentTitle']}' not found in the attachment list.")
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
- {'Name': 'Kan ikke sende Digital Post uden modtager.'},
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 = print_journal_window.CheckBoxControl(AutomationId="datacardCheckbox").GetPattern(auto.PatternId.TogglePattern).ToggleState
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(AutomationId="datacardCheckbox").GetPattern(auto.PatternId.TogglePattern).Toggle()
506
+ print_journal_window.CheckBoxControl(
507
+ AutomationId="datacardCheckbox"
508
+ ).GetPattern(auto.PatternId.TogglePattern).Toggle()
390
509
 
391
- print_journal_window.PaneControl(AutomationId="printButton").Click(simulateMove=False, waitTime=0)
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
- {'ClassName': 'AcrobatSDIWindow'},
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()
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbu_dev_shared_components
3
- Version: 3.0.1
3
+ Version: 3.0.4
4
4
  Summary: Shared components to use in RPA projects
5
5
  Author-email: MBU <rpa@mbu.aarhus.dk>
6
6
  License-Expression: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "mbu_dev_shared_components"
7
- version = "3.0.1" # Specify the version manually here
7
+ version = "3.0.4"
8
8
  authors = [
9
9
  { name="MBU", email="rpa@mbu.aarhus.dk" },
10
10
  ]