본문 바로가기
BAPI · BADI · RFC · Interface

[SAP ABAP] BAPI_INCOMINGINVOICE_CANCEL — 송장 취소 BAPI 호출 방법 (MR8M 자동화 · REASONREVERSAL)

by Song.sh 2026. 5. 21.

SAP MM 모듈에서 송장(Invoice) 처리는 구매 프로세스의 마지막 정산 단계입니다. 잘못된 송장을 발견했을 때 화면에서는 MR8M 으로 취소를 수행하지만, 인터페이스나 자동화 배치에서는 표준 BAPI 인 BAPI_INCOMINGINVOICE_CANCEL 을 호출해서 처리해야 합니다.

 

이 BAPI 는 단순히 송장 전표를 "삭제" 하는 것이 아니라, 원본 송장과 반대 부호의 취소 전표(Reversal Document)를 새로 생성 해서 두 전표를 자동 청산(clearing) 시키는 구조입니다. 그래서 호출 후 원본 전표번호와 취소 전표번호가 각각 살아남고, 회계 추적이 끝까지 가능해집니다.

 

이 글에서는 송장 취소 BAPI 의 시그니처·취소 사유 코드·실제 호출 패턴·COMMIT 처리·자주 발생하는 오류를 정리합니다. 송장 생성 BAPI(BAPI_INCOMINGINVOICE_CREATE) 와 같이 사용해야 송장 등록·취소의 양방향 인터페이스가 완성됩니다.

핵심 — BAPI_INCOMINGINVOICE_CANCEL 시그니처

📋 구분 파라미터 타입 설명
IMPORTING INVOICEDOCNUMBER BAPI_INCINV_FLD-INV_DOC_NO 취소할 송장 전표번호 (RBKP-BELNR)
IMPORTING FISCALYEAR BAPI_INCINV_FLD-FISC_YEAR 회계연도 (RBKP-GJAHR)
IMPORTING REASONREVERSAL BAPI_INCINV_FLD-REASON_REV 취소 사유 코드 (T041C 참조)
IMPORTING POSTINGDATE (Optional) BAPI_INCINV_FLD-PSTNG_DATE 취소 전표 전기일자 (생략 시 시스템 일자)
EXPORTING INVOICEDOCNUMBER_REVERSAL BAPI_INCINV_FLD-INV_DOC_NO 생성된 취소 전표번호
EXPORTING FISCALYEAR_REVERSAL BAPI_INCINV_FLD-FISC_YEAR 취소 전표 회계연도
TABLES RETURN BAPIRET2 처리 결과 메시지 (Type E 가 하나라도 있으면 실패)

송장 생성 BAPI(BAPI_INCOMINGINVOICE_CREATE) 가 헤더·아이템·세금·계정분개까지 수십 개 파라미터를 받는 것과 대조적으로, 취소 BAPI 는 "전표번호 + 회계연도 + 사유" 단 3가지 면 충분합니다. 취소 시 필요한 헤더·아이템 정보를 SAP 가 원본 전표에서 그대로 읽어와서 자동으로 반대 부호로 뒤집어 새 전표를 만들어주기 때문입니다.


1단계 — 취소 사유 코드 (REASONREVERSAL)

SAP 표준 취소 사유 코드는 T041C 테이블에 정의되어 있습니다. 회사 시스템에 따라 추가·변경될 수 있으니 운영 환경의 코드 마스터를 먼저 확인하시기 바랍니다.

코드 의미 설명
01 현 회계기간 내 취소 원본 전표와 동일 기간에 취소 (가장 일반적)
02 전기 회계기간 취소 이전 기간 전표 취소 — 별도 전기일자 지정 필요
03 실제 취소 송장 자체가 잘못 발행된 경우
04 정정 전 취소 송장을 정정하기 위해 먼저 취소
05 회계 결산 정정 연도·반기 결산 과정에서 발생한 정정용 취소

자동화 배치에서 가장 많이 쓰는 값은 01 또는 03 입니다. 이전 기간 송장을 현재 기간에 취소하는 경우라면 반드시 02 와 함께 POSTINGDATE 를 명시해야 회계 기간 마감 오류를 피할 수 있습니다.


2단계 — 호출 전 사전 체크 (취소 가능 여부)

