Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 2 additions & 8 deletions .pylintrc
Original file line number Diff line number Diff line change
@@ -1,13 +1,7 @@
[pylint.messages_control]
disable =
C0303, # Trailing whitespace
C0103, # Variable names
C0305, # Trailing newlines
C0304, # Missing final line
C0301, # Line too long
I1101, E1101, # C-modules members
W0621, # Redefine outer name
R0913 # Too many arguments
R0913, # Too many arguments
R0914 # Too many local variables

[MASTER]
ignore-paths = ^tests/ # Ignore the tests folder
69 changes: 20 additions & 49 deletions ITK_dev_shared_components/SAP/gridview_util.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""This module provides static functions to peform common tasks with SAP GuiGridView COM objects."""
"""This module provides static functions to perform common tasks with SAP GuiGridView COM objects."""

def scroll_entire_table(grid_view, return_to_top=False) -> None:
"""This function scrolls through the entire table to load all cells.
Expand All @@ -9,11 +9,13 @@ def scroll_entire_table(grid_view, return_to_top=False) -> None:

Returns:
_type_: _description_
"""
"""
if grid_view.RowCount == 0 or grid_view.VisibleRowCount == 0:
return

for i in range(0, grid_view.RowCount, grid_view.VisibleRowCount):
grid_view.FirstVisibleRow = i

if return_to_top:
grid_view.FirstVisibleRow = 0

Expand All @@ -28,7 +30,7 @@ def get_all_rows(grid_view, pre_load=True) -> tuple[tuple[str]]:

Returns:
tuple[tuple[str]]: A 2D tuple of all cell values in the gridview.
"""
"""

if pre_load:
scroll_entire_table(grid_view, True)
Expand All @@ -43,9 +45,9 @@ def get_all_rows(grid_view, pre_load=True) -> tuple[tuple[str]]:
for c in columns:
v = grid_view.GetCellValue(r, c)
row_data.append(v)

output.append(tuple(row_data))

return tuple(output)


Expand All @@ -60,7 +62,7 @@ def get_row(grid_view, row:int, scroll_to_row=False) -> tuple[str]:

Returns:
tuple[str]: A tuple of the row's data.
"""
"""

if scroll_to_row:
grid_view.FirstVisibleRow = row
Expand All @@ -83,7 +85,7 @@ def iterate_rows(grid_view) -> tuple[str]:

Yields:
tuple[str]: A tuple of the next row's data.
"""
"""

row = 0
while row < grid_view.RowCount:
Expand All @@ -104,7 +106,7 @@ def get_column_titles(grid_view) -> tuple[str]:

Returns:
tuple[str]: A tuple of the gridview's column titles.
"""
"""

return tuple(grid_view.GetColumnTitles(c)[0] for c in grid_view.ColumnOrder)

Expand All @@ -123,7 +125,7 @@ def find_row_index_by_value(grid_view, column:str, value:str) -> int:

Returns:
int: The index of the first row which column value matches the given value.
"""
"""

if column not in grid_view.ColumnOrder:
raise ValueError(f"Column '{column}' not in grid_view")
Expand All @@ -132,14 +134,14 @@ def find_row_index_by_value(grid_view, column:str, value:str) -> int:
# Only scroll when row isn't visible
if not grid_view.FirstVisibleRow <= row <= grid_view.FirstVisibleRow + grid_view.VisibleRowCount-1:
grid_view.FirstVisibleRow = row

if grid_view.GetCellValue(row, column) == value:
return row

return -1

def find_all_row_indecies_by_value(grid_view, column:str, value:str) -> list[int]:
"""Find all indecies of the rows where the given column's value
def find_all_row_indices_by_value(grid_view, column:str, value:str) -> list[int]:
"""Find all indices of the rows where the given column's value
match the given value. Returns an empty list if no row is found.

