SAP MM 모듈의 송장 검증(MIRO) 화면을 자동화하거나 외부 시스템(그룹웨어·전자결재·거래처 EDI)에서 들어온 송장 데이터를 SAP 에 등록할 때는 BAPI_INCOMINGINVOICE_CREATE 를 호출합니다. 이 BAPI 하나로 일반 송장·대변메모(Credit Memo)·후속 차변(Subsequent Debit)·후속 대변(Subsequent Credit) 네 가지 문서 유형을 모두 처리할 수 있습니다.
송장 취소 BAPI(BAPI_INCOMINGINVOICE_CANCEL) 가 단순한 시그니처를 갖는 것과 달리, 생성 BAPI 는 헤더·아이템·세금·계정분개·자재·자료배분까지 10개가 넘는 테이블 파라미터 를 다룹니다. 그래서 핵심 키 필드(INVOICE_IND·DE_CRE_IND) 의 의미를 이해하지 못하면 의도와 다른 문서 유형이 생성됩니다.
이 글에서는 BAPI 시그니처 정리·문서 유형 분기 키·헤더·아이템·세금 파라미터 채우기·시뮬레이션 모드 활용·COMMIT 처리·자주 발생하는 오류 패턴까지 단계별로 정리합니다.
핵심 — 문서 유형은 INVOICE_IND × DE_CRE_IND 조합으로 결정
BAPI 가 생성할 문서 유형은 헤더의 INVOICE_IND + 아이템의 DE_CRE_IND 두 플래그 조합으로 결정됩니다. 이 조합을 잘못 채우면 송장으로 등록해야 할 건이 대변메모로 들어가는 사고가 발생합니다.
| INVOICE_IND | DE_CRE_IND | 문서 유형 | 의미 |
|---|---|---|---|
| X | (공백) | 송장(Invoice) | 표준 매입 송장. 가장 일반적 |
| (공백) | (공백) | 대변메모(Credit Memo) | 송장 금액 환수 — 부호 반대 |
| X | 1 | 후속 차변(Subsequent Debit) | 기존 송장 추가 청구 — 차차 |
| (공백) | 2 | 후속 대변(Subsequent Credit) | 기존 송장 일부 환수 — 차대 |
실무에서 가장 헷갈리는 케이스가 "송장 일부 환수" 입니다. 송장 전체를 취소하지 않고 일부 금액만 돌려받을 때는 대변메모가 아니라 후속 대변(차대) 으로 처리해야 합니다. 대변메모는 새로운 독립 전표지만, 후속 대변은 원본 송장을 참조해서 일부 환수만 기록하는 구조이기 때문입니다.
시그니처 — IMPORTING / EXPORTING / TABLES
| 📋 구분 | 파라미터 | 타입 | 설명 |
|---|---|---|---|
| IMPORTING | HEADERDATA |
BAPI_INCINV_CREATE_HEADER |
송장 헤더 (필수) |
| IMPORTING | ADDRESSDATA (Opt) |
BAPI_INCINV_CREATE_ADDRESSDATA |
일회성 거래처 주소 |
| EXPORTING | INVOICEDOCNUMBER |
BAPI_INCINV_FLD-INV_DOC_NO |
생성된 송장 전표번호 |
| EXPORTING | FISCALYEAR |
BAPI_INCINV_FLD-FISC_YEAR |
생성된 송장 회계연도 |
| TABLES | ITEMDATA |
BAPI_INCINV_CREATE_ITEM |
PO 참조 아이템 (필수) |
| TABLES | ACCOUNTINGDATA (Opt) |
BAPI_INCINV_CREATE_ACCOUNT |
계정분개 (CO 분개 필요 시) |
| TABLES | GLACCOUNTDATA (Opt) |
BAPI_INCINV_CREATE_GL_ACCOUNT |
G/L 계정 직접 분개 |
| TABLES | MATERIALDATA (Opt) |
BAPI_INCINV_CREATE_MATERIAL |
자재 직접 분개 |
| TABLES | TAXDATA (Opt) |
BAPI_INCINV_CREATE_TAX |
세금 (수동 입력 시) |
| TABLES | WITHTAXDATA (Opt) |
BAPI_INCINV_CREATE_WITHTAX |
원천세 |
| TABLES | RETURN |
BAPIRET2 |
메시지 (Type E 검증) |
| TABLES | EXTENSIONIN (Opt) |
BAPIPAREX |
CI Include 확장 필드 |
호출 시 반드시 채워야 하는 것은 HEADERDATA + ITEMDATA 두 개. 나머지는 시나리오에 따라 선택. PO 참조 표준 송장이라면 PO 헤더·아이템에서 정보 끌어와 BAPI 가 자동 분개해주므로 ACCOUNTING/GL 은 비워둬도 됩니다.
1단계 — HEADERDATA 채우기
헤더는 송장 1건당 하나의 행. 회사 코드·송장일자·금액·통화·거래처 등 청구서 1장의 상단 정보가 들어갑니다.
DATA ls_header TYPE bapi_incinv_create_header.
ls_header-invoice_ind = 'X'. " 송장(X) / 대변메모(공백)
ls_header-doc_date = '20260520'. " 송장일자
ls_header-pstng_date = sy-datum. " 전기일자
ls_header-comp_code = '1000'. " 회사코드
ls_header-diff_inv = '0000100001'. " 거래처 (LIFNR)
ls_header-currency = 'KRW'. " 통화
ls_header-gross_amount = '1100000'. " 총금액 (세금 포함)
ls_header-calc_tax_ind = 'X'. " 세금 자동 계산
ls_header-pmnttrms = '0001'. " 지급 조건
ls_header-ref_doc_no = 'INV-2026-001'." 거래처 송장번호
핵심 포인트 — GROSS_AMOUNT 는 세금 포함 총금액. 화면 MIRO 에서 사용자가 입력하는 "총금액" 필드와 같은 값입니다. 세금을 자동 계산하게 하려면 CALC_TAX_IND = 'X' 를 설정하고, 직접 계산해서 채울 거면 TAXDATA 테이블을 별도로 넘깁니다.
2단계 — ITEMDATA 채우기 (PO 참조)
아이템은 PO 라인별로 한 줄씩 추가합니다. 가장 흔한 패턴이 PO 1개의 여러 아이템을 한 송장에 묶어 청구하는 경우입니다.
DATA: lt_item TYPE TABLE OF bapi_incinv_create_item,
ls_item TYPE bapi_incinv_create_item.
" 첫 번째 아이템 (PO 4500000001 - 라인 10)
ls_item-invoice_doc_item = '000001'.
ls_item-po_number = '4500000001'.
ls_item-po_item = '00010'.
ls_item-tax_code = 'V0'. " 면세
ls_item-item_amount = '500000'. " 세전 금액
ls_item-quantity = '10'.
ls_item-po_unit = 'EA'.
ls_item-de_cre_ind = space. " 표준 송장
APPEND ls_item TO lt_item.
" 두 번째 아이템 (PO 4500000001 - 라인 20)
ls_item-invoice_doc_item = '000002'.
ls_item-po_number = '4500000001'.
ls_item-po_item = '00020'.
ls_item-tax_code = 'V0'.
ls_item-item_amount = '500000'.
ls_item-quantity = '5'.
ls_item-po_unit = 'EA'.
ls_item-de_cre_ind = space.
APPEND ls_item TO lt_item.
ITEM_AMOUNT 합계 + 세금 = GROSS_AMOUNT 가 일치해야 합니다. 위 예시에서 500,000 + 500,000 + 세금 100,000 = 1,100,000 이 헤더의 GROSS_AMOUNT 와 맞아야 BAPI 가 통과합니다. 1원이라도 차이 나면 M8 060 ("Balance not zero") 에러가 발생합니다.
3단계 — 세금 처리 (자동 계산 vs 수동 입력)
세금 처리는 두 가지 패턴 중 하나를 선택합니다.
자동 계산 — HEADERDATA-CALC_TAX_IND = 'X'. SAP 가 아이템의 TAX_CODE 와 회사코드 세금 테이블을 보고 세금을 자동 계산합니다. TAXDATA 테이블은 비웁니다.
수동 입력 — CALC_TAX_IND = ' ' + TAXDATA 테이블에 세금 금액을 직접 채웁니다. 외부 시스템에서 받은 세금 금액을 그대로 반영해야 할 때 사용합니다.
" 수동 입력 패턴
DATA: lt_tax TYPE TABLE OF bapi_incinv_create_tax,
ls_tax TYPE bapi_incinv_create_tax.
ls_tax-tax_code = 'V1'. " 10% 매입부가세
ls_tax-tax_amount = '100000'. " 세금 100,000
ls_tax-taxjurcode = ''. " 미국 외에는 비움
APPEND ls_tax TO lt_tax.
권장은 자동 계산입니다. 표준 SAP 의 세금 계산 로직을 그대로 사용하면 부가세·면세 분류 오류가 줄어듭니다. 외부 시스템 세금이 SAP 자동 계산과 다르면 그때만 수동 입력으로 전환하시기 바랍니다.
4단계 — 시뮬레이션 모드 활용 (실제 등록 전 검증)
HEADERDATA-SIMULATION = 'X' 로 호출하면 실제 전표를 만들지 않고 검증만 수행합니다. 인터페이스 개발 단계나 운영 중 대량 등록 직전 사전 점검에 유용합니다.
ls_header-simulation = 'X'. " 시뮬레이션 모드
CALL FUNCTION 'BAPI_INCOMINGINVOICE_CREATE'
EXPORTING
headerdata = ls_header
IMPORTING
invoicedocnumber = lv_doc
fiscalyear = lv_year
TABLES
itemdata = lt_item
return = lt_return.
" 시뮬레이션 결과 검증
READ TABLE lt_return WITH KEY type = 'E' TRANSPORTING NO FIELDS.
IF sy-subrc = 0.
" 실제 등록 시에도 동일 에러 발생할 것
ENDIF.
시뮬레이션 모드에서도 RETURN 메시지는 정상으로 돌아옵니다. 에러가 없으면 실제 호출(SIMULATION 비우고 재호출) 시 성공할 가능성이 높습니다. 단, 시뮬레이션은 DB 상태를 보지 못하는 일부 검증을 건너뛰므로 100% 보장은 아닙니다.
5단계 — BAPI 호출 + COMMIT
전체 호출 흐름입니다. 헤더·아이템 채우고 호출, RETURN 검증 후 COMMIT 처리.
DATA: lt_return TYPE TABLE OF bapiret2,
ls_return TYPE bapiret2,
lv_doc TYPE bapi_incinv_fld-inv_doc_no,
lv_year TYPE bapi_incinv_fld-fisc_year.
CALL FUNCTION 'BAPI_INCOMINGINVOICE_CREATE'
EXPORTING
headerdata = ls_header
IMPORTING
invoicedocnumber = lv_doc
fiscalyear = lv_year
TABLES
itemdata = lt_item
taxdata = lt_tax
return = lt_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.
ELSE.
CALL FUNCTION 'BAPI_TRANSACTION_COMMIT'
EXPORTING wait = 'X'.
WRITE: / '송장 등록 성공:', lv_doc, '/', lv_year.
ENDIF.
흔히 빠뜨리는 함정
GROSS_AMOUNT 와 ITEM_AMOUNT 합계 불일치
가장 흔한 에러입니다. 헤더 총금액 = 아이템 합계 + 세금. 통화 환산이나 소수점 처리 차이로 1원이라도 어긋나면 BAPI 가 거부합니다. 외부 데이터를 받아서 등록할 때는 두 값을 ABAP 측에서 다시 계산해서 비교한 후 호출하시기 바랍니다.
PO 번호 / PO 아이템 매칭 오류
PO_NUMBER + PO_ITEM 조합이 실제 EKKO/EKPO 에 존재하지 않거나, 이미 송장 처리가 끝난(EREKZ = 'X') PO 라인을 참조하면 "No more items can be invoiced for purchase order" 에러가 발생합니다. 호출 전 EKPO 의 송장 완료 플래그 확인이 안전합니다.
통화 자릿수
JPY 같은 0자리 통화, KWD 같은 3자리 통화에서 금액 단위가 BAPI 와 다르면 100배·1000배 어긋난 송장이 생성될 수 있습니다. TCURX 테이블에서 통화별 소수점 자릿수를 확인 후 호출 측에서 미리 정합화하시기 바랍니다.
CALC_TAX_IND 와 TAXDATA 동시 사용
자동 계산을 켜두고 TAXDATA 까지 채우면 BAPI 가 두 값을 비교해서 다르면 에러를 냅니다. 한 쪽만 사용하시기 바랍니다.
COMMIT 누락
BAPI 자체는 호출 성공이라도 BAPI_TRANSACTION_COMMIT 을 호출하지 않으면 DB 에 반영되지 않습니다. 비동기 인터페이스에서 COMMIT 직후 다른 BAPI 를 또 호출하려면 wait = 'X' 옵션이 필수입니다.
거래처 일회성 처리
거래처 마스터에 없는 일회성 거래처로 송장을 등록하려면 ADDRESSDATA 파라미터에 주소·세금ID 를 넣어야 합니다. 일반 거래처면 DIFF_INV 만 채우고 ADDRESSDATA 는 비웁니다.
전체 코드 — 복사용 통합본
*&---------------------------------------------------------------------*
*& Report Z_XX_INVOICE_CREATE
*&---------------------------------------------------------------------*
*& 송장 생성 BAPI 호출 샘플 (PO 참조 표준 송장)
*&---------------------------------------------------------------------*
REPORT z_xx_invoice_create.
DATA: ls_header TYPE bapi_incinv_create_header,
lt_item TYPE TABLE OF bapi_incinv_create_item,
ls_item TYPE bapi_incinv_create_item,
lt_return TYPE TABLE OF bapiret2,
ls_return TYPE bapiret2,
lv_doc TYPE bapi_incinv_fld-inv_doc_no,
lv_year TYPE bapi_incinv_fld-fisc_year.
* ★ 1) 헤더 데이터 (송장 1건의 청구서 상단 정보)
ls_header-invoice_ind = 'X'. " 송장(X) / 대변메모(' ')
ls_header-doc_date = sy-datum. " 송장일자
ls_header-pstng_date = sy-datum. " 전기일자
ls_header-comp_code = '1000'. " 회사코드
ls_header-diff_inv = '0000100001'. " 거래처
ls_header-currency = 'KRW'.
ls_header-gross_amount = '1100000'. " 총금액 (세금 포함)
ls_header-calc_tax_ind = 'X'. " 세금 자동 계산
ls_header-pmnttrms = '0001'.
ls_header-ref_doc_no = 'INV-2026-001'." 거래처 송장번호
* ★ 2) 아이템 데이터 (PO 라인별)
ls_item-invoice_doc_item = '000001'.
ls_item-po_number = '4500000001'.
ls_item-po_item = '00010'.
ls_item-tax_code = 'V1'. " 10% 매입부가세
ls_item-item_amount = '500000'.
ls_item-quantity = '10'.
ls_item-po_unit = 'EA'.
ls_item-de_cre_ind = space. " 표준 송장
APPEND ls_item TO lt_item.
ls_item-invoice_doc_item = '000002'.
ls_item-po_number = '4500000001'.
ls_item-po_item = '00020'.
ls_item-tax_code = 'V1'.
ls_item-item_amount = '500000'.
ls_item-quantity = '5'.
ls_item-po_unit = 'EA'.
ls_item-de_cre_ind = space.
APPEND ls_item TO lt_item.
* ★ 3) BAPI 호출
CALL FUNCTION 'BAPI_INCOMINGINVOICE_CREATE'
EXPORTING
headerdata = ls_header
IMPORTING
invoicedocnumber = lv_doc
fiscalyear = lv_year
TABLES
itemdata = lt_item
return = lt_return.
* ★ 4) 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_doc,
/ '회계연도:', lv_year.
ENDIF.
같이 보면 좋은 글은 "BAPI_INCOMINGINVOICE_CANCEL — 송장 취소 BAPI 호출 방법" 입니다. 송장 생성 → 잘못 등록 발견 → 취소 → 정정 송장 재등록 순서의 인터페이스 시나리오에서 두 BAPI 를 짝으로 익혀두면 편리합니다.
요약
| 단계 | 처리 내용 | 핵심 포인트 |
|---|---|---|
| 1 | 문서 유형 결정 | INVOICE_IND × DE_CRE_IND 조합 |
| 2 | HEADERDATA 채우기 | GROSS_AMOUNT 는 세금 포함 총액 |
| 3 | ITEMDATA 채우기 | PO 참조 + 아이템 합계 검증 |
| 4 | 세금 처리 | 자동 계산(CALC_TAX_IND) 권장 |
| 5 | 시뮬레이션 | SIMULATION = 'X' 사전 검증 |
| 6 | 호출 + COMMIT | RETURN 검증 후 wait='X' COMMIT |
송장 생성 BAPI 는 시그니처 자체보다 "GROSS_AMOUNT = ITEM_AMOUNT 합계 + 세금" 의 금액 정합성과 INVOICE_IND × DE_CRE_IND 조합 의 문서 유형 매핑이 품질의 90% 를 좌우합니다. 외부 시스템 연동에서는 시뮬레이션 모드로 사전 검증한 후 실제 등록하는 패턴을 워크플로우에 박아두면 잘못된 송장이 운영 DB 에 들어가는 사고를 막을 수 있습니다.
Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다.
BAPI 시그니처·구조체 필드(BAPI_INCINV_CREATE_HEADER·BAPI_INCINV_CREATE_ITEM)·문서 유형 분기 로직은 SAP MM 표준(패키지 MRM · ECC 6.0 / S/4HANA on-premise) 기준이며, 사내 세금·결재·BAdI 커스터마이징에 따라 일부 동작이 다를 수 있으니 운영 시스템 적용 전 개발·QA 환경에서 검증하시기 바랍니다.