diff --git a/changelog.md b/changelog.md index 5a42e79..d79739d 100644 --- a/changelog.md +++ b/changelog.md @@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.14.0] - 2025-08-22 + +### Changed + +- SAP: Changed how kundekontakter are created with 0 or 1 aftaler. + +### Added + +- Eflyt: Added functions for sending letters. + ## [2.13.0] - 2025-08-13 ### Added @@ -238,7 +248,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Initial release -[Unreleased]: https://github.com/itk-dev-rpa/ITK-dev-shared-components/compare/2.13.0...HEAD +[Unreleased]: https://github.com/itk-dev-rpa/ITK-dev-shared-components/compare/2.14.0...HEAD +[2.14.0]: https://github.com/itk-dev-rpa/ITK-dev-shared-components/releases/tag/2.14.0 [2.13.0]: https://github.com/itk-dev-rpa/ITK-dev-shared-components/releases/tag/2.13.0 [2.12.1]: https://github.com/itk-dev-rpa/ITK-dev-shared-components/releases/tag/2.12.1 [2.12.0]: https://github.com/itk-dev-rpa/ITK-dev-shared-components/releases/tag/2.12.0 diff --git a/itk_dev_shared_components/eflyt/eflyt_letter.py b/itk_dev_shared_components/eflyt/eflyt_letter.py new file mode 100644 index 0000000..f6b1167 --- /dev/null +++ b/itk_dev_shared_components/eflyt/eflyt_letter.py @@ -0,0 +1,132 @@ +"""This module contains functions for sending letters from a case in eFlyt.""" + +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.support.select import Select +from selenium.webdriver.support.ui import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC +from selenium.common.exceptions import TimeoutException + +from itk_dev_shared_components.eflyt import eflyt_case + + +def send_letter_to_anmelder(browser: webdriver.Chrome, letter_text: str) -> bool: + """Open the 'Breve' tab and send a letter to the anmelder using the 'Individuelt brev'-template. + + Args: + browser: The webdriver browser object. + original_letter: The title of the original logiværtserklæring. + + Returns: + bool: True if the letter was sent. + """ + eflyt_case.change_tab(browser, tab_index=3) + + click_letter_template(browser, "- Individuelt brev") + + # Select the anmelder as the receiver + select_letter_receiver(browser, "anmelder") + + # Click 'Send brev' + browser.find_element(By.ID, "ctl00_ContentPlaceHolder2_ptFanePerson_bcPersonTab_btnSendBrev").click() + + # Insert the correct letter text + text_area = browser.find_element(By.ID, "ctl00_ContentPlaceHolder2_ptFanePerson_bcPersonTab_txtStandardText") + text_area.clear() + text_area.send_keys(letter_text) + # Click 'Ok' + browser.find_element(By.ID, "ctl00_ContentPlaceHolder2_ptFanePerson_bcPersonTab_btnOK").click() + + # Check if a warning appears + if check_digital_post_warning(browser): + # Click 'Nej' + browser.find_element(By.ID, "ctl00_ContentPlaceHolder2_ptFanePerson_bcPersonTab_btnDeleteLetter").click() + return False + + # Click 'Ja' + browser.find_element(By.ID, "ctl00_ContentPlaceHolder2_ptFanePerson_bcPersonTab_btnSaveLetter").click() + return True + + +def click_letter_template(browser: webdriver.Chrome, letter_name: str): + """Click the letter template with the given name under the "Breve" tab. + + Args: + browser: The webdriver browser object. + letter_name: The literal name of the letter template to click. + + Raises: + ValueError: If the letter wasn't found in the list. + """ + letter_table = browser.find_element(By.ID, "ctl00_ContentPlaceHolder2_ptFanePerson_bcPersonTab_GridViewBreveNew") + rows = letter_table.find_elements(By.TAG_NAME, "tr") + + for row in rows: + text = row.find_element(By.XPATH, "td[2]").text + if text == letter_name: + row.find_element(By.XPATH, "td[1]/input").click() + return + + raise ValueError(f"Template with the name '{letter_name}' was not found.") + + +def select_letter_receiver(browser: webdriver.Chrome, receiver_name: str) -> None: + """Select the receiver for the letter. The search is fuzzy so it's only checked + if the options contains the receiver name. + + I some cases there's only one option for the receiver in which + case there's a text label instead of a select element. In this + case the predefined name is still checked against the desired receiver. + + Args: + browser: The webdriver browser object. + receiver_name: The name of the receiver to select. + + Raises: + ValueError: If the given name isn't found in the select options. + ValueError: If the given name doesn't match the static label. + """ + # Check if there is a select for the receiver name + try: + # Wait for the dropdown to be present + name_select_element = WebDriverWait(browser, 2).until( + EC.presence_of_element_located((By.ID, "ctl00_ContentPlaceHolder2_ptFanePerson_bcPersonTab_ddlModtager")) + ) + name_select = Select(name_select_element) + + # Wait until the dropdown has more than one option + WebDriverWait(browser, 2).until(lambda browser: len(name_select.options) > 1) + + for i, option in enumerate(name_select.options): + if receiver_name in option.text: + name_select.select_by_index(i) + return + + raise ValueError(f"'{receiver_name}' wasn't found on the list of possible receivers.") + + except TimeoutException: + pass # Continue to the next check if the dropdown is not found + + # If there's simply a label for the receiver, check if the name matches + try: + name_label = WebDriverWait(browser, 2).until( + EC.presence_of_element_located((By.ID, "ctl00_ContentPlaceHolder2_ptFanePerson_bcPersonTab_lblModtagerName")) + ) + if receiver_name not in name_label.text: + raise ValueError(f"'{receiver_name}' didn't match the predefined receiver.") + except TimeoutException as exc: + raise ValueError("Receiver name label did not load in time.") from exc + + +def check_digital_post_warning(browser: webdriver.Chrome) -> bool: + """Check if a red warning text has appeared warning that + a letter must be sent manually. + + Args: + browser: The webdriver browser object. + + Returns: + bool: True if the warning has appeared. + """ + warning_text = browser.find_elements(By.XPATH, "//font[@color='red']") + return len(warning_text) != 0 and "Dokumentet skal sendes manuelt" in warning_text[0].text diff --git a/itk_dev_shared_components/eflyt/eflyt_login.py b/itk_dev_shared_components/eflyt/eflyt_login.py index 9285efa..07cae5e 100644 --- a/itk_dev_shared_components/eflyt/eflyt_login.py +++ b/itk_dev_shared_components/eflyt/eflyt_login.py @@ -1,4 +1,4 @@ -"""Module for logging into Eflyt/Daedalus/Whatchamacallit using Selenium""" +"""Module for logging into Eflyt using Selenium""" import time from selenium import webdriver diff --git a/itk_dev_shared_components/sap/opret_kundekontakt.py b/itk_dev_shared_components/sap/opret_kundekontakt.py index 57c91b7..d08539b 100644 --- a/itk_dev_shared_components/sap/opret_kundekontakt.py +++ b/itk_dev_shared_components/sap/opret_kundekontakt.py @@ -26,6 +26,62 @@ def opret_kundekontakter(session, fp: str, aftaler: list[str] | None, """ fmcacov.open_forretningspartner(session, fp) + if not aftaler: + _select_no_aftaler(session) + elif len(aftaler) == 1: + _select_single_aftale(session, aftaler[0]) + else: + _select_multiple_aftaler(session, aftaler) + + # Set art + session.findById("wnd[0]/usr/tabsTSTRIP/tabpT_CA/ssubSUBSR_TSTRIP:SAPLBPT1:0510/cmbBCONTD-CTYPE").Value = art + + # Go to editor and paste (lock if multithreaded) + session.findById("wnd[0]/usr/subNOTICE:SAPLEENO:1002/btnEENO_TEXTE-EDITOR").press() + if lock: + lock.acquire() + _set_clipboard(notat) + session.findById("wnd[0]/tbar[1]/btn[9]").press() + if lock: + lock.release() + + # Back and save + session.findById("wnd[0]/tbar[0]/btn[3]").press() + session.findById("wnd[0]/tbar[0]/btn[11]").press() + + _confirm_kundekontakt(session, notat, art) + + +def _select_no_aftaler(session): + """Right click the fp and select 'Opret kundekontakt'. + + Args: + session: The sap session object. + """ + session.findById("wnd[0]/shellcont/shell").nodeContextMenu("GP0000000001") + session.findById("wnd[0]/shellcont/shell").selectContextMenuItem("POPUP") + + +def _select_single_aftale(session, aftale: str): + """Right click a single aftale and select 'Opret kundekontakt'. + + Args: + session: The sap session object. + aftale: The aftale number. + """ + tree = session.findById("wnd[0]/shellcont/shell") + node_key = tree_util.get_node_key_by_text(tree, aftale) + tree.nodeContextMenu(node_key) + tree.selectContextMenuItem("POPUP") + + +def _select_multiple_aftaler(session, aftaler: list[str]): + """Use 'Opret kundekontakt-flere' to select multiple aftaler. + + Args: + session: The sap session object. + aftaler: The list of aftaler. + """ # Click 'Opret kundekontakt-flere session.findById("wnd[0]/shellcont/shell").nodeContextMenu("GP0000000001") session.findById("wnd[0]/shellcont/shell").selectContextMenuItem("FLERE") @@ -49,24 +105,6 @@ def opret_kundekontakter(session, fp: str, aftaler: list[str] | None, aftale_tree.ChangeCheckBox(key, name, True) session.findById("wnd[1]/usr/cntlCONTAINER_PSOBKEY/shellcont/shell/shellcont[1]/shell[0]").pressButton("OK") - # Set art - session.findById("wnd[0]/usr/tabsTSTRIP/tabpT_CA/ssubSUBSR_TSTRIP:SAPLBPT1:0510/cmbBCONTD-CTYPE").Value = art - - # Go to editor and paste (lock if multithreaded) - session.findById("wnd[0]/usr/subNOTICE:SAPLEENO:1002/btnEENO_TEXTE-EDITOR").press() - if lock: - lock.acquire() - _set_clipboard(notat) - session.findById("wnd[0]/tbar[1]/btn[9]").press() - if lock: - lock.release() - - # Back and save - session.findById("wnd[0]/tbar[0]/btn[3]").press() - session.findById("wnd[0]/tbar[0]/btn[11]").press() - - _confirm_kundekontakt(session, notat, art) - def _set_clipboard(text: str) -> None: """Private function to set text to the clipboard. diff --git a/pyproject.toml b/pyproject.toml index 2231fbd..eb528fa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "itk_dev_shared_components" -version = "2.13.0" +version = "2.14.0" authors = [ { name="ITK Development", email="itk-rpa@mkb.aarhus.dk" }, ]