BAPI 는 내부적으로 reversal_allowed_check 라는 검증 PERFORM 을 수행해서 취소 가능 여부를 먼저 판단합니다. 다음 조건 중 하나라도 걸리면 RETURN 에 에러 메시지가 담겨 돌아오므로 호출 전 미리 검증해두면 불필요한 시도를 줄일 수 있습니다.

* 사전 체크 패턴
SELECT SINGLE rbstat                  " 송장 상태
              stblg                   " 이미 취소된 전표번호
              stjah                   " 이미 취소된 회계연도
              zfbdt                   " 지급예정일
  INTO  (lv_rbstat, lv_stblg, lv_stjah, lv_zfbdt)
  FROM  rbkp
  WHERE belnr = lv_invoice_doc
    AND gjahr = lv_fiscal_year.

IF lv_stblg IS NOT INITIAL.
  " 이미 취소된 전표 — 재취소 불가
  MESSAGE 'Invoice already cancelled' TYPE 'E'.
ENDIF.

IF lv_rbstat = 'X'.
  " 차단 상태 — 취소 불가
  MESSAGE 'Invoice blocked' TYPE 'E'.
ENDIF.

RBKP-STBLG 가 채워져 있으면 이미 취소된 전표라는 뜻입니다. 같은 송장을 두 번 취소하려고 시도하면 표준 화면에서는 막혀 있지만, BAPI 직접 호출 시에는 RETURN 메시지로만 알려주므로 인터페이스 로직에서 이 컬럼을 먼저 확인하는 게 안전합니다.


3단계 — BAPI 호출 기본 패턴

가장 단순한 호출 형태입니다. 단일 전표 취소 + 시스템 일자 사용 + 사유 01.

DATA: lt_return TYPE TABLE OF bapiret2,
      ls_return TYPE bapiret2,
      lv_rev_doc  TYPE bapi_incinv_fld-inv_doc_no,
      lv_rev_year TYPE bapi_incinv_fld-fisc_year.

CALL FUNCTION 'BAPI_INCOMINGINVOICE_CANCEL'
  EXPORTING
    invoicedocnumber          = '5100000001'   " 취소할 송장 번호
    fiscalyear                = '2026'
    reasonreversal            = '01'
*   postingdate               = sy-datum       " 생략 시 시스템 일자
  IMPORTING
    invoicedocnumber_reversal = lv_rev_doc
    fiscalyear_reversal       = lv_rev_year
  TABLES
    return                    = lt_return.

" 에러 체크
READ TABLE lt_return INTO ls_return
     WITH KEY type = 'E'.
IF sy-subrc = 0.
  " 에러 발생 — 롤백
  CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
ELSE.
  " 정상 처리 — 커밋
  CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
    EXPORTING wait = 'X'.
  WRITE: / '취소 전표번호:', lv_rev_doc,
         / '회계연도:',      lv_rev_year.
ENDIF.

핵심은 호출 직후 RETURN 테이블의 Type 'E' 메시지 유무 검증 입니다. BAPI 자체는 호출 성공/실패 여부를 SY-SUBRC 로 알려주지 않고 RETURN 메시지로만 표현하므로 이 체크를 빼먹으면 실패한 취소를 성공으로 오인할 수 있습니다.


4단계 — COMMIT 처리 (필수)

표준 BAPI 패턴과 동일하게 BAPI_INCOMINGINVOICE_CANCEL 도 호출 자체로는 DB 에 반영되지 않습니다. BAPI_TRANSACTION_COMMIT 을 명시적으로 호출해야 취소 전표가 실제로 저장됩니다.

" 정상 처리 시
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
  EXPORTING
    wait = 'X'.   " 동기 커밋 (DB 반영 완료까지 대기)

wait = 'X' 옵션은 DB 반영이 완료될 때까지 기다린 후 다음 라인으로 진행하라는 뜻입니다. 이 옵션 없이 커밋하면 호출은 비동기로 처리되어 직후 SELECT 시 아직 데이터가 보이지 않을 수 있습니다. 인터페이스에서 취소 후 결과를 즉시 다른 시스템에 송신해야 한다면 반드시 wait = 'X' 를 넣어야 합니다.


5단계 — 다건 일괄 취소 처리

여러 송장을 한 배치에서 취소할 때 한 건씩 호출하면서 건별 단위로 RETURN 검증·로그 적재 하는 패턴을 권장합니다. 한 건이 실패해도 나머지 송장 취소가 영향받지 않게 하기 위함입니다.

