mbu-dev-shared-components 0.0.50__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.
Files changed (42) hide show
  1. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/.pylintrc +3 -1
  2. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/PKG-INFO +3 -3
  3. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/README.md +0 -1
  4. {mbu_dev_shared_components-0.0.50/mbu_dev_shared_components/office365 → mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/msoffice365}/sharepoint_api/files.py +83 -29
  5. mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/solteqtand/app_handler.py +336 -0
  6. mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/solteqtand/db_handler.py +187 -0
  7. mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/utils/__init__.py +0 -0
  8. mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/utils/file_handler.py +33 -0
  9. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components.egg-info/PKG-INFO +3 -3
  10. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components.egg-info/SOURCES.txt +9 -5
  11. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components.egg-info/requires.txt +2 -1
  12. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/pyproject.toml +3 -2
  13. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/.github/workflows/pylint.yml +0 -0
  14. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/.gitignore +0 -0
  15. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/LICENSE +0 -0
  16. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/getorganized/__init__.py +0 -0
  17. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/getorganized/auth.py +0 -0
  18. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/getorganized/cases.py +0 -0
  19. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/getorganized/contacts.py +0 -0
  20. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/getorganized/documents.py +0 -0
  21. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/getorganized/objects.py +0 -0
  22. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/google/__init__.py +0 -0
  23. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/google/api/__init__.py +0 -0
  24. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/google/api/auth.py +0 -0
  25. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/google/workspace/__init__.py +0 -0
  26. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/google/workspace/alerts.py +0 -0
  27. {mbu_dev_shared_components-0.0.50/mbu_dev_shared_components/office365 → mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/msoffice365}/__init__.py +0 -0
  28. {mbu_dev_shared_components-0.0.50/mbu_dev_shared_components/office365 → mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/msoffice365}/excel/__init__.py +0 -0
  29. {mbu_dev_shared_components-0.0.50/mbu_dev_shared_components/office365 → mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/msoffice365}/excel/excel_reader.py +0 -0
  30. {mbu_dev_shared_components-0.0.50/mbu_dev_shared_components/office365 → mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/msoffice365}/sharepoint_api/__init__.py +0 -0
  31. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/os2forms/__init__.py +0 -0
  32. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/os2forms/documents.py +0 -0
  33. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/os2forms/forms.py +0 -0
  34. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/sap/__init__.py +0 -0
  35. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/sap/create_invoice.py +0 -0
  36. {mbu_dev_shared_components-0.0.50/mbu_dev_shared_components/utils → mbu_dev_shared_components-0.0.52/mbu_dev_shared_components/solteqtand}/__init__.py +0 -0
  37. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/utils/db_stored_procedure_executor.py +0 -0
  38. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/utils/fernet_encryptor.py +0 -0
  39. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components/utils/json_handler.py +0 -0
  40. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components.egg-info/dependency_links.txt +0 -0
  41. {mbu_dev_shared_components-0.0.50 → mbu_dev_shared_components-0.0.52}/mbu_dev_shared_components.egg-info/top_level.txt +0 -0
  42. {mbu_dev_shared_components-0.0.50 → 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.50
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
@@ -9,12 +9,13 @@ Classifier: Operating System :: Microsoft :: Windows
9
9
  Requires-Python: >=3.7
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
- Requires-Dist: shareplum
13
12
  Requires-Dist: openpyxl>=3.1.2
14
13
  Requires-Dist: pyodbc>=5.1.0
15
14
  Requires-Dist: requests_ntlm>=1.2.0
16
15
  Requires-Dist: python-dateutil==2.9.*
17
16
  Requires-Dist: cryptography>=43.0.0
17
+ Requires-Dist: office365-rest-python-client
18
+ Requires-Dist: uiautomation
18
19
 
19
20
  # mbu-dev-shared-components
20
21
 
@@ -60,7 +61,6 @@ Additionally, it provides functionalities to count the total number of rows and
60
61
  #### - Invoices
61
62
 
62
63
  This module provides the InvoiceCreator class to create invoices in SAP.
63
-
64
64
  The InvoiceCreator class offers methods to open a specified business partner, and creat an invoice.
65
65
 
66
66
  - Open business partner
@@ -42,7 +42,6 @@ Additionally, it provides functionalities to count the total number of rows and
42
42
  #### - Invoices
43
43
 
44
44
  This module provides the InvoiceCreator class to create invoices in SAP.
45
-
46
45
  The InvoiceCreator class offers methods to open a specified business partner, and creat an invoice.
47
46
 
48
47
  - Open business partner
@@ -30,15 +30,16 @@ Example:
30
30
  """
31
31
 
32
32
  from pathlib import PurePath
33
- from typing import Optional
34
- from shareplum import Site, Office365
35
- from shareplum.site import Version
33
+ from typing import Optional, List
34
+ import os
35
+ from office365.runtime.auth.user_credential import UserCredential
36
+ from office365.sharepoint.client_context import ClientContext
36
37
 
37
38
 
38
39
  class Sharepoint:
39
40
  """
40
41
  A class to interact with a SharePoint site, enabling authentication, file listing,
41
- downloading, and saving functionalities within a specified SharePoint document library.
42
+ downloading, uploading, and saving functionalities within a specified SharePoint document library.
42
43
 
43
44
  Attributes:
44
45
  username (str): Username for authentication.
@@ -48,32 +49,36 @@ class Sharepoint:
48
49
  document_library (str): Document library path.
49
50
  """
50
51
 
51
- def __init__(self, username: str, password: str, site_url: str, site_name: str, document_library: str):
52
+ def __init__(
53
+ self, username: str, password: str, site_url: str, site_name: str, document_library: str
54
+ ):
52
55
  """Initializes the Sharepoint class with credentials and site details."""
53
56
  self.username = username
54
57
  self.password = password
55
58
  self.site_url = site_url
56
59
  self.site_name = site_name
57
60
  self.document_library = document_library
58
- self.site = self._auth()
61
+ self.ctx = self._auth()
59
62
 
60
- def _auth(self) -> Optional[Site]:
63
+ def _auth(self) -> Optional[ClientContext]:
61
64
  """
62
- Authenticates to the SharePoint site and returns the site object.
65
+ Authenticates to the SharePoint site and returns the client context.
63
66
 
64
67
  Returns:
65
- Optional[Site]: A SharePlum Site object for interacting with the SharePoint site if authentication is successful,
68
+ Optional[ClientContext]: A ClientContext object for interacting with the SharePoint site if authentication is successful,
66
69
  otherwise None.
67
70
  """
68
71
  try:
69
- authcookie = Office365(self.site_url, username=self.username, password=self.password).GetCookies()
70
- site = Site(f'{self.site_url}/sites/{self.site_name}', version=Version.v365, authcookie=authcookie)
71
- return site
72
+ site_full_url = f"{self.site_url}/teams/{self.site_name}"
73
+ ctx = ClientContext(site_full_url).with_credentials(
74
+ UserCredential(self.username, self.password)
75
+ )
76
+ return ctx
72
77
  except Exception as e:
73
78
  print(f"Failed to authenticate: {e}")
74
79
  return None
75
80
 
76
- def fetch_files_list(self, folder_name: str) -> Optional[list]:
81
+ def fetch_files_list(self, folder_name: str) -> Optional[List[dict]]:
77
82
  """
78
83
  Retrieves a list of files from a specified folder within the document library.
79
84
 
@@ -81,13 +86,17 @@ class Sharepoint:
81
86
  folder_name (str): The name of the folder within the document library.
82
87
 
83
88
  Returns:
84
- list: A list of file dictionaries in the specified folder, or an empty list if an error occurs or if the site is not authenticated.
89
+ list: A list of file dictionaries in the specified folder, or None if an error occurs or if the site is not authenticated.
85
90
  """
86
- if self.site:
91
+ if self.ctx:
87
92
  try:
88
- folder = self.site.Folder(f'{self.document_library}/{folder_name}')
93
+ folder_url = f"/teams/{self.site_name}/{self.document_library}/{folder_name}"
94
+ folder = self.ctx.web.get_folder_by_server_relative_url(folder_url)
89
95
  files = folder.files
90
- return files
96
+ self.ctx.load(files)
97
+ self.ctx.execute_query()
98
+ files_list = [{"Name": file.name} for file in files]
99
+ return files_list
91
100
  except Exception as e:
92
101
  print(f"Error retrieving files: {e}")
93
102
  return None
@@ -102,13 +111,14 @@ class Sharepoint:
102
111
  folder_name (str): The name of the folder where the file is located.
103
112
 
104
113
  Returns:
105
- bytes (Optional): The binary content of the file if successful, otherwise None.
114
+ Optional[bytes]: The binary content of the file if successful, otherwise None.
106
115
  """
107
- if self.site:
116
+ if self.ctx:
108
117
  try:
109
- folder = self.site.Folder(f'{self.document_library}/{folder_name}')
110
- file_content = folder.get_file(file_name)
111
- return file_content
118
+ file_url = f"/teams/{self.site_name}/{self.document_library}/{folder_name}/{file_name}"
119
+ file = self.ctx.web.get_file_by_server_relative_url(file_url)
120
+ file_content = file.read().execute_query()
121
+ return file_content.value
112
122
  except Exception as e:
113
123
  print(f"Failed to download file: {e}")
114
124
  return None
@@ -124,7 +134,7 @@ class Sharepoint:
124
134
  file_content (bytes): The binary content of the file.
125
135
  """
126
136
  file_directory_path = PurePath(folder_destination, file_name)
127
- with open(file_directory_path, 'wb') as file:
137
+ with open(file_directory_path, "wb") as file:
128
138
  file.write(file_content)
129
139
 
130
140
  def download_file(self, folder: str, filename: str, folder_destination: str):
@@ -151,9 +161,53 @@ class Sharepoint:
151
161
  folder_destination (str): The local folder path where the downloaded files will be saved.
152
162
  """
153
163
  files_list = self.fetch_files_list(folder)
154
- for file in files_list:
155
- file_content = self.fetch_file_content(file['Name'], folder)
156
- if file_content:
157
- self._write_file(folder_destination, file['Name'], file_content)
158
- else:
159
- print(f"Failed to download {file['Name']}")
164
+ if files_list:
165
+ for file in files_list:
166
+ file_content = self.fetch_file_content(file["Name"], folder)
167
+ if file_content:
168
+ self._write_file(folder_destination, file["Name"], file_content)
169
+ else:
170
+ print(f"Failed to download {file['Name']}")
171
+ else:
172
+ print(f"No files found in folder {folder}")
173
+
174
+ def upload_file(self, folder_name: str, file_path: str, file_name: Optional[str] = None):
175
+ """
176
+ Uploads a single file to a specified folder within the document library.
177
+
178
+ Args:
179
+ folder_name (str): The name of the folder within the document library.
180
+ file_path (str): The local path to the file to be uploaded.
181
+ file_name (Optional[str]): The name to give the file in SharePoint. If not provided, uses the name from file_path.
182
+ """
183
+ if self.ctx:
184
+ try:
185
+ if file_name is None:
186
+ file_name = os.path.basename(file_path)
187
+
188
+ folder_url = f"/teams/{self.site_name}/{self.document_library}/{folder_name}"
189
+ target_folder = self.ctx.web.get_folder_by_server_relative_url(folder_url)
190
+
191
+ with open(file_path, 'rb') as content_file:
192
+ file_content = content_file.read()
193
+
194
+ target_folder.upload_file(file_name, file_content).execute_query()
195
+ print(f"File '{file_name}' uploaded successfully to '{folder_url}'.")
196
+ except Exception as e:
197
+ print(f"Failed to upload file '{file_name}': {e}")
198
+
199
+ def upload_files(self, folder_name: str, files: List[str]):
200
+ """
201
+ Uploads multiple files to a specified folder within the document library.
202
+
203
+ Args:
204
+ folder_name (str): The name of the folder within the document library.
205
+ files (List[str]): A list of local file paths to be uploaded.
206
+ """
207
+ if self.ctx:
208
+ for file_path in files:
209
+ try:
210
+ file_name = os.path.basename(file_path)
211
+ self.upload_file(folder_name, file_path, file_name)
212
+ except Exception as e:
213
+ print(f"Failed to upload file '{file_path}': {e}")
@@ -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))
@@ -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.50
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
@@ -9,12 +9,13 @@ Classifier: Operating System :: Microsoft :: Windows
9
9
  Requires-Python: >=3.7
