mbu-dev-shared-components 2.3.0__tar.gz → 2.4.3__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 (61) hide show
  1. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/PKG-INFO +6 -2
  2. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/msoffice365/sharepoint_api/files.py +273 -4
  3. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/application/appointment.py +9 -0
  4. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/application/base_ui.py +22 -6
  5. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/application/document.py +3 -1
  6. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/application/edi_portal.py +17 -3
  7. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/application/patient.py +5 -3
  8. mbu_dev_shared_components-2.4.3/mbu_dev_shared_components/tests/go_tests/go_integration_tests.py +278 -0
  9. mbu_dev_shared_components-2.4.3/mbu_dev_shared_components/tests/go_tests/objects_tests.py +124 -0
  10. mbu_dev_shared_components-2.4.3/mbu_dev_shared_components/tests/msoffice_tests/msoffice_integration_tests.py +188 -0
  11. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components.egg-info/PKG-INFO +6 -2
  12. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components.egg-info/SOURCES.txt +3 -0
  13. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components.egg-info/requires.txt +5 -0
  14. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/pyproject.toml +13 -2
  15. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/LICENSE +0 -0
  16. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/README.md +0 -0
  17. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/database/__init__.py +0 -0
  18. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/database/constants.py +0 -0
  19. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/database/logging.py +0 -0
  20. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/database/utility.py +0 -0
  21. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/getorganized/__init__.py +0 -0
  22. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/getorganized/auth.py +0 -0
  23. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/getorganized/cases.py +0 -0
  24. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/getorganized/contacts.py +0 -0
  25. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/getorganized/documents.py +0 -0
  26. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/getorganized/objects.py +0 -0
  27. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/google/__init__.py +0 -0
  28. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/google/api/__init__.py +0 -0
  29. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/google/api/auth.py +0 -0
  30. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/google/workspace/__init__.py +0 -0
  31. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/google/workspace/alerts.py +0 -0
  32. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/msoffice365/__init__.py +0 -0
  33. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/msoffice365/excel/__init__.py +0 -0
  34. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/msoffice365/excel/excel_reader.py +0 -0
  35. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/msoffice365/sharepoint_api/__init__.py +0 -0
  36. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/os2forms/__init__.py +0 -0
  37. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/os2forms/documents.py +0 -0
  38. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/os2forms/forms.py +0 -0
  39. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/romexis/__init__.py +0 -0
  40. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/romexis/db_handler.py +0 -0
  41. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/romexis/helper_functions.py +0 -0
  42. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/sap/__init__.py +0 -0
  43. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/sap/create_invoice.py +0 -0
  44. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/__init__.py +0 -0
  45. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/application/__init__.py +0 -0
  46. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/application/app_handler.py +0 -0
  47. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/application/clinic.py +0 -0
  48. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/application/event.py +0 -0
  49. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/application/exceptions.py +0 -0
  50. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/application/handler_base.py +0 -0
  51. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/application/journal_note.py +0 -0
  52. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/database/__init__.py +0 -0
  53. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/solteqtand/database/db_handler.py +0 -0
  54. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/utils/__init__.py +0 -0
  55. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/utils/db_stored_procedure_executor.py +0 -0
  56. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/utils/fernet_encryptor.py +0 -0
  57. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/utils/file_handler.py +0 -0
  58. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components/utils/json_handler.py +0 -0
  59. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components.egg-info/dependency_links.txt +0 -0
  60. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/mbu_dev_shared_components.egg-info/top_level.txt +0 -0
  61. {mbu_dev_shared_components-2.3.0 → mbu_dev_shared_components-2.4.3}/setup.cfg +0 -0
@@ -1,10 +1,10 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mbu_dev_shared_components
3
- Version: 2.3.0
3
+ Version: 2.4.3
4
4
  Summary: Shared components to use in RPA projects
5
5
  Author-email: MBU <rpa@mbu.aarhus.dk>
6
+ License-Expression: MIT
6
7
  Classifier: Programming Language :: Python :: 3
7
- Classifier: License :: OSI Approved :: MIT License
8
8
  Classifier: Operating System :: Microsoft :: Windows
9
9
  Requires-Python: >=3.7
10
10
  Description-Content-Type: text/markdown
@@ -19,7 +19,11 @@ Requires-Dist: uiautomation
19
19
  Requires-Dist: pillow