LOOP AT lt_cancel_target INTO ls_target.

  REFRESH lt_return.
  CLEAR: lv_rev_doc, lv_rev_year.

  CALL FUNCTION 'BAPI_INCOMINGINVOICE_CANCEL'
    EXPORTING
      invoicedocnumber          = ls_target-belnr
      fiscalyear                = ls_target-gjahr
      reasonreversal            = ls_target-reason
      postingdate               = ls_target-pst_date
    IMPORTING
      invoicedocnumber_reversal = lv_rev_doc
      fiscalyear_reversal       = lv_rev_year
    TABLES
      return                    = lt_return.

  READ TABLE lt_return WITH KEY type = 'E' TRANSPORTING NO FIELDS.

  IF sy-subrc = 0.
    CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
    " 로그 적재 (실패)
    APPEND VALUE #( belnr  = ls_target-belnr
                    status = 'FAIL'
                    msg    = ls_return-message ) TO lt_log.
  ELSE.
    CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = 'X'.
    " 로그 적재 (성공)
    APPEND VALUE #( belnr     = ls_target-belnr
                    rev_belnr = lv_rev_doc
                    status    = 'OK' ) TO lt_log.
  ENDIF.

ENDLOOP.

한 LUW 안에서 COMMIT 을 여러 번 호출하지 않는 것 도 가능하지만, 그 경우 한 건이 실패해서 ROLLBACK 을 하면 같은 LUW 의 이전 성공 건도 함께 롤백됩니다. 정책상 건별 독립 처리가 필요하면 위와 같이 매 건마다 COMMIT/ROLLBACK 을 명시하는 패턴이 안전합니다.


흔히 빠뜨리는 함정

이미 지급된 송장은 취소 불가

송장이 이미 지급 처리(F-53 · F110) 되었다면 BAPI 가 거부합니다. RETURN 에 "Document already paid" 메시지가 담겨 오므로 먼저 지급 취소(FBRA) 를 한 후 송장 취소를 수행해야 합니다.

회계 기간 마감

원본 전표의 POSTINGDATE 가 속한 회계 기간이 마감된 경우, 같은 기간으로 취소 전표를 끊으려고 하면 "Posting period locked" 오류가 발생합니다. 이 경우 REASON 02 + 현재 열려 있는 기간의 POSTINGDATE 를 명시해서 호출해야 합니다.

회계연도 누락 시 검색 실패

전표번호만 같고 회계연도가 다른 경우가 있을 수 있습니다(연도가 바뀌면서 번호 범위 재시작). FISCALYEAR 를 잘못 넘기면 BAPI 가 다른 전표를 취소하거나 "Document not found" 로 실패합니다. 호출 전 RBKP 에서 BELNR + GJAHR 조합으로 정확히 한 건이 조회되는지 확인하시기 바랍니다.

COMMIT 누락

가장 흔한 실수입니다. BAPI 호출만 하고 BAPI_TRANSACTION_COMMIT 을 빼먹으면 RETURN 에는 성공이 떠도 DB 에는 반영되지 않습니다. 다음 LUW 가 시작되면(예: 새 화면 진입) 변경분이 그대로 폐기됩니다.

한 LUW 안에서 COMMIT/SELECT 순서

COMMIT 직후 같은 프로그램에서 RBKP 를 SELECT 해서 취소 전표를 확인하려면 wait = 'X' 가 필수입니다. 비동기 COMMIT 직후 SELECT 하면 아직 update task 가 끝나지 않아 데이터가 안 보일 수 있습니다.

MR8M (수기) vs BAPI 차이

화면 MR8M 은 BAdI MRM_HEADER_DEFAULT · INVOICE_UPDATE 같은 화면 흐름용 BAdI 들이 함께 호출되는데, BAPI 직접 호출 시에는 일부 BAdI 가 트리거되지 않는 경우가 있습니다. 사내 커스텀 BAdI 가 화면에만 의존해서 동작하도록 짜여 있다면 BAPI 전환 시 누락 분기를 점검해야 합니다.


전체 코드 — 복사용 통합본

*&---------------------------------------------------------------------*
*& Report  Z_XX_INVOICE_CANCEL
*&---------------------------------------------------------------------*
*& 송장 취소 BAPI 호출 샘플
*&---------------------------------------------------------------------*
REPORT z_xx_invoice_cancel.