10
10
  Description-Content-Type: text/markdown
11
11
  License-File: LICENSE
12
- Requires-Dist: shareplum
13
12
  Requires-Dist: openpyxl>=3.1.2
14
13
  Requires-Dist: pyodbc>=5.1.0
15
14
  Requires-Dist: requests_ntlm>=1.2.0
16
15
  Requires-Dist: python-dateutil==2.9.*
17
16
  Requires-Dist: cryptography>=43.0.0
17
+ Requires-Dist: office365-rest-python-client
18
+ Requires-Dist: uiautomation
18
19
 
19
20
  # mbu-dev-shared-components
20
21
 
@@ -60,7 +61,6 @@ Additionally, it provides functionalities to count the total number of rows and
60
61
  #### - Invoices
61
62
 
62
63
  This module provides the InvoiceCreator class to create invoices in SAP.
63
-
64
64
  The InvoiceCreator class offers methods to open a specified business partner, and creat an invoice.
65
65
 
66
66
  - Open business partner
@@ -20,17 +20,21 @@ mbu_dev_shared_components/google/api/__init__.py
20
20
  mbu_dev_shared_components/google/api/auth.py
21
21
  mbu_dev_shared_components/google/workspace/__init__.py
22
22
  mbu_dev_shared_components/google/workspace/alerts.py
