简介
- SAP GUI 前台操作一次,即可完成SAP自动化批处理制作,支持alv录制
- 支持部分录制,即不需要录制完整事务码,可以在当前窗口执行部分重复操作
- 无需开发、配置、传输请求,如果操作熟练,就可以做到无需测试
- 录制完成后自动生成excel模板,保存好模板未来可以在任何sap环境继续使用
- 适用于SAP快捷登录、日常操作的简化、一次性数据的批处理、自动化测试
环境准备
下载LAN RPA
执行LAN_RPA.exe文件
配置SAP账号密码
点击 连接-编辑,会打开一个excel
填入描述(必须和sap gui里填写的描述一致,区分大小写),客户端,用户名,语言,密码
保存excel,点击 连接-重读,读取配置的sap连接
配置SAP服务器参数
TCODE RZ11,输入 sapgui/user_scripting 设置值为TRUE,这个参数是启动SAP服务器gui script的意思。服务器重启后会失效,可以RZ10改,RZ10改完服务器重启不会失效。
配置SAP GUI通知
gui-选项
把脚本通知的勾去掉,这个配置的作用是执行脚本时不会发出gui 通知
上手
窗口管理
选择配置好的SAP连接,点击 打开 ,即可快速打开连接,快速切换用户和语言
选中打开的会话,右键
双击:跳转到窗口,如果双击窗口没跳出来,再双击一次就可以了
新会话:打开新的窗口
关闭其他:关闭其他窗口
关闭全部:关闭全部窗口
开始录制(以下demo都存在网盘的模板文件夹,有些是录屏示例,有些已经在生产使用了,可直接执行)
选中一个会话,点击 开始录制,跳转到sap窗口,在窗口输入事务码开始操作,操作结束之后点击 结束录制 即可完成制作。
注意工具只会根据输入的值生成模板,输入框的默认值如果要作为模板可输入字段,需要手工输入一下。
demo1 批量修改物料描述
选择一个系统,点击 打开 按钮,打开一个连接
打开后选中一个会话,点击 开始录制 ,此时会自动跳转到SAP窗口。(如果选中的会话不是初始的tcode,可以点 初始化 按钮,还原到开始菜单)
输入 mm02 ,回车
输入物料号(注意mm02进来可能默认有物料号,此时需要重新输入,不能直接默认不输入物料直接回车进去),回车
选中视图,回车
修改描述,点击保存
操作完毕后点 结束录制
此时会弹出一个文件保存框,输入文件名
保存后会自动打开一个excel模板文件
插入一行,填写描述(此步骤可选)
在 字段 页签,添加多行数据,其中doc_num表示条目数,如果有行项目,也通过doc_num把抬头和行项目关联
在 表1 页签,添加多行数据(这里的表1是根据选择视图的弹出框自动生成的,作用是进来选中视图。如果视图位置不固定,可以在mm02保存默认视图,这次每次直接回车,不用再选视图)
保存excel,选中一个会话,点击 执行 按钮
此时会跳转到SAP窗口开始执行,执行完毕会自动打开excel,查看返回消息
demo2 批量下载alv报表
打开或者选择一个会话,点击 开始录制
输入tcode,输入选择条件,执行
右键下载
输入文件名称(注意必须输入,默认带出来的删掉输一次),点击生成(或如果文件已存在就点击替换)
点 结束录制
保存模板
填一下选择条件,和导出的文件名称
保存后点击执行
查看返回消息
demo3 CA01创建工艺路线
录制好的模板放在网盘的 demo 文件夹了,注意录制工序的时候录一行就好了
demo4 BP维护branch code
demo5 migo 批量创建物料凭证
demo6 批量冲销物料凭证
点击 开始录制,输入se37
点击 功能模块-执行-测试序列
输入
BAPI_GOODSMVT_CANCEL
BAPI_TRANSACTION_COMMIT
点击执行
输入物料凭证和年度,点击执行
点击返回
再点执行
点击 结束录制,在弹出的excel填写数据
在 字段 页签填写需要冲销的物料凭证
在 表1 页签按doc_num复制数据
保存excel,点击 执行
demo7 SM30 带输入条件 批量维护
正常录制,没什么特别的,模板在demo文件夹中
demo8 CS01创建BOM
demo9 va32 重新派生
录制的时候双击第一行行项目,在明细界面操作完之后,点击返回,然后点保存
在 字段 页签填入计划协议号
在 表1 页签填入行,用doc_num和和计划协议对应,要处理几行就复制几行
对于 科目分配 页签位置不固定 的解决方法:分场景录两个模板分别跑对应的数据,数据不分开用不同模板跑两次都可以,程序遇到找不到屏幕的情况会自动跳过不会卡住的
demo10 se38创建程序
以安装ZLAN_ACC为例,直接执行demo文件夹中的 se38安装zlan_acc.xlsx 即可
自动执行到这个界面,敲一下回车就完成安装了
如果要自己录制,也正常从se38进去录制即可
demo11 su01增加角色与参数文件
关于alv录制,和表一样只录入一行即可,有两种做法,推荐第二种
1.点击插入行按钮,在excel的sheet“表”中填入多行,会重复执行插入行和填写alv单元格的操作,如果此操作导致alv错位,建议每次插入一行,保存好了再进来插入一行
2.在空白行输入,如果有多行数据,每次执行会在下一行输入。
如果进来alv已有多行数据,需要翻页才能到空白行,可以在录制前选中alv一列筛选为空的数据,然后保存用户的缺省布局,这样每次进来从第一行开始都是alv可输入的空白行了
demo12 co01 创建生产订单
demo13 spro配置ewm Map Document Types from ERP System to EWM
此模板是每次进入spro配置一条,可以优化成:用一个窗口先打开spro,展开节点到指定位置,只录制添加新条目和返回,执行的时候也从已经展开节点的窗口执行,提升效率
demo14 FS00创建科目
详细解释
配置
点击 配置-编辑
会打开配置文件
[general]
theme = yeti 主题
delay = 0 执行延时,如1则会等待1秒再执行下一步。
template_no_ask = False 录制结束时会根据填写的模板路径直接保存模板,不会弹出文件选择框
bar_total = False 是否在进度条显示总条目数和当前已完成的条目数
refresh_interval = 1000 会话自动刷新间隔。如果打开的窗口太多1秒可能刷新不完,在前台会看到卡顿,此时需要调大刷新间隔
log = False 是否输出日志,如果为True,则执行完之后会把日志输出到excel上
[path]
vbs = C:\Users\10042832\AppData\Roaming\SAP\SAP GUI\Scripts\SCRIPT6.VBS vbs文件位置
template = C:/Users/DELL/Desktop/temp/ca01.xlsx 模板位置
录制原理
- 根据输入的字段,生成对应excel的列。所以如果要录入字段有默认值,要做一次输入操作,不能直接回车跳过屏幕了
- 录制多行的表时只需要录制一行,如果要进入行明细界面操作,就双击录制的那一行,在明细界面操作完了之后点击 返回 按钮,不要直接保存,以返回判断双击操作结束了。
- 字段和表通过doc_num关联,表录制的时候录一行,如果要操作多行,在 表 页签复制doc_num为多行,即可按顺序操作表
- 录制的时候不要通过F4搜索帮助选择值,直接填入值,不要有无效操作,这样执行效率最高
- 录制过程如果想中断,点 结束录制,然后在弹出的文件选择框点 取消 就可以了。点 开始录制 开始一次新的录制,点 结束录制 结束一次录制,如果点了 结束录制 没有保存模板文件,可以继续点 结束录制 重复生成模板,不需要重复录制
- 尽量录复杂的场景,把可能弹出的屏幕都录到,如果屏幕多录了执行的时候找不到屏幕会自动跳过,不会卡住
- 如果多次操作同一笔单据,有可能保存好了sap的锁还没释放,再进入会报错被自己锁定,可以修改操作等待时间,delay = 1,则每次操作都等1秒,放在后台慢慢执行吧
版本
v1.0
初始版本,后续考虑要不要录windows窗口和网页,以及多窗口的并发。有需求可以交流一下
v1.1
- 兼容了事务码录入了/n 的场景
- 增加了对abap编辑器的支持,直接粘贴代码吧,手敲代码的换行还没处理
- 增加了终止功能,可以在执行过程中中断了
- 修复了连接超时断开,会话没有刷新的问题
v1.2
网盘中只更新了exe文件,用exe文件覆盖之前下载的文件夹中的exe
- 修复读值漏了前导零的bug
- 增加了对alv的支持:参考demo11 su01增加角色与参数文件
1.编辑alv单元格
2.插入行
V1.3
- 增加对spro tree操作的支持(DEMO13)
- 忽略窗口最大化的操作(maximize),可以后台执行,不会因为SAP窗口最大化而在前台弹出
v1.4
- 修复了SAP下拉框没有匹配到excel值的bug(demo fsp0)
- 修复了SAP单选框没有匹配到excel值的bug(demo fs00)
v1.5
增加alv场景时,把table control换行逻辑改到了,修复table不能自动写到第二行的bug
v1.7
处理了录制结束没点返回,也没点保存的表操作场景。如只在sm30界面操作数据,不从tcode进去,也没有保存
代码
import datetime
import sys
import configparser
# from math import ceil
import webbrowser
import openpyxl
import pyperclip
from openpyxl.utils import get_column_letter
import xlsxwriter
import json
import numpy as np
import re
import time
from operator import methodcaller
# import eventlet
import subprocess
import os
import tkinter as tk
from tkinter import ttk, font, messagebox, filedialog
from tkinter import *
import pandas as pd
import ttkbootstrap
from ttkbootstrap.constants import *
from ttkbootstrap import Style
import pythoncom
# import pypiwin32
import win32com
from win32com import client
from win32com.client import Dispatch
class lan_rpa:
def __init__(self):
# 布局
self.v_excption = 'lan stop'
self.v_column = 0
self.v_pady = 3
self.v_padx = (10, 0)
# 初始化sapgui
self.__sapgui__start()
# 变量
self.v_filename_script = 'Script6.vbs'
self.v_input = {}
self.v_con_sel = {'desc': '', 'client': '', 'user': '', 'langu': ''}
self.v_path_connection = 'connection.xlsx'
self.v_path_config = 'config.ini'
# 读连接
self.__connection_read(True)
# 读配置
self.v_config = configparser.ConfigParser()
self.v_config.read('config.ini')
def __tkinter_main(self):
root = tk.Tk()
self.v_root = root
root.wm_title('LAN RPA v1.2')
# root.iconphoto(False, tk.PhotoImage(file=self.__resource_path('lan.png')))
root.iconphoto(False, tk.PhotoImage(file='lan.png'))
ttkbootstrap.Style().theme_use(self.v_config['general']['theme'])
# 刷新事件
root.after(self.v_config['general']['refresh_interval'], self.__session_refresh)
# 布局
frame1 = tk.Frame(root) # 连接选择
self.v_frame1 = frame1
frame2 = tk.Frame(root) # 会话列表+资源列表
# paned = tk.PanedWindow(frame2, orient=tk.HORIZONTAL) # 默认是左右分布的
# paned.pack(fill=tk.BOTH, expand=1) # 放到主窗口,上下左右缩放
# frame_session = tk.Frame(paned) # 会话列表
# paned.add(frame_session)
# frame_explorer = tk.Frame(paned) # 资源列表
# paned.add(frame_explorer)
frame3 = tk.Frame(root)
frame4 = tk.Frame(root)
frame1.grid(row=0, column=0)
frame2.grid(row=1, column=0, sticky='w', padx=self.v_padx, pady=10)
frame3.grid(row=2, column=0, sticky='w', pady=10)
frame4.grid(row=3, column=0, sticky='w', pady=10)
# 菜单
self.__menubar(root)
# 连接选择
self.__tkinter_frame_con()
# 会话
# self.__tkinter_frame_session(frame_session)
self.__tkinter_frame_session(frame2)
# # 资源浏览器
# self.__tkinter_frame_explorer(frame_explorer)
# 收藏列表
# self.__tkinter_frame_favorite(frame3, root)
self.__tkinter_frame_record(frame3)
# 模板文件
self.__tkinter_frame_file(frame4)
# root.attributes('-topmost', True) #置顶
# 居中显示
dialog_width = 600
dialog_height = 400
# root.geometry('600x450')
self.__window_geometry(dialog_width, dialog_height, root)
root.mainloop()
def __window_geometry(self, dialog_width, dialog_height, root):
screenwidth = root.winfo_screenwidth()
screenheight = root.winfo_screenheight()
root.geometry("%dx%d+%d+%d" % (
dialog_width, dialog_height, (screenwidth - dialog_width) / 2, (screenheight - dialog_height) / 2))
def __tkinter_frame_record(self, frame3):
# ttk.Button(frame3, text='刷新', command=self.__session_refresh).grid(row=0, column=self.__column_next(0),
# padx=self.v_padx)
# tk.Button(frame3, text="刷新", command=self.__session_refresh).grid(row=0, column=self.__column_next(0),
# padx=self.v_padx)
tk.Button(frame3, text="初始化", command=self.__session_init).grid(row=0, column=self.__column_next(0),
padx=self.v_padx)
tk.Button(frame3, text="开始录制", command=self.__record_start).grid(row=0, column=self.__column_next(),
padx=self.v_padx)
tk.Button(frame3, text="结束录制", command=self.__record_end).grid(row=0, column=self.__column_next(),
padx=self.v_padx)
frame_progressbar = tk.Frame(frame3)
frame_progressbar.grid(row=0, column=self.__column_next(), padx=self.v_padx)
self.v_progress_bar = ttk.Progressbar(frame_progressbar, length='100', mode="determinate", orient=tk.HORIZONTAL)
self.v_progress_bar.grid(row=0)
self.v_progress_count = tk.Label(frame_progressbar, text='')
if self.v_config['general']['bar_total'] == 'True':
self.v_progress_count.grid(row=1)
self.v_progress_remain = tk.Label(frame3, text='')
self.v_progress_remain.grid(row=0, column=self.__column_next())
def __menubar(self, root):
menubar = tk.Menu(root)
menu_connection = tk.Menu(menubar)
menu_config = tk.Menu(menubar)
menu_about = tk.Menu(menubar)
menu_connection.add_command(label='编辑', command=self.__connection_edit)
menu_connection.add_command(label='重读', command=self.__connection_read)
menu_config.add_command(label='主题', command=self.__win_theme)
menu_config.add_command(label='编辑', command=self.__config_edit)
menu_config.add_command(label='重读', command=self.__config_read)
menu_about.add_command(label='帮助', command=self.__about_document)
menu_about.add_command(label='交流', command=self.__about_conmunicate) # 777363609
menubar.add_cascade(label='连接', menu=menu_connection)
menubar.add_cascade(label='配置', menu=menu_config)
menubar.add_cascade(label='关于', menu=menu_about)
root.config(menu=menubar)
def __tkinter_frame_session(self, frame_session):
# session info list treeview
col_text = ('id', 'SID', '客户端', 'TCODE', '用户', '语言')
col_name = ('id', 'sid', 'client', 'tcode', 'user', 'langu')
col_width = ('50', '50', '50', '180', '110', '80')
self.v_column_len = len(col_name) # 记下tv展示的列,因为SystemSessionId和SessionNumber在后面两列
xbar = ttk.Scrollbar(frame_session, orient='horizontal') # 水平滚动条
ybar = ttk.Scrollbar(frame_session, orient='vertical') # 垂直滚动条
xbar.pack(side=BOTTOM, fill=X)
ybar.pack(side=RIGHT, fill=Y)
tv = ttk.Treeview(frame_session, column=col_name, yscrollcommand=ybar.set, xscrollcommand=xbar.set,
show='headings') # 隐藏首列
self.v_tv_session = tv
for i in range(len(col_text)):
tv.column(col_name[i], width=col_width[i], anchor='n')
tv.heading(col_name[i], text=col_text[i])
ybar['command'] = self.v_tv_session.yview
xbar['command'] = self.v_tv_session.xview
tv.pack()
# 数据
# self.__session_refresh()
# 右键菜单
self.v_right_click_menu = tk.Menu(frame_session, tearoff=0)
self.v_right_click_menu.add_command(label='新会话', command=self.__session_create)
self.v_right_click_menu.add_command(label='关闭其他', command=self.__session_close_other)
self.v_right_click_menu.add_command(label='关闭全部', command=self.__session_close_all)
# self.v_right_click_menu.add_command(label='关闭后面', command=self.__session_close_back)
# 绑定事件
tv.bind('<Double-1>', self.__session_JumpForward) # 双击左键跳转窗口
tv.bind('<Button-3>', self.__session_right_click) # 右键菜单
# def __tkinter_connection_config(self):
# self.v_tkt_SAPConnectionConfig = tkt_SAPConnectionConfig()
# self.v_tkt_SAPConnectionConfig.open()
def main(self):
# self.__sapgui_init()
# session处理
# close_all()
# return
# logoin = self.login_init()
self.__tkinter_main()
return
def __session_close_other(self):
session_info = self.__session_info_selected()
for connection in self.v_application.Connections:
for session in connection.Sessions:
if session.Busy == False:
if session.Info.SystemSessionId != session_info.SystemSessionId:
session.StartTransaction('ex')
break
if session.Info.SessionNumber != session_info.SessionNumber:
session.findById("wnd[0]").close()
continue
# self.__session_refresh()
def __session_close_all(self):
for connection in self.v_application.Connections:
for session in connection.Sessions:
if session.Busy == False:
session.StartTransaction('ex')
break
# self.__session_refresh()
def __handle_connection_create(self):
self.__connection_create(self.v_input['desc'].get(), self.v_input['client'].get(),
self.v_input['user'].get(),
self.v_input['password'], self.v_input['langu'].get())
# self.__session_refresh()
def __session_create(self):
session = self.__session_selected()
session.CreateSession()
def __session_refresh(self):
session_info_sel = self.__session_info_selected() # 在刷新tv数据之前先取到选中的行,为了刷新之后重新选中会话
self.v_session_info_list = self.__session_info_list_get()
i = 1
data_list = []
if self.v_session_info_list:
for session_info in self.v_session_info_list:
try:
data_obj = (str(i), session_info.SystemName, session_info.Client, session_info.Transaction,
session_info.User,
session_info.Language,
session_info.SystemSessionId, session_info.SessionNumber)
data_list.append(data_obj)
i = i + 1
except Exception as e: # 可能窗口关闭,连接断开了
continue
self.v_tv_session.delete(*self.v_tv_session.get_children())
for i in range(len(data_list)):
self.v_tv_session.insert('', 'end', values=data_list[i])
# 选中会话
try:
for child in self.v_tv_session.get_children():
if session_info_sel.SystemSessionId == self.v_tv_session.item(child)['values'][self.v_column_len] \
and session_info_sel.SessionNumber == self.v_tv_session.item(child)['values'][
self.v_column_len + 1]:
self.v_tv_session.selection_set(child)
break
except Exception as e:
if self.v_tv_session.get_children():
self.v_tv_session.selection_set(self.v_tv_session.get_children()[0]) # 默认选中第一个窗口
# 继续刷新
self.v_root.after(self.v_config['general']['refresh_interval'], self.__session_refresh)
def __on_focus_in(self, event):
pass
# self.__session_list_refresh()
def __session_right_click(self, event):
row_id = self.v_tv_session.identify_row(event.y)
if row_id:
self.v_tv_session.selection_set(row_id)
self.v_right_click_menu.post(event.x_root, event.y_root)
def __session_JumpForward(self, event):
session = self.__session_selected()
if session:
session.findById("wnd[0]").Visualize(True)
session.findById("wnd[0]").JumpForward()
return session
def __session_selected(self):
session_info = self.__session_info_selected()
try: # 如果session关闭了,session_info.就会异常
session = self.__session_get(session_info.SystemSessionId, session_info.SessionNumber)
return session
except Exception as e:
messagebox.showinfo('选择会话', '当前会话繁忙或已关闭:' + str(e))
def __session_info_selected(self):
item_text = None
for item in self.v_tv_session.selection(): # 这里values全部转成str!!
item_text = self.v_tv_session.item(item, "values")
if item_text:
for session_info in self.v_session_info_list:
try:
if session_info.SystemSessionId == item_text[self.v_column_len] and str(
session_info.SessionNumber) == \
item_text[self.v_column_len + 1]:
return session_info
except Exception as e:
continue
def __tkinter_frame_con(self):
# Input控件
self.v_con_sel['desc'] = tk.StringVar()
self.v_input['desc'] = ttk.Combobox(self.v_frame1, textvariable=self.v_con_sel['desc'], state='readonly',
width='15')
self.v_con_sel['client'] = tk.StringVar()
self.v_input['client'] = ttk.Combobox(self.v_frame1, textvariable=self.v_con_sel['client'],
state='readonly',
width='3')
self.v_con_sel['user'] = tk.StringVar()
self.v_input['user'] = ttk.Combobox(self.v_frame1, textvariable=self.v_con_sel['user'], state='readonly',
width='12')
self.v_con_sel['langu'] = tk.StringVar()
self.v_input['langu'] = ttk.Combobox(self.v_frame1, textvariable=self.v_con_sel['langu'], state='readonly',
width='5')
# 布局
tk.Label(self.v_frame1, text='描述').grid(row=0, column=0, padx=self.v_padx)
self.v_input['desc'].grid(row=0, column=1)
tk.Label(self.v_frame1, text='客户端').grid(row=0, column=2, padx=self.v_padx)
self.v_input['client'].grid(row=0, column=3)
tk.Label(self.v_frame1, text='用户').grid(row=0, column=4, padx=self.v_padx)
self.v_input['user'].grid(row=0, column=5)
tk.Label(self.v_frame1, text='语言').grid(row=0, column=6, padx=self.v_padx)
self.v_input['langu'].grid(row=0, column=7)
# 设置连接值
self.__con_sel_refresh()
# 绑定事件:下拉框列表值
self.v_input['desc'].bind("<<ComboboxSelected>>", self.__input_selected_desc)
self.v_input['client'].bind("<<ComboboxSelected>>", self.__input_selected_client)
self.v_input['user'].bind("<<ComboboxSelected>>", self.__input_selected_user)
ttk.Button(self.v_frame1, text='打开', command=self.__handle_connection_create).grid(row=0, column=8,
padx=self.v_padx,
pady=10, )
def __con_sel_refresh(self):
self.v_input['desc']['value'] = list(self.v_con_list.keys())
self.v_input['desc'].current(0) # 设置默认值
self.__input_selected_desc(None)
def __input_selected_desc(self, event):
self.v_input['client'].delete(0, tk.END)
self.v_input['client']['value'] = list(self.v_con_list[self.v_con_sel['desc'].get()].keys())
self.v_input['client'].current(0) # 设置默认值
self.__input_selected_client(event)
self.__input_selected_user(event)
def __input_selected_client(self, event):
self.v_input['user'].delete(0, tk.END)
self.v_input['user']['value'] = list(self.v_con_list[self.v_con_sel['desc'].get()]
[self.v_con_sel['client'].get()].keys())
self.v_input['user'].current(0) # 设置默认值
self.__input_selected_user(event)
def __input_selected_user(self, event):
self.v_input['langu'].delete(0, tk.END)
self.v_input['langu']['value'] = list(self.v_con_list[self.v_con_sel['desc'].get()]
[self.v_con_sel['client'].get()][self.v_con_sel['user'].get()].keys())
self.v_input['langu'].current(0) # 设置默认值
self.v_input['password'] = \
self.v_con_list[self.v_con_sel['desc'].get()][self.v_con_sel['client'].get()][
self.v_con_sel['user'].get()][
self.v_con_sel['langu'].get()]
def __filedialog_select_vbs(self):
(filepath, tempfilename) = os.path.split(self.v_config['path']['vbs'])
selected_file_path = filedialog.askopenfilename(initialdir=filepath,
filetypes=[('脚本', '*.vbs')]) # 使用askopenfilename函数选择单个文件
if selected_file_path:
self.v_config['path']['vbs'] = selected_file_path
self.__config_write()
self.v_stringvar_vbs.set(selected_file_path)
def __filedialog_select_template(self):
(filepath, tempfilename) = os.path.split(self.v_config['path']['template'])
selected_file_path = filedialog.askopenfilename(initialdir=filepath,
filetypes=[('模板', '*.xlsx')]) # 使用askopenfilename函数选择单个文件
if selected_file_path:
self.v_config['path']['template'] = selected_file_path
self.__config_write()
self.v_stringvar_template.set(selected_file_path)
def __tkinter_frame_file(self, frame3):
v_width_input = 55
self.v_stringvar_vbs = tk.StringVar()
self.v_stringvar_template = tk.StringVar()
self.__refresh_file()
# 布局控件
# tk.Label(frame3, text="脚本").grid(column=0, row=0, pady=v_pady)
# tk.Entry(frame3, textvariable=self.v_stringvar_vbs, width=v_width_input).grid(column=1, row=0)
# tk.Button(frame3, text="选择", command=self.__filedialog_select_vbs).grid(row=0, column=2)
# tk.Button(frame3, text="解析", command=self.__file_explain).grid(row=0, column=3, padx=self.v_padx)
tk.Label(frame3, text="模板").grid(column=0, row=1, pady=self.v_pady, padx=self.v_padx)
tk.Entry(frame3, textvariable=self.v_stringvar_template, width=v_width_input).grid(column=1, row=1)
tk.Button(frame3, text="选择", command=self.__filedialog_select_template).grid(row=1, column=2)
tk.Button(frame3, text="打开", command=self.__file_open_template).grid(row=1, column=3, padx=self.v_padx)
tk.Button(frame3, text="执行", command=self.__button_file_execute).grid(row=1, column=4, padx=self.v_padx)
# button_exec = tk.Button(frame3, text="执行")
# button_exec.after(1,self.__button_file_execute)
# button_exec.grid(row=1, column=4, padx=self.v_padx)
tk.Button(frame3, text="终止", command=self.__button_stop).grid(row=1, column=5, padx=self.v_padx)
def __refresh_file(self):
# self.v_stringvar_vbs.set(self.v_config['path']['vbs'])
self.v_stringvar_template.set(self.v_config['path']['template'])
# def __config_get(self, key):
# try:
# return self.v_config.loc[self.v_config['key'] == key, 'value'].iloc[0]
# except Exception as e:
# return ''
def __file_close(self, file):
xlApp = Dispatch('Excel.Application')
try:
xlApp.DisplayAlerts = False # 设置不显示警告和消息框
except Exception as e:
messagebox.showerror('关闭excel', e)
return
# xlBook = xlApp.Workbooks.Open(file)
workbooks_n = xlApp.Workbooks.Count
# print(f'已打开工作簿的数量为:{workbooks_n}个')
if workbooks_n < 0: return
for i in range(1, workbooks_n + 1): # 工作簿索引从1开始
path_ = xlApp.Workbooks(i).Path
name_ = xlApp.Workbooks(i).Name
file_ = os.path.join(path_, name_)
# path = path_ + "\\backup_" + name_
# print(f'第{i}个excel的文件路径为:{path}')
if file == "/".join(file_.split("\\")): # 转义的\\替换回/
xlApp.Workbooks(i).Close() # 关闭当前打开的文件,不保存文件
# xlApp.Workbooks(i).Activate()
# # xlApp.Workbooks(i).SaveAs(path)
# xlApp.Workbooks(path).Close()
# xlApp.Quit() #关闭所有打开的excel文件
del xlApp
def __file_open_template(self):
try:
os.startfile(self.v_stringvar_template.get())
except Exception as e:
messagebox.showerror('打开文件', e)
def __file_execute(self):
self.v_stop = True
if not self.v_stringvar_template.get():
messagebox.showerror('文件不能为空')
return
self.v_config['path']['template'] = self.v_stringvar_template.get()
self.__config_write()
# self.v_root.state('icon') # 最小化
oper_start = 0
oper_end = 0
session = self.__session_JumpForward(None)
if not session:
return
self.__file_close(self.v_stringvar_template.get()) # 先关闭,再回写消息,再打开
dict = pd.read_excel(self.v_stringvar_template.get(), sheet_name=None,dtype='str') #str防止001.1这样的被当时float
# 循环操作
tabix = -1
total = len(dict['字段']['doc_num'])
start_time = time.time() # 记录程序开始运行时间
for doc_num in dict['字段']['doc_num']:
if not self.v_stop:
break
path_t = ''
log = ''
tabix = tabix + 1
self.v_root.update()
if str(doc_num) == 'nan':
continue
table_entry = False
for i in range(len(dict['操作']['path'])):
attr, equal, path, value = self.__file_oper_get(dict, i)
table_entry = self.__table_entry_begin(dict['操作'], i, attr, value,
table_entry) # 要传上一个table的Path,因为此时是win0
table_flag = self.__file_path_check_table(path)
alv_flag = self.__check_alv(attr)
if alv_flag:
table_flag = True
if not table_flag and not table_entry: # 字段:也可能不是字段,比如win0
if oper_start != 0: # 记录了表开始,现在回到了字段,说明表结束了
oper_end = i - 1
if attr == 'text': # 避免focus和double click等匹配到了值(或者参考表哪里用 = 判断?)
for key in dict['字段']:
if key == path + '+' + attr:
value = dict['字段'].loc[dict['字段']['doc_num'] == doc_num, key].iloc[
0] # dict.loc根据行+列返回一个列的series,取series第一个值
if re.findall('/okcd', path):
if value[:2] != '/n' and value[:2] != '/N':
value = '/n' + value
break
elif attr == 'insertText':
for key in dict['代码']:
if key == value: # 在 操作 页签的值记录了插入代码的位置
value_oper = value
if len(dict['代码'][key]) > 1:
value = '\n\r'.join(dict['代码'][key]) + ',' + value_oper
else:
value = str(dict['代码'][key][0]) + ',' + value_oper
break
else: # 表:第一行正常循环,多行后面处理
if oper_start == 0:
oper_start = i
# table = path.split(',')[0]
for key in dict: # 循环每个sheet匹配表字段
if re.findall('表', key):
for key2 in dict[key]: # 循环取列名
if key2 == path.split(',')[0] + '+' + attr: # 匹配一次列名就定位到表了
row_doc_tab = len(dict[key].loc[dict[key]['doc_num'] == doc_num, 'doc_num'])
sheet_tbl = key
break
else: # 里面break了则触发外面break跳出第二层循环
continue
break
# 开始执行
if oper_start != 0 and oper_end != 0: # 表:循环完了,开始一次性执行表全部条目
pagesize = 0
row_current = 0
for x in range(row_doc_tab): # 条目数
# 翻页:规定表和表不关联,处理完表1再处理表2,所以在表条目维度翻页就可以
attr_t, equal_t, path_t, value_t = self.__file_oper_get(dict, oper_start)
if self.__file_path_check_table(path_t): # 进了表才考虑定位行
if 'getAbsoluteRow' in attr_t: # 选表行
row_start = int(attr_t.split('(')[1].strip(')')[0]) # 选中绝对行要不要翻页的?
path_tc = path_t
else:
row_start = int(path_t.split(',')[1].strip(']')[0]) # 开始录制的表行(可能不是从第一行开始的)
path_tc = re.findall(r"(.+)/", path_t)[0]
if pagesize == 0:
pagesize = session.findById(path_tc).verticalScrollbar.pagesize
if not row_current:
row_current = row_start
# div_mod = divmod(row_current, pagesize)
# if div_mod[0] != 0 and div_mod[1] == 0: # 翻页:用翻页按钮是因为发现CA01不填第二行无法定位到第二行
if row_current == pagesize: # 翻页
log_r = self.__script_exec('sendVKey', '', session.findById("wnd[0]"), '82', "wnd[0]")
if log_r:
log = log + log_r
row_current = row_start
for oper_now in range(oper_start, oper_end + 1): # range不包含上限
attr_t, equal_t, path_t, value_t = self.__file_oper_get(dict, oper_now)
if self.__file_path_check_table(path_t):
if 'getAbsoluteRow' in attr_t: # 选表行
attr_t = attr_t.split('(')[0] + '(' + str(row_current) + ')' + attr_t.split(')')[
1] # path_t就是tc的位置不用处理,但是行在attr上
elif 'modifyCell' in attr_t or 'insertRows': #修改alv单元格
pass
else:
path_t = path_t.split(',')[0] + ',' + str(row_current) + ']'
try:
obj_t = session.findById(path_t)
except Exception as e:
pass
for key2 in dict[sheet_tbl]: # 循环取列名
if key2 == path_t.split(',')[0] + '+' + attr_t \
or key2 == path_t + '+' + attr_t: # 选中行
break
if equal_t == 'X': # 有=号才是可输入字段
value_t = dict[sheet_tbl].loc[dict[sheet_tbl]['doc_num'] == doc_num, key2].iloc[
x] # 取series第x个值
elif path_t == 'wnd[0]' or attr_t == 'insertRows': # 双击/ alv插入行
value_t = value_t
elif attr_t == 'modifyCell':
value_t = [row_current,re.findall(r"\"(.+?)\"", value_t.rsplit(',',2)[1])[0],dict[sheet_tbl].loc[dict[sheet_tbl]['doc_num'] == doc_num, key2].iloc[
x]] #行/列名/值
else:
value_t = 'nan'
log_r = self.__script_exec(attr_t, equal_t, obj_t, value_t, path_t)
if log_r:
log = log + log_r
row_current = row_current + 1
oper_start = 0
oper_end = 0
if oper_start == 0: # 非表直接执行
try:
obj = session.findById(path) # 到执行的时候才可以取,因为可能前面在执行表
except Exception as e:
pass
log_r = self.__script_exec(attr, equal, obj, value, path)
if log_r:
log = log + log_r
# 判断表的双击是否返回或者保存了
table_entry = self.__table_entry_end(path, attr, value, table_entry)
# 回写消息
msg_type = session.findById('wnd[0]/sbar').MessageType
msg_text = session.findById('wnd[0]/sbar').Text
dict['字段'].loc[
tabix, 'msg_type'] = msg_type # 正确改值方式,否则:SettingWithCopyWarning: A value is trying to be set on a copy of a slice from a DataFrame
dict['字段'].loc[tabix, 'msg_text'] = msg_text
if self.v_config['general']['log'] == 'True':
dict['字段'].loc[tabix, 'log'] = log
end_time = time.time() # 记录程序结束运行时间
self.v_progress_bar['value'] = (tabix + 1) / total * 100
self.v_progress_count['text'] = str(tabix + 1) + '/' + str(total)
self.v_progress_remain['text'] = '剩余时间:' + str(
datetime.timedelta(
seconds=int((end_time - start_time) / (tabix + 1) * (total - (tabix + 1))))) # int向下取整
try: # 只覆盖一个sheet 只有openpyxl有 a mode
with pd.ExcelWriter(self.v_stringvar_template.get(), mode="a", engine="openpyxl",
if_sheet_exists="replace", ) as writer:
pd.DataFrame(dict['字段']).to_excel(writer, sheet_name="字段", index=False)
# 按列遍历:单元格改回文本格式
for x in range(1, writer.sheets['字段'].max_row + 1): # 先改有文本的单元格,再改列,直接改列不起作用
for y in range(0, writer.sheets['字段'].max_column):
writer.sheets['字段'][x][y].number_format = '@'
for i in range(1, writer.sheets['字段'].max_column + 1):
writer.sheets['字段'].column_dimensions[get_column_letter(i)].number_format = '@'
except Exception as e:
messagebox.showerror('消息回写excel出错', e)
self.__file_open_template()
def __script_exec(self, attr, equal, obj, value, path): # path可选,用于报错,可以优化成传入path和session
value = str(value) # 避免1.0被当做float丢失.0了
if attr == 'ResizeWorkingPane':
return
elif 'getAbsoluteRow' in attr:
attr_1 = attr.split('(')[0]
value_1 = attr.split('(')[1].split(')')[0]
obj = getattr(obj, attr_1)(value_1) # 动态调用只能分两步:先取table row,再设置为True
attr = attr.split('.')[1]
time.sleep(int(self.v_config['general']['delay']))
try:
# if str(equal) != 'nan':
if str(equal) == 'X':
setattr(obj, attr, value) # 设置属性值
else:
if str(value) == 'nan':
getattr(obj, attr)() # 直接调用方法
else:
if obj.type == 'GuiShell': # 如果是abap编辑器则传入多个参数
if attr == 'insertText': # 代码直接转tuple可能有特殊字符
getattr(obj, attr)(value.rsplit(',',2)[0], int(value.rsplit(',',2)[1]),int(value.rsplit(',',2)[2]))
elif attr == 'insertRows':
getattr(obj, attr)(value) # 调用方法传参
else:
value = eval(value) # 转成tuple
getattr(obj, attr)(*value) # 加* tuple变参数
else:
getattr(obj, attr)(value) # 调用方法传参
except Exception as e:
return str(e) + str(path) + ':' + str(attr) + ':' + str(value) + ';'
def __file_oper_get(self, df, i):
path = df['操作']['path'][i]
attr = df['操作']['attr'][i]
equal = df['操作']['equal'][i]
value = df['操作']['value'][i]
if attr == 'insertText' or attr == 'modifyCell': # 如果是代码则不去掉引号
pass
else:
value_n = re.findall(r"\"(.+?)\"", str(value)) # 去掉引号
if value_n:
value = value_n[0]
return attr, equal, path, value
def __file_explain(self):
# v_len_max = 30000 #excel单元格最大字符数 32000
if self.v_config['general']['template_no_ask'] == 'False': # 是否问模板路径
self.v_path = self.__filedialog_create_template()
if self.v_path:
self.v_stringvar_template.set(self.v_path)
else:
return
operation_list = []
try:
with open(self.v_path_script, encoding='UTF-16') as f: # 代码保存出来就是UTF-16不知道为啥
for line in f.readlines(): # readlines(),函数把所有的行都读取进来;
line = line.strip() # 删除行后的换行符,img_file 就是每行的内容啦
path = re.findall(r"session.findById\(\"(.+?)\"\).", line) # (" ").
if not path:
continue
attr = re.findall(r"\).(.+?) ", line) # 测试空格不用转义
if not attr: # 没有值的情况,比如setFocus
attr = re.findall('(?<=\).).*$', line)
value = re.findall('(?<=\s).*$', line) # \s空格 取空格右边全部
equal = ''
if value:
# equal = re.search('=', value[0]) # 值中找=号
# if equal:
if value[0][:1] == '=': # 如果第一位是等于号(值中可能也有=号)
equal = 'X'
value = re.findall('(?<=\s).*$', value[0]) # \s空格 取空格右边全部
path = self.__array_get_first(path)
attr = self.__array_get_first(attr)
value = self.__array_get_first(value)
operation = (path, attr, equal, value)
operation_list.append(operation)
except Exception as e:
messagebox.showerror('解析文件', e)
return
dict_field = {}
dict_tables = {}
dict_code = {} # 代码
dict_operation = {'path': {}, 'attr': {}, 'equal': {}, 'value': {}}
# 开始解析操作
i = -1
table_entry = False
df_operation_list = pd.DataFrame(operation_list)
df_operation_list.columns = ['path', 'attr', 'equal', 'value']
for oper in operation_list:
i = i + 1
path = oper[0]
attr = oper[1]
equal = oper[2]
value = oper[3]
path_t = '' # 上一个表的path
if attr == 'caretPosition' or attr == 'verticalScrollbar.position' or attr == 'currentCellColumn':
continue
elif attr == 'insertText': # 代码:先写 代码 页签,再写操作
value_code = value.rsplit(",", 2)[0] # 从右向左按 , 拆两次
code_list = value_code.split(' + vbCr + "" + vbLf + ') # 根据换行符号拆开
for x in range(len(code_list)):
code_list[x] = code_list[x][1:len(code_list[x]) - 1] # 去掉首尾引号
dict_code[value.rsplit(",", 2)[1] + ',' + value.rsplit(",", 2)[
2]] = code_list # 录制的脚本中文会变成?,需要录完了在excel自己覆盖一下数据
value = value.rsplit(",", 2)[1] + ',' + value.rsplit(",", 2)[2] # 如果插入代码,则把位置写到操作页签,把值写入代码页签
dict_operation['path'][i] = path
dict_operation['attr'][i] = attr
dict_operation['equal'][i] = equal
dict_operation['value'][i] = value
# 判断表的双击是否跳转屏幕了
# oper_next = operation_list[i + 1]
table_entry = self.__table_entry_begin(df_operation_list, i, attr, value, table_entry)
table_flag = self.__file_path_check_table(path)
alv_flag = self.__check_alv(attr)
if alv_flag: #alv跟表一样处理
table_flag = True
if table_flag: # 如果是跳转的就沿用上一个表名,所以没有第一个表没有填值,只是双击的情况也需要记录表名
if 'getAbsoluteRow' in attr: # 选表行
table = path
tabix = re.findall(r"\((.+?)\)", attr)[0]
else: # 表字段
if not alv_flag: #table control
table = re.findall(r"(.+)/", path)[0] # 贪婪匹配:从整个字符去掉最后一个匹配
tabix = re.findall(r",(.+?)\]", path)[0]
else: #alv
table = path
tabix = value_code = value.rsplit(",", 2)[0] # 从右向左按 , 拆两次,第一个值为行
if equal == 'X' or attr == 'setFocus' or attr == 'modifyCell': # setFocus必须要有,有些表没有输入直接双击的,不然匹配不到表
if alv_flag:
value = value.rsplit(',',2)[2]
quotes = []
quotes = re.findall(r"\"(.+?)\"", value) # 值中找引号
if quotes:
value = re.findall(r"\"(.+?)\"", value)[0]
elif '"' in value: # 如果引号中间是空,正则也是找不到的
value = ''
if table_flag or table_entry: # 当前为表或者是表双击跳转的屏幕
if table_flag and not alv_flag: # 表上还是要拆分行,只保留列;alv列不写入path了,直接读operation的value取到列
path = path.split(',')[0]
if not dict_tables.__contains__(table):
dict_tables[table] = {}
if not dict_tables[table].__contains__(path + '+' + attr):
dict_tables[table][path + '+' + attr] = {}
dict_tables[table][path + '+' + attr][tabix] = value
else: # 字段
dict_field[path + '+' + attr] = {0: value} # 一个path可能有多个属性操作,拼接起来作为字段名
# 判断表的双击是否返回或者保存了
table_entry = self.__table_entry_end(path, attr, value, table_entry)
dict_field['msg_type'] = {0: ''}
dict_field['msg_text'] = {0: ''}
dict_field['log'] = {0: ''}
# 写入excel
self.__file_close(self.v_stringvar_template.get()) # 先关闭,避免写入的时候占用了
excel_colum = []
try: # with就不用再save了
with pd.ExcelWriter(self.v_stringvar_template.get(),
engine='xlsxwriter') as writer: # xlsxwriter才能写格式,但是xlsxwriter只能写不能读(所以不能修改)
df1 = pd.DataFrame(dict_field)
df1.insert(0, 'doc_num', 1)
df1.to_excel(writer, sheet_name='字段', index=False) # 不要第一列的序号
excel_colum.append(len(df1.keys()))
i = 0
for key in dict_tables.keys():
i = i + 1
dict_table = dict_tables[key]
df2 = pd.DataFrame(dict_table)
df2.insert(0, 'doc_num', 1)
df2.to_excel(writer, sheet_name='表' + str(i), index=False)
excel_colum.append(len(dict_table.keys()))
# df_code = pd.DataFrame(dict_code)
df_code = pd.DataFrame(pd.DataFrame.from_dict(dict_code, orient='index').values.T,
columns=list(dict_code.keys())) # dict_code列长度可能不一样需要特殊处理
df_code.to_excel(writer, sheet_name='代码', na_rep='', index=False)
excel_colum.append(len(df_code.keys()))
df3 = pd.DataFrame(dict_operation)
df3.to_excel(writer, sheet_name='操作', na_rep='', index=False) # na_rep 缺失数据表示
excel_colum.append(len(df3.keys()))
# 设置单元格格式为文本
workbook = writer.book
format_text = workbook.add_format({'num_format': '@'})
i = -1
for sheet_name in writer.sheets:
i = i + 1
writer.sheets[sheet_name].set_column(0, excel_colum[i] - 1, None, format_text) # 不设置宽度
except Exception as e:
# raise e
messagebox.showerror('写入excel', str(e))
else:
self.__file_open_template()
def __table_entry_end(self, path, attr, value, table_entry):
if table_entry and ((attr == 'sendVKey' and value == '11') or path == "wnd[0]/tbar[0]/btn[11]"): # 保存
messagebox.showerror('请检查操作', '双击表跳转之后要返回,不要直接保存')
raise self.v_excption
if table_entry and ((attr == 'sendVKey' and value == '3') or path == "wnd[0]/tbar[0]/btn[3]" # 返回
):
table_entry = False
return table_entry
def __table_entry_begin(self, operation_list, i, attr, value, table_entry): # 双击就关联表,否则就算点了button 2进到明细也认为互不关联
if i == 0:
return
oper_path_pre = operation_list['path'][i - 1]
if operation_list['path'][i] == 'wnd[0]' and (attr == 'sendVKey' and value == '2') and i + 1 < len(
operation_list): # 上一个path_t存在表名是表上双击的
oper_path_next = operation_list['path'][i + 1]
table_next = oper_path_next.split('/')[0:len(oper_path_next.split('/')) - 1]
table_pre = oper_path_pre.split('/')[0:len(oper_path_pre.split('/')) - 1]
if table_next != table_pre: # 其实这样也不准确,因为跳过去可能直接翻页了,这样取到的也不是屏幕名
table_entry = True
return table_entry
def __file_path_check_table(self, path):
if path.split('/')[len(path.split('/')) - 2][:3] == 'tbl' \
or path.split('/')[len(path.split('/')) - 1][:3] == 'tbl': # 如果是表选中行则最后一个不是字段是表名
return TRUE
else:
return False
def __check_alv(self, attr):
if attr == 'modifyCell' or attr == 'insertRows':
return TRUE
else:
return False
def __array_get_first(self, field):
if field:
field = field[0]
else:
field = ''
return field
def __connection_read(self, init=False):
con = {}
df = pd.read_excel(self.v_path_connection, sheet_name=None)
# 连接
for i in range(len(df['连接']['desc'])):
desc = str(df['连接']['desc'][i])
client = str(df['连接']['client'][i])
user = str(df['连接']['user'][i])
langu = str(df['连接']['langu'][i])
password = str(df['连接']['password'][i])
if not con.__contains__(desc):
con[desc] = {}
if not con[desc].__contains__(client):
con[desc][client] = {}
if not con[desc][client].__contains__(user):
con[desc][client][user] = {}
if not con[desc][client][user].__contains__(langu):
con[desc][client][user][langu] = password
self.v_con_list = con
if not self.v_con_list:
self.v_con_list = {"未配置": {"300": {"abap01": {"zh": "123456"}}}}
# with open(self.v_path_connection, 'w') as fp:
# json.dump(self.v_con_list, fp)
if not init:
self.__con_sel_refresh()
def __config_write(self):
with open(self.v_path_config, "w+") as f:
self.v_config.write(f)
def __win_theme(self):
# root = ttkbootstrap.Window()
root = tk.Tk()
root.wm_title('主题')
# root.iconphoto(False, tk.PhotoImage(file='lan.png'))
style = ttkbootstrap.Style()
theme_names = style.theme_names() # 以列表的形式返回多个主题名
theme_selection = ttkbootstrap.Frame(root, padding=(10, 10, 10, 0))
theme_selection.pack(fill=X, expand=YES)
lbl = ttkbootstrap.Label(theme_selection, text="主题:")
theme_cbo = ttkbootstrap.Combobox(
master=theme_selection,
text=style.theme.name,
values=theme_names,
)
theme_cbo.pack(padx=10, side=RIGHT)
theme_cbo.current(theme_names.index(style.theme.name))
lbl.pack(side=RIGHT)
def change_theme(event):
theme_cbo_value = theme_cbo.get()
style.theme_use(theme_cbo_value)
# theme_selected.configure(text=theme_cbo_value)
theme_cbo.selection_clear()
self.v_config['general']['theme'] = theme_cbo_value
self.__config_write()
theme_cbo.bind('<<ComboboxSelected>>', change_theme)
# theme_selected = ttkbootstrap.Label(
# master=theme_selection,
# text="litera",
# font="-size 24 -weight bold"
# )
# theme_selected.pack(side=LEFT)
self.__window_geometry(200, 50, root)
root.mainloop()
def __filedialog_create_template(self):
path = filedialog.asksaveasfilename(filetypes=[('excel文件', ['.xlsx'])],
title='生成模板',
initialfile='template.xlsx'
)
return path
def __resource_path(self, relative_path):
if getattr(sys, 'frozen', False): # 是否Bundle Resource
base_path = sys._MEIPASS
else:
base_path = os.path.abspath(".")
return os.path.join(base_path, relative_path)
def __session_info_list_get(self):
out_list = []
try: # 有可能超时连接断开,一个连接都没有了
for connection in self.v_application.Connections:
for session in connection.Sessions:
outdict = {}
try: # 有可能超时断开,刚好执行到session.Info,就出异常了
if session.Busy == False:
session_info = session.Info
outdict = session_info
out_list.append((outdict))
except Exception as e:
pass
except Exception as e:
pass
return out_list
def __sapgui__start(self):
self.v_path_sapgui = "C:\Program Files (x86)\SAP\FrontEnd\SAPgui\saplogon.exe"
if not self.__sapgui_check_logon():
if self.v_path_sapgui and os.path.exists(self.v_path_sapgui):
subprocess.Popen(self.v_path_sapgui)
time.sleep(7)
return self.__sapgui_check_logon()
else:
raise ValueError('Path to saplogon.exe not found')
def __sapgui_check_logon(self):
try:
self.v_application = win32com.client.GetObject('SAPGUI').GetScriptingEngine
except Exception as e:
return False
else:
return True
def __connection_create(self, desc, client, user, pwd, langu=None, close_conn=False, change_pwd=True):
try:
self.v_application.OpenConnection(desc, True)
except pythoncom.com_error as error:
hr, msg, exc, arg = error.args
sap_error = "COM: {0} ({1})".format(msg, hr)
msg = "Connection '{0}' not found. {1}".format(desc, sap_error)
# raise RuntimeError(msg)
messagebox.showerror('打开连接错误', error)
try:
sap_session = self.v_application.Children(self.v_application.Children.Count - 1).Children(0)
except Exception as e:
messagebox.showerror('获取会话', '检查SAP是否开启了sapgui script(RZ11配置)')
return
sap_session.findById("wnd[0]").JumpBackward()
sap_session.findById('wnd[0]/usr/txtRSYST-BNAME').text = user
sap_session.findById('wnd[0]/usr/pwdRSYST-BCODE').text = pwd
sap_session.findById('wnd[0]/usr/txtRSYST-MANDT').text = client
sap_session.findById('wnd[0]/usr/txtRSYST-LANGU').text = langu
sap_session.findById('wnd[0]').sendVKey(0)
# msg = sap_session.findById('wnd[0]/sbar')
# if msg and msg[0] == "E":
# sap_session.findById('wnd[0]').sendVKey(15) #shift + f3
# raise RuntimeError("Could not log to the SAP server. {0}".format(msg[2]))
# 重复登录
try:
sap_session.findById("wnd[1]/usr/radMULTI_LOGON_OPT2")
except pythoncom.com_error as error:
pass
else:
sap_session.findById("wnd[1]/usr/radMULTI_LOGON_OPT2").select()
sap_session.findById("wnd[1]/tbar[0]/btn[0]").press()
# copyright弹框
try:
sap_session.findById("wnd[1]").sendVKey(0)
except pythoncom.com_error as error:
pass
# return_pwd = SAPLogonPwd.is_change_password_window_while_connect(sap_session, change_pwd=change_pwd)
# SAPNewSession.close_new_windows_while_connect(sap_session)
return sap_session
def __session_get(self, SystemSessionId, SessionNumber):
for connection in self.v_application.Connections:
for session in connection.Sessions:
if session.Busy == False:
if session.Info.SystemSessionId == SystemSessionId and session.Info.SessionNumber == SessionNumber:
return session
def __session_init(self):
session = self.__session_selected()
session.StartTransaction('')
# self.__session_refresh()
def __record_start(self):
session = self.__session_selected()
self.v_session = session # 获得session的句柄,这样方法执行完了,session的record可以保持不丢失
session.Record = False
session.SaveAsUnicode = True # 会存UTF-16
session.RecordFile = self.v_filename_script
self.v_path_script = session.RecordFile # 本地变量丢失句柄会清掉/关闭Record会丢掉先记下来
self.v_config['path']['vbs'] = self.v_path_script
self.__config_write()
session.findById("wnd[0]").JumpForward()
session.Record = True # 执行这句之后就会创建文件,之后前台操作都是实时写文件的
def __record_end(self):
session = self.__session_selected()
if not session:
return
if session.RecordFile:
self.v_path_script = session.RecordFile
else:
self.v_path_script = self.v_config['path']['vbs']
session.Record = False # 会清空 session.RecordFile
self.v_session = None # 释放session,此时gui右下角的脚本状态会立即消失
self.__file_explain()
# class SAPGUI:
# def __tv_data_set(self,tv, data):
def __column_next(self, column=None):
if column != None:
self.v_column = column
self.v_column = self.v_column + 1
return self.v_column
def __connection_edit(self):
os.startfile(self.v_path_connection)
def __config_edit(self):
os.startfile('config.ini')
def __config_read(self):
self.v_config.read('config.ini')
def __button_file_execute(self):
# self.__file_execute()
self.v_root.after(1, self.__file_execute)
def __button_stop(self):
messagebox.showinfo('终止', '收到,当前条目执行完就会停止执行')
self.v_stop = False
def __about_document(self):
# webbrowser.open('https://xiaolan.blog.csdn.net/article/details/128494871')
webbrowser.open('https://blog.csdn.net/cylcylcylcylwo/article/details/128494871')
def __about_conmunicate(self):
pyperclip.copy('777363609') # 相当如写入到剪切板
if __name__ == "__main__":
lan_rpa = lan_rpa()
lan_rpa.main()