20
20
  Requires-Dist: psutil
21
21
  Requires-Dist: docx2pdf
22
+ Requires-Dist: pandas>=2.2.3
22
23
  Requires-Dist: rawpy
24
+ Provides-Extra: dev
25
+ Requires-Dist: pytest>=7.0; extra == "dev"
26
+ Requires-Dist: pytest-dependency>=0.5.1; extra == "dev"
23
27
  Dynamic: license-file
24
28
 
25
29
  # MBU Dev Shared Components
@@ -29,12 +29,25 @@ Example:
29
29
  sp.download_files("FolderName", "C:\\LocalPath")
30
30
  """
31
31
 
32
- from pathlib import PurePath
33
- from typing import Optional, List
34
32
  import os
33
+
34
+ import math
35
+
36
+ import traceback
37
+
38
+ from pathlib import PurePath
39
+
40
+ from io import BytesIO
41
+
42
+ from typing import Optional, List, Dict, Any
43
+
44
+ from openpyxl.styles import Font, Alignment
45
+ from openpyxl import load_workbook
46
+
47
+ import pandas as pd
48
+
35
49
  from office365.runtime.auth.user_credential import UserCredential
36
50
  from office365.sharepoint.client_context import ClientContext
37
-
38
51
  from office365.sharepoint.files.file import File
39
52
 
40
53
 
@@ -136,7 +149,6 @@ class Sharepoint:
136
149
  file_content = File.open_binary(self.ctx, file_url)
137
150
  return file_content.content
138
151
  except Exception:
139
- import traceback
140
152
  print("Failed to download file:")
141
153
  traceback.print_exc()
142
154
  return None
@@ -249,3 +261,260 @@ class Sharepoint:
249
261
  print(f"File '{file_name}' uploaded successfully to '{folder_url}'.")
250
262
  except Exception as e:
251
263
  print(f"Failed to upload file '{file_name}': {e}")
264
+
265
+ def append_row_to_sharepoint_excel(
266
+ self,
267
+ required_headers: Optional[List[str]] = None,
268
+ folder_name: str = "",
269
+ excel_file_name: str = "",
270
+ sheet_name: str = "",
271
+ new_row: Dict = None,
272
+ ) -> None:
273
+ """
274
+ • Appends a row to an existing Excel file.
275
+ • Sorts and formats based on provided parameters.
276
+ """
277
+
278
+ # 1. Pull file
279
+ binary_file = self.fetch_file_using_open_binary(excel_file_name, folder_name)
280
+
281
+ if binary_file is None:
282
+ raise FileNotFoundError(f"File '{excel_file_name}' not found in folder '{folder_name}'.")
283
+
284
+ wb = load_workbook(BytesIO(binary_file))
285
+
286
+ if sheet_name not in wb.sheetnames:
287
+ raise ValueError(f"Sheet '{sheet_name}' not found in '{excel_file_name}'")
288
+
289
+ ws = wb[sheet_name]
290
+
291
+ # 2. Validate headers
292
+ if required_headers:
293
+ current_headers = [cell.value for cell in ws[1]]
294
+
295
+ if current_headers != required_headers:
296
+ raise ValueError(
297
+ f"Header mismatch in sheet '{sheet_name}'!\n"
298
+ f"Expected: {required_headers}\n"
299
+ f"Found: {current_headers}"
300
+ )
301
+
302
+ # 2.5 Clean up empty rows before appending
303
+ for row_idx in range(ws.max_row, 1, -1): # Start from bottom, skip header
304
+ row_values = [cell.value for cell in ws[row_idx]]
305
+
306
+ if all(cell is None for cell in row_values):
307
+ ws.delete_rows(row_idx)
308
+
309
+ # 3. Append new row to sheet
310
+ ws.append([new_row.get(header.value, "") for header in ws[1]])
311
+
312
+ # 4. Save and upload
313
+ temp_stream = BytesIO()
314
+
315
+ wb.save(temp_stream)
316
+
317
+ temp_stream.seek(0)
318
+
319
+ self.upload_file_from_bytes(temp_stream.getvalue(), excel_file_name, folder_name)
320
+
321
+ print(f"✔ Added row + sorted '{sheet_name}' in '{excel_file_name}'.")
322
+
323
+ def format_and_sort_excel_file(
324
+ self,
325
+ folder_name: str,
326
+ excel_file_name: str,
327
+ sheet_name: str,
328
+ sorting_keys: Optional[List[Dict[str, Any]]] = None,
329
+ font_config: Optional[Dict[int, Dict[str, Any]]] = None,
330
+ bold_rows: Optional[List[int]] = None,
331
+ italic_rows: Optional[List[int]] = None,
332
+ align_horizontal: str = "center",
333
+ align_vertical: str = "center",
334
+ column_widths: Any = "auto",
335
+ freeze_panes: Optional[str] = None,
336
+ ):
337
+ """
338
+ Sorts and formats an Excel worksheet based on provided styling and sorting rules.
339
+
340
+ Params:
341
+ folder_name: Name of the folder where the file resides
342
+ excel_file_name: Name of the excel file
343
+ sheet_name: Name of the sheet that will be sorted
344
+ sorting_keys: List of dicts like [{"key": "A", "ascending": True, "type": "datetime"}]
345
+ bold_rows: List of row numbers to bold (defaults to [1])
346
+ italic_rows: List of row numbers to italicize
347
+ font_config: Dict of row -> font config (overrides bold/italic)
348
+ align_horizontal: Horizontal text alignment
349
+ align_vertical: Vertical text alignment
350
+ column_widths: "auto" or an int to represent a pixel value
351
+ freeze_panes: E.g., "A2" to freeze header row
352
+
353
+ Returns:
354
+ Modified worksheet
355
+ """
356
+
357
+ # Step 1 - Fetch the file to update from SharePoint and load it as a workbook
358
+ # This ensures we don't override any other sheets in the excel file
359
+ binary_file = self.fetch_file_using_open_binary(excel_file_name, folder_name)
360
+ if binary_file is None:
361
+ raise FileNotFoundError(f"File '{excel_file_name}' not found in folder '{folder_name}'.")
362
+
363
+ wb = load_workbook(BytesIO(binary_file))
364
+ if sheet_name not in wb.sheetnames:
365
+ raise ValueError(f"Sheet '{sheet_name}' not found in '{excel_file_name}'")
366
+
367
+ ws = wb[sheet_name]
368
+
369
+ # Step 2 - Read data into DataFrame
370
+ rows = list(ws.iter_rows(values_only=True))
371
+ header, *data_rows = rows
372
+ df = pd.DataFrame(data_rows, columns=header)
373
+
374
+ # Step 3 – Prepare sorting logic
375
+ # For each sorting instruction, we:
376
+ # - Extract the column to sort by (using letter, index, or name)
377
+ # - Convert the column values to the desired data type if specified (str, int, float, datetime)
378
+ # - Track which columns to sort and in which order (ascending or descending)
379
+ #
380
+ # This ensures the DataFrame is sorted correctly, even when types like dates or numbers need conversion.
381
+ if sorting_keys:
382
+ sort_columns = []
383
+ ascending_flags = []
384
+
385
+ for item in sorting_keys:
386
+ key = item.get("key")
387
+ ascending = item.get("ascending", True)
388
+ dtype = item.get("type")
389
+
390
+ if isinstance(key, int):
391
+ col_name = header[key]
392
+
393
+ elif isinstance(key, str) and key.isalpha():
394
+ col_name = header[ord(key.upper()) - ord("A")]
395
+
396
+ else:
397
+ col_name = key
398
+
399
+ sort_columns.append(col_name)
400
+ ascending_flags.append(ascending)
401
+
402
+ if dtype == "datetime":
403
+ df[col_name] = pd.to_datetime(df[col_name], dayfirst=True, errors="coerce")
404
+
405
+ elif dtype == "int":
406
+ df[col_name] = pd.to_numeric(df[col_name], errors="coerce", downcast="integer")
407
+
408
+ elif dtype == "float":
409
+ df[col_name] = pd.to_numeric(df[col_name], errors="coerce", downcast="float")
410
+
411
+ elif dtype == "str":
412
+ df[col_name] = df[col_name].astype(str)
413
+
414
+ # Step 4 – Sort
415
+ df.sort_values(by=sort_columns, ascending=ascending_flags, inplace=True)
416
+
417
+ # Step 5 - Overwrite worksheet
418
+ ws.delete_rows(1, ws.max_row)
419
+
420
+ ws.append(header)
421
+
422
+ for _, row in df.iterrows():
423
+ ws.append(list(row))
424
+
425
+ # Step 6 – Adjust column widths and apply wrapping if needed
426
+ #
427
+ # If column_widths is "auto":
428
+ # - Calculate the max content length in each column and set the column width accordingly (+2 for padding)
429
+ #
430
+ # If column_widths is a single int:
431
+ # - Use it as a global max width across all columns
432
+ # - If content fits, set width based on actual content length
433
+ # - If content exceeds the max width clamp column width and enable wrap_text for that column's cells
434
+ #
435
+ # Then, for wrapped cells, auto-adjust the row height:
436
+ # - Estimate how many lines the wrapped text would occupy and set row height accordingly to ensure all content is visible
437
+ if column_widths in (None, "auto"):
438
+ for col in ws.columns:
439
+ max_len = max(len(str(cell.value or "")) for cell in col)
440
+
441
+ ws.column_dimensions[col[0].column_letter].width = max_len + 2
442
+
443
+ elif isinstance(column_widths, int):
444
+ for col in ws.columns:
445
+ col_letter = col[0].column_letter
446
+
447
+ max_len = max(len(str(cell.value or "")) for cell in col)
448
+
449
+ # If content fits, auto-size
450
+ if max_len + 2 <= column_widths:
451
+ ws.column_dimensions[col_letter].width = max_len + 2
452
+
453
+ # Else, cap width and enable wrap
454
+ else:
455
+ ws.column_dimensions[col_letter].width = column_widths
456
+
457
+ for cell in col:
458
+ cell.alignment = Alignment(wrap_text=True)
459
+
460
+ # Here we handle row height
461
+ for row in ws.iter_rows():
462
+ max_line_count = 1
463
+
464
+ for cell in row:
465
+ if cell.value and cell.alignment and cell.alignment.wrap_text:
466
+ col_letter = cell.column_letter
467
+ col_width = ws.column_dimensions[col_letter].width or 10
468
+ chars_per_line = col_width * 1.2
469
+ lines = str(cell.value).split("\n")
470
+ line_count = sum(math.ceil(len(line) / chars_per_line) for line in lines)
471
+ max_line_count = max(max_line_count, line_count)
472
+
473
+ ws.row_dimensions[row[0].row].height = max_line_count * 20
474
+
475
+ else:
476
+ raise ValueError(f"Column width provided with incorrect datatype - datatype int expected, instead column width is of datatype {type(column_widths)}")
477
+
478
+ # Step 7 - Freeze panes if needed
479
+ if freeze_panes:
480
+ ws.freeze_panes = freeze_panes
481
+
482
+ # Step 8 – Apply base formatting
483
+ # For each cell in the worksheet:
484
+ # - Apply font styling based on either a custom `font_config` (row-specific) or default to bold/italic based on row number (e.g., header rows)
485
+ # - Set horizontal and vertical alignment for consistent layout
486
+ # - Disable text wrapping by default (wrapping will be handled later if needed)
487
+ #
488
+ # This ensures a clean, uniform look across the sheet while allowing for custom styling where defined.
489
+ for row_idx, row in enumerate(ws.iter_rows(), start=1):
490
+ for cell in row:
491
+ if font_config and row_idx in font_config:
492
+ config = font_config[row_idx]
493
+
494
+ cell.font = Font(
495
+ name=config.get("name", "Calibri"),
496
+ size=config.get("size", 11),
497
+ bold=config.get("bold", False),
498
+ italic=config.get("italic", False),
499
+ )
500
+
501
+ else:
502
+ cell.font = Font(
503
+ bold=row_idx in bold_rows if bold_rows else False,
504
+ italic=row_idx in italic_rows if italic_rows else False,
505
+ )
506
+
507
+ cell.alignment = Alignment(
508
+ horizontal=align_horizontal,
509
+ vertical=align_vertical,
510
+ wrap_text=cell.alignment.wrap_text
511
+ )
512
+
513
+ # Step 9 - Save and re-upload
514
+ temp_stream = BytesIO()
515
+
516
+ wb.save(temp_stream)
517
+
518
+ temp_stream.seek(0)
519
+
520
+ self.upload_file_from_bytes(temp_stream.getvalue(), excel_file_name, folder_name)
@@ -276,6 +276,7 @@ class AppointmentHandler(HandlerBase):
276
276
  """
