Skip to content

Commit fe86dfa

Browse files
authored
Merge pull request #32 from itk-dev-rpa/release/1.3.0
Release/1.3.0
2 parents b10bd6b + dc59021 commit fe86dfa

File tree

9 files changed

+159
-65
lines changed

9 files changed

+159
-65
lines changed

.github/workflows/Linting.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ jobs:
1010
python-version: ["3.11"]
1111
fail-fast: false
1212
steps:
13-
- uses: actions/checkout@v3
13+
- uses: actions/checkout@v4
1414
- name: Set up Python ${{ matrix.python-version }}
15-
uses: actions/setup-python@v3
15+
uses: actions/setup-python@v5
1616
with:
1717
python-version: ${{ matrix.python-version }}
1818

changelog.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
## [1.3.0] - 2024-02-20
11+
12+
### Added
13+
14+
- SAP.fmcacov module
15+
- Function to dismiss "key-popup" in SAP.
16+
- Function to reliably open forretningspartners in SAP.
17+
- Test for all of the above.
18+
19+
### Changed
20+
21+
- opret_kundekontakt uses the new SAP.fmcacov module.
22+
- Updated Github actions dependencies.
23+
24+
### Removed
25+
26+
- SAP login using web portal. Wasn't used or maintained.
27+
1028
## [1.2.0] - 2024-02-12
1129

1230
### Added
@@ -43,7 +61,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4361

4462
- Initial release
4563

46-
[Unreleased] https://github.com/itk-dev-rpa/ITK-dev-shared-components/compare/1.2.0...HEAD
64+
[Unreleased] https://github.com/itk-dev-rpa/ITK-dev-shared-components/compare/1.3.0...HEAD
65+
[1.3.0] https://github.com/itk-dev-rpa/ITK-dev-shared-components/releases/tag/1.3.0
4766
[1.2.0] https://github.com/itk-dev-rpa/ITK-dev-shared-components/releases/tag/1.2.0
4867
[1.1.0] https://github.com/itk-dev-rpa/ITK-dev-shared-components/releases/tag/1.1.0
4968
[1.0.0] https://github.com/itk-dev-rpa/ITK-dev-shared-components/releases/tag/1.0.0
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
"""This module contains functions to help with actions in the fmcacov transaction."""
2+
3+
4+
def open_forretningspartner(session, fp: str) -> None:
5+
"""Start the transaction FMCACOV and open the given forretningspartner.
6+
If the fp-number also matches a cvr-number the fp-number will be opened.
7+
8+
Args:
9+
session: The SAP session to perform the action.
10+
fp: The forretningspartner number.
11+
12+
Raises:
13+
ValueError: If the forretningspartner wasn't found.
14+
"""
15+
session.StartTransaction('fmcacov')
16+
17+
# Find forretningspartner
18+
session.findById("wnd[0]/usr/ctxtGPART_DYN").text = fp
19+
session.findById("wnd[0]").sendVKey(0)
20+
21+
# Detect window "Forretningspartner * Entries"
22+
if session.findById('wnd[1]/usr', False) is not None:
23+
# Pop-up detected
24+
for row_id in range(3, 5):
25+
fp_row = session.FindById(f'wnd[1]/usr/lbl[103,{row_id}]')
26+
if fp_row.text == fp:
27+
fp_row.SetFocus()
28+
# Press 'Accept' button.
29+
session.FindById('wnd[1]/tbar[0]/btn[0]').press()
30+
break
31+
else:
32+
# Range exhausted
33+
raise ValueError(f"Forretningspartner '{fp}' was not found in pop-up.")
34+
35+
36+
def dismiss_key_popup(session, fp: str = "25564617") -> None:
37+
"""Once a day a popup appears asking to generate a new "afstemningsnøgle".
38+
This function forces it to appear and clicks "Ja" on it.
39+
This is done by pretending to do "Kontovedligehold" on the given forretningspartner.
40+
The function should start and end at the home screen.
41+
42+
Args:
43+
session: The SAP session object.
44+
fp: The forretningspartner to used. Defaults to "25564617" a fictional person in AAK.
45+
46+
Raises:
47+
RuntimeError: If a popup other than the expected ones appear.
48+
"""
49+
open_forretningspartner(session, fp)
50+
51+
# Right click the first row in the postliste and click "Kontovedligehold med filter"
52+
postliste = session.findById("wnd[0]/usr/tabsDATA_DISP/tabpDATA_DISP_FC1/ssubDATA_DISP_SCA:RFMCA_COV:0202/cntlRFMCA_COV_0100_CONT5/shellcont/shell")
53+
postliste.setCurrentCell(0, "VTREF")
54+
postliste.contextMenu()
55+
postliste.selectContextMenuItem("ACC_MAINT_FILTER")
56+
57+
# Check if the key popup appeared
58+
popup = session.findbyid("wnd[1]", False)
59+
if popup:
60+
if popup.text == "Kontrol af afstemningsnøgle":
61+
# Press "Ja"
62+
session.findbyid("wnd[1]/usr/btnSPOP-OPTION1").press()
63+
else:
64+
raise RuntimeError(f"Another popup is blocking: {popup.text}")
65+
66+
# Check and dismiss confirmation dialog
67+
confirmation_text = session.findbyid("wnd[1]/usr/txtMESSTXT1").text
68+
if 'oprettet' in confirmation_text:
69+
session.findById("wnd[1]/tbar[0]/btn[0]").press()
70+
else:
71+
raise RuntimeError(f"Confirmation dialog wasn't as expected: {confirmation_text}.")
72+
73+
# Go back to home screen (Afbryd x 3)
74+
session.findById("wnd[0]/tbar[0]/btn[12]").press()
75+
session.findById("wnd[0]/tbar[0]/btn[12]").press()
76+
session.findById("wnd[0]/tbar[0]/btn[12]").press()