DATA: lt_return    TYPE TABLE OF bapiret2,
      ls_return    TYPE bapiret2,
      lv_rev_doc   TYPE bapi_incinv_fld-inv_doc_no,
      lv_rev_year  TYPE bapi_incinv_fld-fisc_year.

DATA: lv_belnr     TYPE bapi_incinv_fld-inv_doc_no VALUE '5100000001',
      lv_gjahr     TYPE bapi_incinv_fld-fisc_year  VALUE '2026',
      lv_reason    TYPE bapi_incinv_fld-reason_rev VALUE '01',
      lv_pst_date  TYPE bapi_incinv_fld-pstng_date.

DATA: lv_stblg     TYPE rbkp-stblg,
      lv_rbstat    TYPE rbkp-rbstat.

* ★ 사전 체크: 이미 취소되었거나 차단된 전표 검증
SELECT SINGLE stblg rbstat
  INTO (lv_stblg, lv_rbstat)
  FROM rbkp
  WHERE belnr = lv_belnr
    AND gjahr = lv_gjahr.

IF sy-subrc <> 0.
  WRITE: / '대상 송장 없음:', lv_belnr, lv_gjahr.
  EXIT.
ENDIF.

IF lv_stblg IS NOT INITIAL.
  WRITE: / '이미 취소된 전표 — 재취소 불가. 취소 전표:', lv_stblg.
  EXIT.
ENDIF.

* ★ 핵심: BAPI 호출
CALL FUNCTION 'BAPI_INCOMINGINVOICE_CANCEL'
  EXPORTING
    invoicedocnumber          = lv_belnr
    fiscalyear                = lv_gjahr
    reasonreversal            = lv_reason
*   postingdate               = lv_pst_date   " 필요 시 활성화
  IMPORTING
    invoicedocnumber_reversal = lv_rev_doc
    fiscalyear_reversal       = lv_rev_year
  TABLES
    return                    = lt_return.

* ★ RETURN 검증
READ TABLE lt_return INTO ls_return
     WITH KEY type = 'E'.

IF sy-subrc = 0.
  " 에러 발생 — 롤백
  CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
  WRITE: / '취소 실패:', ls_return-message.

  LOOP AT lt_return INTO ls_return.
    WRITE: / ls_return-type, ls_return-id,
             ls_return-number, ls_return-message.
  ENDLOOP.

ELSE.
  " 정상 처리 — 동기 커밋
  CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
    EXPORTING
      wait = 'X'.

  WRITE: / '취소 성공',
         / '원본 송장:    ', lv_belnr, '/', lv_gjahr,
         / '취소 전표번호:', lv_rev_doc, '/', lv_rev_year.
ENDIF.

같이 보면 좋은 글은 "BAPI_INCOMINGINVOICE_CREATE — 송장 생성 BAPI 호출 패턴" 입니다. 송장 생성 → 잘못 등록 발견 → 취소 → 정정 송장 재등록 순서가 인터페이스에서 가장 흔한 시나리오이므로 두 BAPI 를 한 세트로 익혀두시면 좋습니다.


요약

단계 처리 내용 핵심 포인트
1 사전 체크 RBKP-STBLG 확인 — 재취소 차단
2 취소 사유 결정 현 기간은 01, 이전 기간은 02 + 별도 일자
3 BAPI 호출 BELNR + GJAHR + REASON 3개로 트리거
4 RETURN 검증 Type 'E' 있으면 ROLLBACK
5 COMMIT wait = 'X' 동기 처리 권장

송장 취소 BAPI 는 시그니처가 단순한 만큼 사전 체크와 RETURN 검증이 품질을 좌우합니다. 특히 인터페이스 / 배치에서 사용할 때는 "이미 취소된 전표 재취소 방지"·"COMMIT/ROLLBACK 명시"·"한 건 실패가 전체에 영향 없게 분리" 세 가지를 패턴으로 박아두면 운영 중 사고가 거의 발생하지 않습니다.


Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다.

BAPI 시그니처·취소 사유 코드(T041CRBKP 필드 구조는 SAP MM 표준(패키지 MRM · ECC 6.0 / S/4HANA on-premise) 기준이며, 사내 추가 커스터마이징·BAdI 구현·결재 흐름에 따라 일부 동작이 다를 수 있으니 운영 시스템 적용 전 개발·QA 환경에서 검증하시기 바랍니다.