23
- mbu_dev_shared_components/office365/__init__.py
24
- mbu_dev_shared_components/office365/excel/__init__.py
25
- mbu_dev_shared_components/office365/excel/excel_reader.py
26
- mbu_dev_shared_components/office365/sharepoint_api/__init__.py
27
- mbu_dev_shared_components/office365/sharepoint_api/files.py
23
+ mbu_dev_shared_components/msoffice365/__init__.py
24
+ mbu_dev_shared_components/msoffice365/excel/__init__.py
25
+ mbu_dev_shared_components/msoffice365/excel/excel_reader.py
26
+ mbu_dev_shared_components/msoffice365/sharepoint_api/__init__.py
27
+ mbu_dev_shared_components/msoffice365/sharepoint_api/files.py
28
28
  mbu_dev_shared_components/os2forms/__init__.py
29
29
  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
@@ -1,6 +1,7 @@
1
- shareplum
2
1
  openpyxl>=3.1.2
3
2
  pyodbc>=5.1.0
4
3
  requests_ntlm>=1.2.0
5
4
  python-dateutil==2.9.*
6
5
  cryptography>=43.0.0
6
+ office365-rest-python-client
7
+ uiautomation
@@ -17,12 +17,13 @@ classifiers = [
17
17
  "Operating System :: Microsoft :: Windows",
18
18
  ]
19
19
  dependencies = [
20
- "shareplum",
21
20
  "openpyxl >= 3.1.2",
22
21
  "pyodbc >= 5.1.0",
23
22
  "requests_ntlm >= 1.2.0",
24
23
  "python-dateutil == 2.9.*",
25
- "cryptography >= 43.0.0"
24
+ "cryptography >= 43.0.0",
25
+ "office365-rest-python-client",
26
+ "uiautomation",
26
27
  ]
27
28
 
28
29
  [tool.setuptools_scm]