itk_dev_shared_components/sap/gridview_util.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""This module provides static functions to perform common tasks with SAP GuiGridView COM objects."""
22

3+
from typing import Iterator
4+
35

46
def scroll_entire_table(grid_view, return_to_top=False) -> None:
57
"""This function scrolls through the entire table to load all cells.
@@ -72,7 +74,7 @@ def get_row(grid_view, row: int, scroll_to_row=False) -> tuple[str]:
7274
return tuple(row_data)
7375

7476

75-
def iterate_rows(grid_view) -> tuple[str]:
77+
def iterate_rows(grid_view) -> Iterator[tuple[str]]:
7678
"""This generator yields each row of the table in order.
7779
This is preferable to loading all rows as this will only load a row when it's needed.
7880
As a side effect this function scrolls the yielded row into view, so the

itk_dev_shared_components/sap/opret_kundekontakt.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import win32clipboard
77

8-
from itk_dev_shared_components.sap import tree_util
8+
from itk_dev_shared_components.sap import tree_util, fmcacov
99

1010

1111
def opret_kundekontakter(session, fp: str, aftaler: list[str] | None,
@@ -24,11 +24,7 @@ def opret_kundekontakter(session, fp: str, aftaler: list[str] | None,
2424
Raises:
2525
RuntimeError: If the kundekontakt wasn't created.
2626
"""
27-
session.StartTransaction('fmcacov')
28-
29-
# Find forretningspartner
30-
session.findById("wnd[0]/usr/ctxtGPART_DYN").text = fp
31-
session.findById("wnd[0]").sendVKey(0)
27+
fmcacov.open_forretningspartner(session, fp)
3228

3329
# Click 'Opret kundekontakt-flere
3430
session.findById("wnd[0]/shellcont/shell").nodeContextMenu("GP0000000001")

itk_dev_shared_components/sap/sap_login.py

Lines changed: 0 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -2,67 +2,15 @@
22
as well as a function to change user passwords."""
33

44
import os
5-
import pathlib
65
import subprocess
76
import time
87

9-
from selenium import webdriver
10-
from selenium.webdriver.common.by import By
118
import pywintypes
129
import win32com.client
1310

1411
from itk_dev_shared_components.sap import multi_session
1512

1613

17-
def login_using_portal(username: str, password: str):
18-
"""Open KMD Portal in Edge, login and start SAP GUI.
19-
Args:
20-
user: KMD Portal username.
21-
password: KMD Portal password.
22-
"""
23-
driver = webdriver.Chrome()
24-
driver.implicitly_wait(10)
25-
driver.get('https://portal.kmd.dk/irj/portal')
26-
driver.maximize_window()
27-
28-
# Login
29-
user_field = driver.find_element(By.ID, 'logonuidfield')
30-
pass_field = driver.find_element(By.ID, 'logonpassfield')
31-
login_button = driver.find_element(By.ID, 'buttonLogon')
32-
user_field.clear()
33-
user_field.send_keys(username)
34-
pass_field.clear()
35-
pass_field.send_keys(password)
36-
login_button.click()
37-
38-
# Opus
39-
mine_genveje = driver.find_element(By.CSS_SELECTOR, "div[title='Mine Genveje']")
40-
mine_genveje.click()
41-
42-
# Wait for download and launch file
43-
_wait_for_download()
44-
45-
driver.quit()
46-
_wait_for_sap_session(10)
47-
48-
49-
def _wait_for_download():
50-
"""Private function that checks if the SAP.erp file has been downloaded.
51-
Raises:
52-
TimeoutError: If the file hasn't been downloaded within 5 seconds.
53-
"""
54-
downloads_folder = str(pathlib.Path.home() / "Downloads")
55-
for _ in range(10):
56-
for file in os.listdir(downloads_folder):
57-
if file.endswith(".sap"):
58-
path = os.path.join(downloads_folder, file)
59-
os.startfile(path)
60-
return
61-
62-
time.sleep(0.5)
63-
raise TimeoutError(f".sap file not found in {downloads_folder}")
64-
65-
6614
def login_using_cli(username: str, password: str, client: str = '751', system: str = 'P02', timeout: int = 10) -> None:
6715
"""Open and login to SAP with commandline expressions.
6816
Args:

