평소 SAP 개발은 Claude Code에 ADT를 붙여 소스를 읽고 고치고, SQL로 테이블을 조회한다. ADT가 닿는 곳은 오브젝트까지다. 트랜잭션 화면을 열고 값을 넣고, F110 같은 자동지급을 돌리는 일은 여전히 SAP GUI에서 사람이 해야 했다.
그래서 SAP GUI Scripting을 붙여 봤다. 처음 목적은 화면 순서대로 현업 매뉴얼을 만드는 것이었다. 설정을 보던 날, 해외 담당자의 자동지급 문의가 이미 와 있었다. 첫 실전은 운영 조회, QA 재현, 지급 실행, 안내 문서까지 이어진 하루가 됐다. 회사명, 시스템 ID, 사람 이름, 전표 번호, 금액은 빼 두었다.
- 로그인은 사람이 하고, 에이전트는 이미 열린 SAP GUI 세션에만 붙는다. 비밀번호는 에이전트를 거치지 않는다.
- 운영은 조회만 했고, 쓰기는 미리 정한 QA 클라이언트에서만 했다.
- 다섯 번의 분석이 낸 첫 답은 원인은 맞혔지만 따라 할 수 없는 절차였다. QA에서 화면을 열어 본 뒤에야 안내가 맞았다.
에이전트는 사람이 열어 둔 GUI에 붙는다
구성은 단순하다. Claude Code가 PC에서 cscript로 VBScript를 실행하고, 스크립트는 실행 중인 SAP Logon에 COM으로 붙는다. 객체는 GuiApplication, GuiConnection, GuiSession이고, 화면 요소는 findById로 찾는다.
Set app = GetObject("SAPGUI").GetScriptingEngine
For Each conn In app.Children
For Each s In conn.Children
WScript.Echo s.Info.SystemName & "/" & s.Info.Client
Next
Next
- 로그인은 내가 한다. 에이전트는 열어 둔 세션을 찾을 뿐이고, 계정 정보는 넘기지 않는다.
- 이 PC에는 Python
pywin32가 없어서 VBScript를 썼다. 설치 없이 바로 실행할 수 있었다. cscript콘솔은 한글 Windows에서 CP949라 한글이 깨진다. 결과는ADODB.Stream으로 UTF-8 파일에 쓰고, 에이전트가 그 파일을 읽게 했다.- GUI에서 바꾼 값은 ADT의 SQL로 다시 확인했다. 화면에 저장됐다고 나와도 그대로 믿지 않았다.
스크립팅은 서버와 클라이언트 둘 다 켜져 있어야 한다
클라이언트는 SAP GUI 옵션의 접근성 및 스크립팅에서 켠다. 이 PC는 레지스트리 UserScripting=1로 이미 켜져 있었다. 스크립트가 붙을 때마다 뜨는 알림 팝업 두 개는 내가 직접 껐다. 보안 옵션이라 에이전트에게 레지스트리를 건드리게 하지 않았다. 팝업이 떠 있으면 스크립트는 엉뚱한 창을 보고 The control could not be found by id로 죽는다. 첫 실패가 나면 팝업부터 보는 편이 빠르다.
서버 스위치는 sapgui/user_scripting이다. ADT로는 RZ11 화면의 현재 값을 직접 읽지 못했다. 파라미터 변경 이력 테이블 PAHI는 SQL로 볼 수 있다.
SELECT hostname, pardate, parname, parvalue
FROM pahi
WHERE parname LIKE 'sapgui/user_scripting%'
가장 최근 행만 보면 개발은 FALSE, QA는 TRUE였다. 이력은 현재 값이 아니다. 마지막에는 로그인한 세션의 GuiConnection.DisabledByServer를 읽었다. 그렇게 보니 운영 세션도 스크립팅이 열려 있었다. 읽기 전용으로만 두는 sapgui/user_scripting_set_readonly 같은 파라미터는 이번 확인에 넣지 않았다.
운영에서 스크립트가 된다는 것은 편하면서 위험하다. 그래서 규칙을 먼저 적었다. 운영에서는 SE16N, FB03 같은 조회만 하고 저장이나 실행은 누르지 않는다. 쓰기는 미리 정한 QA 클라이언트에서만 한다.
조회만으로 지급 묶음이 갈라진 자리를 좁혔다
스크립팅을 보던 날, 해외 담당자가 자동지급(F110)을 돌리면 거래처 대변메모(Credit)가 반영되지 않는다고 했다. 회신에는 Credit 한 건과 송장 두 건이 적혀 있었다.
운영에서는 조회만으로 원인을 좁혔다. 에이전트가 SE16N으로 세 전표의 지급 관련 필드를 읽었다. FB03의 추가 데이터 화면은 첫 답을 검증할 때 봤고, 그 화면이 첫 답의 해결 절차를 뒤집었다. 그 사이에 GUI 자동화의 함정을 여러 번 밟았다.
SE16N이 컬럼을 숨긴다. BSEG를 열었더니 300개가 넘는 컬럼 중 32개만 보였다. 계정에 걸린 기본 ALV 레이아웃 때문이었고, 레이아웃 칸을 비워도 기본 레이아웃이 남았다. 기본 레이아웃이 없는 BSIK, BSAK로 우회했다. 이후에는 ColumnCount를 먼저 본다.
선택조건의 행 번호는 고정이 아니다. SE16N 선택조건은 화면에 21행만 보이고, 필드 순서도 테이블마다 다르다. 행 번호나 설명에 기대지 않고 기술명 열 GS_SELFIELDS-FIELDNAME에서 필드를 찾아 그 행에 값을 넣었다.
셀 단위 읽기는 느리다. ALV를 GetCellValue로 한 칸씩 읽으면 칸마다 COM 왕복이 생긴다. 행과 열이 조금만 많아도 10분을 넘겼고, 같은 세션의 다른 스크립트는 타임아웃이 났다. 필요한 컬럼만 읽고 조건을 좁혔다. F110 로그 같은 일반 목록은 %pc로 파일을 저장한 뒤 그 파일을 읽게 바꿨다.
팝업은 다른 창이다. FB03 추가 데이터는 wnd[1]이다. 처음에는 wnd[0]만 찍어 뒤에 깔린 화면만 남았다.
조회 결과는 분석 셋과 검토 둘에게 나눠 맡겼다. F110은 지급 프로그램이 묶음으로 보는 값이 같은 미결 항목만 한 번에 지급한다. 그 값에는 파트너은행유형(BVTYP), 주거래은행(HBKID), 통화, 지급 방법이 들어 있다. 거래처의 그룹핑 키가 달라도 묶음은 갈라진다. 이 사례에서 실제로 갈라진 자리는 BVTYP과 HBKID였다.
송장 두 건에는 BVTYP이 있고 Credit에는 비어 있었다. Credit에는 HBKID가 있고 송장에는 없었다. Credit은 전기키 21이라 공급업체 계정에서는 차변이다. Credit만 남은 묶음은 차변 잔액이 되어 지급할 수 없다. 그때 남는 메시지는 FZ501, “No pymt possible because items with a debit bal. still exist; see job log”이다. 송장 묶음은 Credit 없이 지급 대상이 된다. 운영의 지급 실행 기록에는 이 거래처가 없어서, 운영에서 이 증상을 다시 돌리지는 못했다.
서버 시간이 한국 시간(KST)이면 입력일(CPUDT)은 한국 날짜다. 입력자가 미국 동부일 때, 서머타임(EDT)은 현지 11:00부터, 표준시(EST)는 현지 10:00부터 CPUDT가 현지 날짜의 다음 날이 된다. F110의 Docs entered up to는 전기일이 아니라 이 입력일과 비교한다. 그래서 그 시각부터 입력한 전표는, 현지 날짜로 돌린 당일 지급에서 빠질 수 있다.
첫 답은 단계마다 다른 곳에서 틀렸다
- 초안은 FB02로 Credit에 파트너은행유형을 넣고 주거래은행을 지우라고 했다. 운영 FB03의 추가 데이터에는 그 칸이 없었다. 전기키 21의 필드 상태와 조정계정의 필드 상태를 보니, 그 시스템에서는 파트너은행유형이 숨김이었다. FB65와 FB02에도 칸이 없었다. 전기키 21이 항상 그 칸을 숨기는 것은 아니다. 따라 할 수 없는 안내였고, 1차 문서를 쓰기 전에 뺐다.
- 에이전트는 QA에 데이터가 없다고 보고했다. ADT SQL은 연결의 로그온 클라이언트를 본다. 데이터는 다른 클라이언트에 있었다.
USING CLIENT를 붙이니 그대로 나왔다. - 1차 안내에는 주거래은행은 원인이 아니라고 썼고, 잔여 항목은 전기키 31이니 FB02로 파트너은행유형을 넣으면 된다고 썼다. 둘 다 QA 재현에서 뒤집혔다.
다섯이 교차 검토해도, 그때의 결론은 운영 데이터를 읽고 만든 추론이었다. 원인은 맞았고, 사람이 따라 할 절차는 화면을 열어 보기 전이었다.
QA에서 세 번 돌려 원인을 갈랐다
쓰기부터는 막을 곳이 필요하다. QA용 헬퍼는 불러올 때 정한 QA 클라이언트의 세션만 잡고, 없으면 그 자리에서 끝난다. 작업을 시작할 때와 저장 직전에 Guard로 시스템과 클라이언트를 한 번 더 본다. 이 Guard에는 구멍이 있었고, 뒤에서 다시 말한다.
Set session = GetSession(QA_SID, QA_CLIENT)
Sub Guard()
If session.Info.SystemName <> QA_SID _
Or session.Info.Client <> QA_CLIENT Then
Err.Raise vbObjectError + 9, "Guard", "not QA"
End If
End Sub
1차 실행은 증상 재현이었다. 송장 두 건을 FB02로 운영과 같은 상태로 맞추고, FB65로 전기키 21 Credit을 입력했다. 지급 탭에 파트너은행유형 칸이 없는 것을 봤고, 전기 뒤 데이터베이스에서도 그 값이 비어 있었다. F110 제안은 담당자가 말한 대로 Credit을 반영하지 않았다. Credit만 따로 묶여 예외로 빠졌고, 메시지는 FZ501이었다.
제안 로그를 %pc로 저장하려다 SAP GUI 보안 창이 떠 스크립트가 멈췄다. 파일 쓰기를 허용할지 묻는 창이라 에이전트가 대신 누르지 않았다. 이유를 들은 뒤 내가 허용을 눌렀다.
2차 실행은 원인을 갈랐다. Credit의 주거래은행을 비워 Credit과 송장 A는 BVTYP만 다르게 했다. 송장 B에는 주거래은행을 넣어 송장 A와 B는 HBKID만 다르게 했다. Credit은 여전히 예외였고, 송장 A와 B도 서로 다른 지급 묶음이 됐다. 두 필드 중 하나만 달라도 묶음이 갈라졌다. 앞선 분석도 두 필드를 원인으로 봤지만, 1차 안내를 쓰면서 주거래은행을 뺐고 그 문장이 틀렸다.
조치는 F-44 반제였다. Credit에 값을 넣을 칸이 없으니 Credit과 송장 한 건을 먼저 반제해 잔여 항목을 만들었다. 잔여 항목의 전기키는 1차 문서의 31이 아니라 34였다. 많이 쓰이는 반제 설정에서 잔여 항목 대변은 34인 경우가 많지만, 여기 적은 34는 그 QA에서 본 값이다. 그 화면에서도 파트너은행유형 칸은 숨겨져 있었고, 값은 원 송장에서 복사됐다. 그 시스템의 대체 규칙이 새 항목에 지급보류를 걸어서, FB02로 보류를 푸는 단계가 하나 더 필요했다.
3차 실행은 지급 확인이었다. 잔여 항목과 나머지 송장이 한 묶음이 됐고, 지급 실행까지 돌려 지급전표가 한 건 생겼다. 은행 파일이나 출력물은 만들지 않았다. 지급 실행 팝업에 즉시 시작 말고 다른 체크박스가 보이면 스크립트가 취소하도록 해 두었다.
조건이 오류 나면 Then으로 들어간다
지급 실행 스크립트를 처음 돌렸을 때 스크립트가 스스로 취소하고 멈췄다. 팝업의 자식을 돌며 지급매체 체크박스가 켜져 있으면 중단하는 조건을 한 줄로 썼기 때문이다. VBScript의 And는 양쪽을 모두 평가한다. 체크박스가 아닌 요소에서 Selected를 읽다 오류가 난다. On Error Resume Next 아래에서 그 조건이 오류 나면 If는 Then으로 들어간다. 이번 Then은 취소라서 멈췄다. 진행 쪽 조건이었다면 검사를 지나갔을 것이다.
그 뒤 안전 가드는 중첩 If로 바꿨다. 위험한 것을 찾으면 중단하는 대신, 예상한 것 외에 하나라도 보이면 중단하는 허용 목록으로 썼다.
글을 쓰려고 스크립트를 다시 읽다가 Guard도 같은 구멍인 것을 알았다. 쓰기 스크립트는 On Error Resume Next를 켠 뒤에 Guard를 불렀다. VBScript의 오류 처리는 프로시저 단위라, Guard 안의 Err.Raise는 호출한 쪽에서 무시되고 다음 줄인 저장이 실행된다. 이번에 시스템을 지켜 준 것은 Guard가 아니라, 헬퍼를 부를 때의 세션 선택이었다. 그 시점에는 오류 무시가 아직 꺼져 있어서 에러가 스크립트를 멈춘다.
다시 쓴다면 가드는 오류를 던지지 않고 스스로 끝낸다. 판정이 실패해도 멈추는 쪽으로 떨어지게 한다. 아래는 그날 실행한 코드가 아니라, 글을 쓰며 고친 형태다.
Sub Guard()
Dim ok : ok = False
On Error Resume Next
ok = (session.Info.SystemName = QA_SID _
And session.Info.Client = QA_CLIENT)
On Error GoTo 0
If Not ok Then WScript.Quit 1
End Sub
사람이 지킨 자리와 다음에 먼저 볼 것
| 사람 | 에이전트 |
|---|---|
| SAP 로그인 | 세션 찾기, 화면 조작, 캡처 |
| 스크립팅 알림 팝업 끄기 | 레지스트리, PAHI, DisabledByServer 확인 |
| SAP GUI 보안 창에서 허용 | 막힌 이유 설명 후 대기 |
| QA에서 재현하자는 판단 | 재현 설계, 전기, F110 세 번, F-44 반제 |
| 담당자에게 회신 | 원인 분석, 문서, 데이터베이스 대조 |
사람이 한 일은 적지만 로그인, 보안 창, 그리고 QA에서 같은 데이터를 만들어 보여 주자는 판단이었다. 에이전트는 QA에 데이터가 없다고 잘못 보고 거기서 멈춰 있었다. 그 판단이 없었다면 1차 문서의 틀린 문장은 그대로 남았을 것이다. 담당자용으로 자른 화면 캡처는 이름과 계좌를 가렸고, 이 글에는 넣지 않았다.
| 막힌 곳 | 다음에 먼저 볼 것 |
|---|---|
| 컨트롤을 찾지 못함 | 알림 팝업과 옵션 창 |
| SE16N 컬럼이 일부만 보임 | ColumnCount, 기본 레이아웃 |
| 셀 읽기가 수 분 이상 | 필요한 컬럼만, 목록은 %pc |
| 스크립트가 끝없이 대기 | SAP GUI 보안 창은 사람이 클릭 |
| 한글 출력 깨짐 | UTF-8 파일로 쓰고 파일을 읽기 |
| 조건 오류인데 Then 실행 | 중첩 If, 허용 목록 |
| Guard가 저장을 막지 못함 | 가드 안에서 Quit, 세션 선택이 먼저 |
| QA 데이터가 없어 보임 | 로그온 클라이언트와 USING CLIENT |
| 당일 입력 전표가 지급에서 빠짐 | 미국 동부 11:00 또는 10:00부터의 CPUDT |
이번에 지킨 원칙은 네 가지다. 로그인은 사람이 한다. 운영은 조회만 한다. 쓰기 스크립트는 정한 시스템과 클라이언트의 세션만 잡는다. GUI에서 바꾼 값은 데이터베이스로 다시 확인한다. 하나 더 있다. 안전장치도 코드라서, 일부러 실패시켜 멈추는지 한 번은 본다.
매끄럽지는 않았다. 사용 한도에 걸려 몇 시간씩 멈춘 적이 있고, 첫 안내는 틀렸다. 그래도 운영 조회, 원인 정리, QA 재현, 지급 실행, 안내 문서까지 하루 안에 끝났다. 다음 목표는 원래 계획했던 현업 매뉴얼이다. QA에서 업무 순서를 돌리며 화면과 조작을 문서로 남기는 일은 이번과 모양이 같다.
이 기록에 대화를 더해 주세요.
궁금한 점, 다른 접근, 직접 해 본 결과를 나눠 주세요.
로그인 상태 확인 중…
댓글을 불러오는 중…