277
277
  try:
278
278
  self.open_tab("Stamkort")
279
+ self.open_sub_tab("Behandlingsstatus")
279
280
 
280
281
  create_booking_button = self.wait_for_control(
281
282
  auto.PaneControl,
@@ -304,6 +305,14 @@ class AppointmentHandler(HandlerBase):
304
305
  case "Stol":
305
306
  child.GetPattern(auto.PatternId.ValuePattern).SetValue(booking_reminder_data["comboBoxChair"])
306
307
 
308
+ # Fill out text booking field
309
+ text_booking_field_group = manage_booking.GroupControl(AutomationId="GroupBox5")
310
+ for child in text_booking_field_group.GetChildren():
311
+ match child.AutomationId:
312
+ case "TextBoxBookingText":
313
+ if child.GetPattern(auto.PatternId.ValuePattern).Value != booking_reminder_data["textBoxBookingText"]:
314
+ child.GetPattern(auto.PatternId.ValuePattern).SetValue(booking_reminder_data["textBoxBookingText"])
315
+
307
316
  # Fill out date and time
308
317
  date_and_time_group = manage_booking.GroupControl(AutomationId="GroupBox4")
309
318
 
@@ -23,10 +23,12 @@ class BaseUI:
23
23
  children = control.GetChildren()
24
24
 
25
25
  for child in children:
26
- if (control_type is None or child.ControlType == control_type) and \
27
- (automation_id is None or child.AutomationId == automation_id) and \
28
- (name is None or child.Name == name) and \
29
- (class_name is None or child.ClassName == class_name):
26
+ if (
27
+ (control_type is None or child.ControlType == control_type) and
28
+ (automation_id is None or child.AutomationId == automation_id) and
29
+ (name is None or child.Name == name) and
30
+ (class_name is None or child.ClassName == class_name)
31
+ ):
30
32
  return child
31
33
 
32
34
  found = self.find_element_by_property(child, control_type, automation_id, name, class_name)
@@ -96,5 +98,19 @@ class BaseUI:
96
98
  raise TimeoutError(f"Control with parameters {search_params} did not disappear within the timeout period.")
97
99
 
98
100
  def close_window(self, window_to_close: auto.WindowControl) -> None:
99
- """Closes specified window by sending CTRL+F4 keystroke."""
100
- window_to_close.SendKeys(text="^({F4})")
101
+ """Closes specified window."""
102
+ window_name = window_to_close.Name
103
+ window_to_close.SetFocus()
104
+ window_to_close.GetWindowPattern().Close()
105
+
106
+ # Handle popup when closin main window
107
+ if window_name.lower().startswith("hovedvindue"):
108
+
109
+ pop_up_window = window_to_close.WindowControl(Name="TMT - Afslut")
110
+ pop_up_window.SetFocus()
111
+ pop_up_window.ButtonControl(Name="Ja").Click(simulateMove=False, waitTime=0)
112
+
113
+ time.sleep(2)
114
+
115
+ else:
116
+ self.app_window = self.wait_for_control(search_params={'AutomationId': 'FormFront'}, control_type=auto.WindowControl)
@@ -5,6 +5,7 @@ import time
5
5
  import psutil
6
6
  from docx2pdf import convert
7
7
  import uiautomation as auto
8
+ from datetime import datetime
8
9
 
9
10
  from .handler_base import HandlerBase
10
11
 
@@ -96,6 +97,8 @@ class DocumentHandler(HandlerBase):
96
97
  Under “Print/Flet patienter” → select template → merge → wait for Word to open,
97
98
  convert to PDF, kill WINWORD.EXE, then create_document() with the new PDF.
98
99
  """
100
+ folder_path = rf"{os.environ.get('USERPROFILE')}\AppData\Local\Temp\Care\TMTand"
101
+
99
102
  try:
100
103
  self.open_tab("Stamkort")
101
104
 
@@ -168,7 +171,6 @@ class DocumentHandler(HandlerBase):
168
171
  if new_value != metadata['templateName']:
169
172
  raise ValueError(f"Failed to set the correct status. Expected '{metadata['templateName']}', but got '{new_value}'.")
170
173
 
171
- folder_path = rf"{os.environ.get('USERPROFILE')}\AppData\Local\Temp\Care\TMTand"
172
174
  shutil.rmtree(folder_path, ignore_errors=True)
173
175
 
174
176
  form_mail_merge.PaneControl(AutomationId="ButtonMerge").GetLegacyIAccessiblePattern().DoDefaultAction()
@@ -28,6 +28,8 @@ class EDIHandler(HandlerBase):
28
28
  )
29
29
  journalforsendelse_button.Click(simulateMove=False, waitTime=0)
30
30
 
31
+ time.sleep(5)
32
+
31
33
  except Exception as e:
32
34
  print(f"Error while opening EDI Portal: {e}")
33
35
 
@@ -70,7 +72,7 @@ class EDIHandler(HandlerBase):
70
72
  if not next_button:
71
73
  try:
72
74
  next_button = self.wait_for_control(
73
- auto.ButtonControl, {"Name": "patientInformationNextButton"},
75
+ auto.ButtonControl, {"AutomationId": "patientInformationNextButton"},
74
76
  search_depth=50,
75
77
  timeout=5
76
78
  )
@@ -97,6 +99,14 @@ class EDIHandler(HandlerBase):
97
99
  dict: A dictionary containing the row count and whether the phone number matches.
98
100
  """
99
101
  try:
102
+ # Handle Hasle Torv Clinic special case
103
+ if extern_clinic_data[0]["contractorId"] == "477052" or extern_clinic_data[0]["contractorId"] == "470678":
104
+ contractor_id = "485055"
105
+ clinic_phone_number = "86135240"
106
+ else:
107
+ contractor_id = extern_clinic_data[0]["contractorId"]
108
+ clinic_phone_number = extern_clinic_data[0]["phoneNumber"]
109
+
100
110
  self.edi_portal_click_next_button(sleep_time=2)
101
111
 
102
112
  class_options = [
@@ -120,7 +130,7 @@ class EDIHandler(HandlerBase):
120
130
  raise RuntimeError("Search box not found")
121
131
  search_box.SetFocus()
122
132
  search_box_value_pattern = search_box.GetPattern(auto.PatternId.ValuePattern)
123
- search_box_value_pattern.SetValue(extern_clinic_data[0]["contractorId"])
133
+ search_box_value_pattern.SetValue(contractor_id)
124
134
  search_box.SendKeys("{ENTER}")
125
135
 
126
136
  time.sleep(sleep_time)
@@ -134,10 +144,14 @@ class EDIHandler(HandlerBase):
134
144
  row_count = grid_pattern.RowCount
135
145
 
136
146
  is_phone_number_match = False
147
+
148
+ if grid_pattern.GetItem(1, 0).Name == "Ingen data i tabellen":
149
+ return {"rowCount": 0, "isPhoneNumberMatch": False}
150
+
137
151
  if row_count > 0:
138
152
  for row in range(row_count):
139
153
  phone_number = grid_pattern.GetItem(row, 4).Name
140
- if phone_number == extern_clinic_data[0]["phoneNumber"]:
154
+ if phone_number == clinic_phone_number:
141
155
  is_phone_number_match = True
142
156
  break
143
157
  return {"rowCount": row_count, "isPhoneNumberMatch": is_phone_number_match}
@@ -112,13 +112,14 @@ class PatientHandler(HandlerBase):
112
112
  TimeoutError: If the patient window does not close within the expected time.
113
113
  """
114
114
 
115
- title_bar_window = self.app_window.TitleBarControl()
116
- title_bar_window.ButtonControl(Name="Luk").Click(simulateMove=False, waitTime=0)
115
+ self.app_window.SetFocus()
116
+ self.app_window.GetWindowPattern().Close()
117
117
 
118
118
  self.app_window = self.wait_for_control_to_disappear(
119
119
  auto.WindowControl,
120
120
  {'AutomationId': 'FormPatient'},
121
- search_depth=2
121
+ search_depth=2,
122
+ timeout=30
122
123
  )
123
124
 
124
125
  self.app_window = self.wait_for_control(
@@ -127,6 +128,7 @@ class PatientHandler(HandlerBase):
127
128
  search_depth=2,
128
129
  timeout=5
129
130
  )
131
+ self.app_window.SetFocus()
130
132
 
131
133
  def change_status(self, status: str) -> None:
132
134
  """