Args:
Expand All @@ -151,8 +153,8 @@ def find_all_row_indecies_by_value(grid_view, column:str, value:str) -> list[int
ValueError: If the column name doesn't exist in the grid view.

Returns:
list[int]: A list of row indecies where the value matches.
"""
list[int]: A list of row indices where the value matches.
"""
if column not in grid_view.ColumnOrder:
raise ValueError(f"Column '{column}' not in grid_view")

Expand All @@ -162,39 +164,8 @@ def find_all_row_indecies_by_value(grid_view, column:str, value:str) -> list[int
# Only scroll when row isn't visible
if not grid_view.FirstVisibleRow <= row <= grid_view.FirstVisibleRow + grid_view.VisibleRowCount-1:
grid_view.FirstVisibleRow = row

if grid_view.GetCellValue(row, column) == value:
rows.append(row)

return rows




if __name__=='__main__':
import win32com.client

SAP = win32com.client.GetObject("SAPGUI")
app = SAP.GetScriptingEngine
connection = app.Connections(0)
session = connection.Sessions(0)

table = session.findById("wnd[0]/usr/cntlGRID1/shellcont/shell")

rows = find_all_row_indecies_by_value(table, "ZZ_PARTNER", '15879880')
print(rows)
table.setCurrentCell(rows[0], "ZZ_PARTNER")

# print(get_row(table, 1, True))

# scroll_entire_table(table)

# data = get_all_rows(table)
# print(len(data), len(data[0]))

# for r in iterate_rows(table):
# print(r)

# print(get_column_titles(table))


return rows
66 changes: 36 additions & 30 deletions ITK_dev_shared_components/SAP/multi_session.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
"""This module provides static function to handle multiple sessions of SAP.
"""This module provides static functions to handle multiple sessions of SAP.
Using this module you can spawn multiple sessions and automatically execute
a function in parallel on the sessions."""

import time
import threading
import math
from typing import Callable

import pythoncom
import win32com.client
import win32gui

def run_with_session(session_index:int, func:Callable, args:tuple) -> None:
"""Run a function in a sepcific session based on the sessions index.
This function is meant to be run inside a seperate thread.
"""Run a function in a specific session based on the sessions index.
This function is meant to be run inside a separate thread.
The function must take a session object as its first argument.
Note that this function will not spawn the sessions before running,
use spawn_sessions to do that.
"""

pythoncom.CoInitialize()

SAP = win32com.client.GetObject("SAPGUI")
app = SAP.GetScriptingEngine
sap = win32com.client.GetObject("SAPGUI")
app = sap.GetScriptingEngine
connection = app.Connections(0)
session = connection.Sessions(session_index)

func(session, *args)

pythoncom.CoUninitialize()


def run_batch(func:Callable, args:tuple[tuple], num_sessions=6) -> None:
"""Run a function in parallel sessions.
The function will be run {num_sessions} times with args[i] as input.
Expand All @@ -40,7 +43,7 @@ def run_batch(func:Callable, args:tuple[tuple], num_sessions=6) -> None:
for i in range(num_sessions):
t = ExThread(target=run_with_session, args=(i, func, args[i]))
threads.append(t)

for t in threads:
t.start()
for t in threads:
Expand All @@ -49,6 +52,7 @@ def run_batch(func:Callable, args:tuple[tuple], num_sessions=6) -> None:
if t.error:
raise t.error


def run_batches(func:Callable, args:tuple[tuple], num_sessions=6):
"""Run a function in parallel batches.
This function runs the input function for each set of arguments in args.
Expand All @@ -62,16 +66,26 @@ def run_batches(func:Callable, args:tuple[tuple], num_sessions=6):
batch = args[b:b+num_sessions]
run_batch(func, args, len(batch))

def spawn_sessions(num_sessions=6) -> list:

def spawn_sessions(num_sessions=6) -> tuple:
"""A function to spawn multiple sessions of SAP.
This function will attempt to spawn the desired number of sessions.
If the current number of open sessions exceeds the desired number of sessions
If the current number of already open sessions exceeds the desired number of sessions
the already open sessions will not be closed to match the desired number.
The number of sessions must be between 1 and 6.
Returns a list of all open sessions.

Args:
num_sessions: The number of sessions desired. Defaults to 6.

Raises:
ValueError: If the number of sessions is not between 1 and 6.

Returns:
tuple: A tuple of all currently open sessions.
"""
SAP = win32com.client.GetObject("SAPGUI")
app = SAP.GetScriptingEngine

sap = win32com.client.GetObject("SAPGUI")
app = sap.GetScriptingEngine
connection = app.Connections(0)
session = connection.Sessions(0)

Expand All @@ -82,25 +96,17 @@ def spawn_sessions(num_sessions=6) -> list:

for _ in range(num_sessions - connection.Sessions.count):
session.CreateSession()

# Wait for the sessions to spawn
while connection.Sessions.count < num_sessions:
time.sleep(0.1)

sessions = list(connection.Sessions)
sessions = tuple(connection.Sessions)
num_sessions = len(sessions)

if num_sessions == 1:
c = 1
elif num_sessions <= 4:
c = 2
elif num_sessions <= 6:
c = 3

if num_sessions < 3:
r = 1
else:
r = 2
# Calculate number of columns and rows
c = math.ceil(math.sqrt(num_sessions))
r = math.ceil(num_sessions / c)

w, h = 1920//c, 1040//r

Expand All @@ -111,20 +117,22 @@ def spawn_sessions(num_sessions=6) -> list:
x = i % c * w
y = i // c * h
win32gui.MoveWindow(hwnd, x, y, w, h, True)

return sessions

def get_all_SAP_sessions() -> tuple:

def get_all_SAP_sessions() -> tuple: # pylint: disable=invalid-name
"""Returns a tuple of all open SAP sessions (on connection index 0).

Returns:
tuple: A tuple of SAP GuiSession objects.
"""
SAP = win32com.client.GetObject("SAPGUI")
app = SAP.GetScriptingEngine
sap = win32com.client.GetObject("SAPGUI")
app = sap.GetScriptingEngine
connection = app.Connections(0)
return tuple(connection.Sessions)


class ExThread(threading.Thread):
"""A thread with a handle to get an exception raised inside the thread: ExThread.error"""
def __init__(self, *args, **kwargs):
Expand All @@ -136,5 +144,3 @@ def run(self):
self._target(*self._args, **self._kwargs)
except Exception as e: # pylint: disable=broad-exception-caught
self.error = e


32 changes: 12 additions & 20 deletions ITK_dev_shared_components/SAP/opret_kundekontakt.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"""This module provides a single function to conviniently peform the action 'opret-kundekontaker' in SAP."""
"""This module provides a single function to conveniently peform the action 'opret-kundekontaker' in SAP."""

from typing import Literal
import win32clipboard
from ITK_dev_shared_components.SAP import tree_util


def opret_kundekontakter(session, fp:str, aftaler:list[str] | None,
art:Literal[' ', 'Automatisk', 'Fakturagrundlag', 'Fuldmagt ifm. værge', 'Konverteret', 'Myndighedshenvend.', 'Orientering', 'Returpost', 'Ringeaktivitet', 'Skriftlig henvend.', 'Telefonisk henvend.'],
def opret_kundekontakter(session, fp:str, aftaler:list[str] | None,
art:Literal[' ', 'Automatisk', 'Fakturagrundlag', 'Fuldmagt ifm. værge', 'Konverteret', 'Myndighedshenvend.', 'Orientering', 'Returpost', 'Ringeaktivitet', 'Skriftlig henvend.', 'Telefonisk henvend.'],
notat:str, lock=None) -> None:
"""Creates a kundekontakt on the given FP and aftaler.

Expand Down Expand Up @@ -41,33 +41,25 @@ def opret_kundekontakter(session, fp:str, aftaler:list[str] | None,

# Go to editor and paste (lock if multithreaded)
session.findById("wnd[0]/usr/subNOTICE:SAPLEENO:1002/btnEENO_TEXTE-EDITOR").press()
if lock:
if lock:
lock.acquire()
_setClipboard(notat)
_set_clipboard(notat)
session.findById("wnd[0]/tbar[1]/btn[9]").press()
if lock:
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()


def _setClipboard(text:str) -> None:
def _set_clipboard(text:str) -> None:
"""Private function to set text to the clipboard.

Args:
text: Text to set to clipboard.
"""
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText(text)
win32clipboard.CloseClipboard()


if __name__ == '__main__':
from ITK_dev_shared_components.SAP import multi_session
from datetime import datetime

session = multi_session.spawn_sessions(1)[0]
fp = '25564617'
aftaler = ['2291987', '2421562', '2311094']
art = 'Orientering'
notat = 'Test '+ str(datetime.now())

opret_kundekontakter(session, fp, aftaler, 'Automatisk', notat)
Loading