SAP MM 모듈에서 자재문서를 취소하려면 화면에서는 MBST·MIGO_GR_REV 를 사용하지만, 인터페이스나 자동 정정 배치에서는 BAPI_GOODSMVT_CANCEL 을 호출합니다. 입고·출고·자재이동 어떤 종류든 한 BAPI 로 일괄 취소 가능합니다.
이 BAPI 는 송장 취소(BAPI_INCOMINGINVOICE_CANCEL) 처럼 자재문서 자체를 삭제하는 것이 아니라 반대 부호의 자재문서를 새로 생성 합니다. 원본 자재문서(예: BWART 101)와 취소 자재문서(BWART 102)가 짝으로 남아 재고 / 분개를 자동 청산하므로 추적 가능성이 끝까지 유지됩니다.
이 글에서는 BAPI 시그니처·전체 vs 부분 취소·전기일자 옵션·취소 가능 여부 사전 체크·COMMIT 처리·자주 발생하는 함정까지 정리합니다.
핵심 — BAPI_GOODSMVT_CANCEL 시그니처
| 📋 구분 | 파라미터 | 타입 | 설명 |
|---|---|---|---|
| IMPORTING | MATERIALDOCUMENT |
BAPI2017_GM_HEAD_02-MAT_DOC |
취소할 자재문서 번호 (MKPF-MBLNR) |
| IMPORTING | MATDOCUMENTYEAR |
BAPI2017_GM_HEAD_02-DOC_YEAR |
회계연도 (MKPF-MJAHR) |
| IMPORTING | GOODSMVT_PSTNG_DATE (Opt) |
BAPI2017_GM_HEAD_02-PSTNG_DATE |
취소 전표 전기일자 (생략 시 시스템 일자) |
| IMPORTING | GOODSMVT_PR_UNAME (Opt) |
PR_UNAME |
인쇄 사용자명 |
| EXPORTING | GOODSMVT_HEADRET |
BAPI2017_GM_HEAD_RET |
취소 자재문서 헤더 (취소 전표 번호 포함) |
| TABLES | RETURN |
BAPIRET2 |
처리 메시지 |
| TABLES | GOODSMVT_MATDOCITEM (Opt) |
BAPI2017_GM_ITEM_04 |
특정 아이템만 취소 시 사용 |
입고 생성 BAPI(BAPI_GOODSMVT_CREATE) 가 HEADER + CODE + ITEM 3종 세트로 복잡한 것과 달리, 취소 BAPI 는 자재문서 번호 + 회계연도만 채우면 끝. 이동유형도, 자재·플랜트·수량도 SAP 가 원본 자재문서에서 그대로 읽어와 반대 부호로 자동 처리합니다.
1단계 — 전체 취소 (가장 단순한 패턴)
자재문서 전체를 한 번에 취소하는 표준 패턴입니다. GOODSMVT_MATDOCITEM 테이블은 비워두면 됩니다.
DATA: lt_return TYPE TABLE OF bapiret2,
ls_return TYPE bapiret2,
ls_headret TYPE bapi2017_gm_head_ret.
CALL FUNCTION 'BAPI_GOODSMVT_CANCEL'
EXPORTING
materialdocument = '5000000001' " 취소할 자재문서
matdocumentyear = '2026'
* goodsmvt_pstng_date = sy-datum " 생략 시 시스템 일자
IMPORTING
goodsmvt_headret = ls_headret
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: / '취소 자재문서:', ls_headret-mat_doc, '/', ls_headret-doc_year.
ENDIF.
핵심은 GOODSMVT_HEADRET 에서 취소 자재문서 번호를 받는 것입니다. 원본 문서(5000000001)와 별개로 SAP 가 새 자재문서(예: 5000000002) 를 생성해 청산합니다. 인터페이스에서 외부 시스템에 취소 결과를 전달할 때 이 번호가 키 값이 됩니다.
2단계 — 부분(아이템) 취소
자재문서의 특정 아이템만 취소할 때 GOODSMVT_MATDOCITEM 테이블에 라인을 지정합니다.
DATA: lt_item TYPE TABLE OF bapi2017_gm_item_04,
ls_item TYPE bapi2017_gm_item_04.
ls_item-mat_doc = '5000000001'.
ls_item-doc_year = '2026'.
ls_item-matdoc_itm = '0001'. " 자재문서 라인번호 (MSEG-ZEILE)
ls_item-entry_qnt = '50'. " 일부 수량만 취소 가능
ls_item-entry_uom = 'EA'.
APPEND ls_item TO lt_item.
CALL FUNCTION 'BAPI_GOODSMVT_CANCEL'
EXPORTING
materialdocument = '5000000001'
matdocumentyear = '2026'
IMPORTING
goodsmvt_headret = ls_headret
TABLES
return = lt_return
goodsmvt_matdocitem = lt_item.
부분 취소가 가능한 경우는 제한적입니다. 일반적으로 자재문서 라인이 후속 소비·이동 없이 그대로 남아 있어야 부분 취소가 허용됩니다. 후속 처리가 이미 발생한 경우 표준은 전체 취소 후 정정 흐름을 권장합니다.
3단계 — 호출 전 사전 체크 (취소 가능 여부)
BAPI 가 RETURN 에 에러를 담아 돌려주기는 하지만, 사전에 MKPF·MSEG 에서 취소 가능 여부를 확인 해두면 실패 호출을 줄일 수 있습니다.
DATA: lv_smbln TYPE mkpf-smbln,
lv_xauto TYPE mseg-xauto.
" 1) 이미 취소된 문서인지 확인 (MKPF-SMBLN 가 채워져 있으면 이미 취소됨)
SELECT SINGLE mkpf~smbln
INTO lv_smbln
FROM mkpf
WHERE mblnr = lv_mblnr
AND mjahr = lv_mjahr.
IF lv_smbln IS NOT INITIAL.
MESSAGE 'Already cancelled' TYPE 'E'.
ENDIF.
" 2) 후속 처리 여부 확인 (XAUTO = 'X' 자동 생성 라인은 별도 검증 필요)
SELECT SINGLE xauto
INTO lv_xauto
FROM mseg
WHERE mblnr = lv_mblnr
AND mjahr = lv_mjahr.
MKPF-SMBLN 이 채워져 있으면 이미 취소된 문서입니다. 한 자재문서를 두 번 취소하면 "Document already reversed" 에러가 발생하므로 사전 차단이 필요합니다.
4단계 — 전기일자 처리 (이전 기간 취소)
원본 자재문서의 전기일자가 마감된 기간에 속해 있고 현재 기간에 취소해야 한다면 GOODSMVT_PSTNG_DATE 에 현재 열려 있는 기간의 일자를 명시합니다.
CALL FUNCTION 'BAPI_GOODSMVT_CANCEL'
EXPORTING
materialdocument = '5000000001'
matdocumentyear = '2025' " 작년 문서
goodsmvt_pstng_date = sy-datum " 올해 일자로 취소
IMPORTING
goodsmvt_headret = ls_headret
TABLES
return = lt_return.
MMRV 에서 현재 회사 / 플랜트의 열린 기간을 확인 후 일자를 결정하시기 바랍니다. 회계 기간(OB52) 도 함께 열려 있어야 정상 처리됩니다.
5단계 — 다건 일괄 취소
여러 자재문서를 한 배치에서 취소할 때는 건별로 RETURN 검증 + COMMIT/ROLLBACK 패턴을 권장합니다. 한 건 실패가 다른 건에 영향 없게 분리하기 위함입니다.
LOOP AT lt_cancel_target INTO ls_target.
REFRESH lt_return.
CLEAR ls_headret.
CALL FUNCTION 'BAPI_GOODSMVT_CANCEL'
EXPORTING
materialdocument = ls_target-mblnr
matdocumentyear = ls_target-mjahr
goodsmvt_pstng_date = ls_target-pstng_date
IMPORTING
goodsmvt_headret = ls_headret
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 #( mblnr = ls_target-mblnr
status = 'FAIL'
msg = ls_return-message ) TO lt_log.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT' EXPORTING wait = 'X'.
APPEND VALUE #( mblnr = ls_target-mblnr
cancel_doc = ls_headret-mat_doc
status = 'OK' ) TO lt_log.
ENDIF.
ENDLOOP.
흔히 빠뜨리는 함정
이미 송장 처리된 자재문서
PO 입고 자재문서가 이미 송장 처리(MIRO) 된 경우, 입고만 취소하면 GR/IR 잔액이 깨집니다. 올바른 순서는 송장 취소(MR8M 또는 BAPI_INCOMINGINVOICE_CANCEL) → 입고 취소 입니다. 송장이 살아 있는데 입고만 취소하면 BAPI 가 거부합니다.
후속 출고가 발생한 경우
입고 후 이미 그 자재가 출고·이동되어 재고에서 빠진 경우 단순 취소가 불가능합니다. 재고 부족 에러("Deficit of stock") 가 발생합니다. 이 경우 후속 이동을 먼저 취소한 다음 원본 입고를 취소해야 합니다.
이미 취소된 문서 재취소
MKPF-SMBLN 에 취소 문서가 이미 채워진 경우 재취소는 불가능합니다. 화면에서는 막혀 있지만 BAPI 직접 호출 시 RETURN 메시지로만 알려주므로 사전 SELECT 가 안전합니다.
회계 기간 마감
원본 문서가 속한 기간이 마감되어 있으면 GOODSMVT_PSTNG_DATE 를 현재 열린 기간으로 명시해야 합니다. 단, 회사 정책상 "전기 기간 자재문서는 현 기간으로 취소" 가 막혀 있는 경우도 있으니 운영팀과 확인이 필요합니다.
배치 / 시리얼 후속 처리
배치 관리 자재의 경우 입고 시 생성된 배치에 이미 후속 변경(BAPI_BATCH_CHANGE) 이 발생하면 일부 필드 검증에 막힐 수 있습니다. 시리얼 관리 자재도 시리얼 상태가 "활성" 이어야만 취소가 진행됩니다.
COMMIT 누락 / 비동기 SELECT
BAPI 자체 호출 성공이라도 BAPI_TRANSACTION_COMMIT 누락 시 DB 미반영. 동기 인터페이스에서 COMMIT 직후 MKPF SELECT 하려면 wait = 'X' 필수입니다.
MR11 GR/IR 청산과의 관계
GR/IR 미청산 라인이 있는 자재문서는 MR11 청산 후 다시 입고 취소를 시도해야 합니다. 회사 정책에 따라 BAPI 호출 시점 검증을 우회하는 경우도 있으므로 운영기 흐름을 사전 확인하시기 바랍니다.
전체 코드 — 복사용 통합본
*&---------------------------------------------------------------------*
*& Report Z_XX_GOODS_RECEIPT_CANCEL
*&---------------------------------------------------------------------*
*& 입고 / 자재이동 취소 BAPI 호출 샘플
*&---------------------------------------------------------------------*
REPORT z_xx_goods_receipt_cancel.
DATA: ls_headret TYPE bapi2017_gm_head_ret,
lt_return TYPE TABLE OF bapiret2,
ls_return TYPE bapiret2.
DATA: lv_mblnr TYPE bapi2017_gm_head_02-mat_doc VALUE '5000000001',
lv_mjahr TYPE bapi2017_gm_head_02-doc_year VALUE '2026',
lv_pstdat TYPE bapi2017_gm_head_02-pstng_date.
DATA: lv_smbln TYPE mkpf-smbln.
* ★ 사전 체크: 이미 취소된 문서인지 확인
SELECT SINGLE smbln
INTO lv_smbln
FROM mkpf
WHERE mblnr = lv_mblnr
AND mjahr = lv_mjahr.
IF sy-subrc <> 0.
WRITE: / '대상 자재문서 없음:', lv_mblnr, lv_mjahr.
EXIT.
ENDIF.
IF lv_smbln IS NOT INITIAL.
WRITE: / '이미 취소된 문서 — 재취소 불가. 취소문서:', lv_smbln.
EXIT.
ENDIF.
* ★ 핵심: BAPI 호출 (전체 취소)
CALL FUNCTION 'BAPI_GOODSMVT_CANCEL'
EXPORTING
materialdocument = lv_mblnr
matdocumentyear = lv_mjahr
* goodsmvt_pstng_date = lv_pstdat " 필요 시 활성화 (이전 기간 취소)
IMPORTING
goodsmvt_headret = ls_headret
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: / '취소 실패:'.
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_mblnr, '/', lv_mjahr,
/ '취소 자재문서:', ls_headret-mat_doc, '/', ls_headret-doc_year.
ENDIF.
같이 보면 좋은 글은 "BAPI_GOODSMVT_CREATE — 입고·출고·자재이동 BAPI 호출 방법" 입니다. 자재문서 생성 → 취소 흐름을 한 세트로 익혀두면 인터페이스의 양방향 자동화에 그대로 적용 가능합니다.
요약
| 단계 | 처리 내용 | 핵심 포인트 |
|---|---|---|
| 1 | 사전 체크 | MKPF-SMBLN · 송장·후속 출고 확인 |
| 2 | 전체 vs 부분 결정 | 전체는 ITEM 비움 · 부분은 ITEM 명시 |
| 3 | 전기일자 결정 | 현 기간 자재문서 일자 또는 현재 일자 |
| 4 | BAPI 호출 | MBLNR + MJAHR 두 개로 트리거 |
| 5 | RETURN 검증 | Type 'E' 있으면 ROLLBACK |
| 6 | COMMIT | wait = 'X' 동기 처리 |
입고 취소 BAPI 는 시그니처가 단순한 만큼 사전 체크와 처리 순서가 품질을 좌우 합니다. 송장 → 입고 순으로 취소, 후속 출고가 있다면 그것부터 취소, 이미 취소된 문서 재시도 차단. 이 세 가지를 인터페이스 워크플로우에 패턴으로 박아두면 운영 중 사고가 거의 발생하지 않습니다.
Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다.
BAPI 시그니처·MKPF·MSEG 필드 구조는 SAP MM 표준(패키지 MB · ECC 6.0 / S/4HANA on-premise) 기준이며, 사내 추가 BAdI · GR/IR 정책 · 결재 흐름에 따라 일부 동작이 다를 수 있으니 운영 시스템 적용 전 개발·QA 환경에서 검증하시기 바랍니다.