pyproject.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "itk_dev_shared_components"
7-
version = "1.2.0"
7+
version = "1.3.0"
88
authors = [
99
{ name="ITK Development", email="itk-rpa@mkb.aarhus.dk" },
1010
]
@@ -18,7 +18,6 @@ classifiers = [
1818
]
1919
dependencies = [
2020
"pywin32 >= 306",
21-
"selenium >= 4.10.0",
2221
"msal >= 1.24.1",
2322
"requests >= 2.31.0",
2423
"beautifulsoup4 >= 4.12.2"

tests/test_sap/test_fmcacov.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""Tests relating to the module SAP.fmcacov."""
2+
3+
import unittest
4+
import os
5+
from itk_dev_shared_components.sap import fmcacov, sap_login, multi_session
6+
7+
# Some tests might look similar, and we want this. pylint: disable=duplicate-code
8+
9+
10+
class TestFmcacov(unittest.TestCase):
11+
"""Tests relating to the module SAP.fmcacov."""
12+
@classmethod
13+
def setUpClass(cls):
14+
"""Launch SAP and get the main session."""
15+
sap_login.kill_sap()
16+
17+
user, password = os.environ['SAP Login'].split(';')
18+
sap_login.login_using_cli(user, password)
19+
20+
cls.session = multi_session.get_all_sap_sessions()[0]
21+
22+
@classmethod
23+
def tearDownClass(cls):
24+
sap_login.kill_sap()
25+
26+
def test_find_forretningspartner(self):
27+
"""Find a test-person in fmcacov and check cpr and name.
28+
To properly test it the fp needs to match a cvr number, but that doesn't exist
29+
as test data.
30+
"""
31+
fmcacov.open_forretningspartner(self.session, "25564617")
32+
33+
cpr = self.session.findById("wnd[0]/usr/txtZDKD_BP_NUM").text
34+
self.assertEqual(cpr, '841289-3981')
35+
36+
name = self.session.findById("wnd[0]/usr/subHEAD_DATA_SUBSCREEN:SAPLZDKD_SUBSCREENS:0100/txtBUS000FLDS-DESCRIP").text
37+
self.assertEqual(name, 'Testbruger Et')
38+
39+
# Go back to home screen.
40+
self.session.findById("wnd[0]/tbar[0]/btn[12]").press()
41+
42+
def test_dismiss_key_popup(self):
43+
"""Try to find 'afstemningsnøgle'-popup and dismiss it.
44+
The popup only appears once a day after midnight.
45+
"""
46+
fmcacov.dismiss_key_popup(self.session)
47+
48+
49+
if __name__ == '__main__':
50+
unittest.main()

tests/test_sap/test_gridview_util.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ def test_get_column_titles(self):
7070
def test_find_row_index_by_value(self):
7171
"""Test finding a single row by column value."""
7272
# Test finding an actual value.
73-
index = gridview_util.find_row_index_by_value(self.table, "TXTU2", "Test Deltrans")
73+
index = gridview_util.find_row_index_by_value(self.table, "TXTU2", "Test deltrans")
7474
self.assertNotEqual(index, -1)
7575

7676
# Test NOT finding a wrong value.
@@ -94,3 +94,7 @@ def test_find_all_row_indices_by_value(self):
9494
# Test error on wrong column name.
9595
with self.assertRaises(ValueError):
9696
gridview_util.find_all_row_indices_by_value(self.table, "Foo", "Bar")
97+
98+
99+
if __name__ == '__main__':
100+
unittest.main()

0 commit comments

Comments
 (0)