SAP MM 모듈에서 구매오더(PO) 생성은 화면 ME21N 으로 수기 처리하지만, MES·생산계획·재고 자동 발주 같은 인터페이스에서는 BAPI_PO_CREATE1 을 호출합니다. 이전 버전(BAPI_PO_CREATE) 보다 인터페이스 구조가 정리되고 OO 객체 기반으로 내부가 다시 짜여 있어, 신규 개발에서는 거의 모든 회사가 _CREATE1 을 사용합니다.
이 BAPI 는 POHEADER + POITEM + POSCHEDULE 3종 세트가 핵심입니다. 헤더 1줄(거래처·회사코드·PO 유형·통화), 아이템 N줄(자재·플랜트·수량·단가), 일정라인 N줄(납기일·수량). 가격 조건·계정 분개·텍스트·파트너 정보는 필요한 시나리오에서만 추가합니다.
이 글에서는 시그니처 구조·헤더·아이템·일정라인 필드 채우기·계정 분개·시뮬레이션 모드(TESTRUN)·COMMIT 처리·자주 발생하는 함정까지 정리합니다.
핵심 — 3종 세트 + 옵션 5종
| 📋 파라미터 | 구조체 | 필수 | 담는 정보 |
|---|---|---|---|
POHEADER |
BAPIMEPOHEADER |
✓ | 회사코드·거래처·PO 유형·통화·결재 그룹 |
POITEM |
BAPIMEPOITEM |
✓ | 자재·플랜트·수량·단가·계정유형 |
POSCHEDULE |
BAPIMEPOSCHEDULE |
✓ | 납기일·일정 수량 (아이템 라인당 1~N건) |
POACCOUNT |
BAPIMEPOACCOUNT |
- | 계정분개 (K=비용센터·A=자산·F=오더) |
POCOND |
BAPIMEPOCOND |
- | 아이템별 가격조건 (할인·운임 등) |
POTEXTHEADER |
BAPIMEPOTEXTHEADER |
- | 헤더 텍스트 (헤더 비고) |
POPARTNER |
BAPIEKKOP |
- | 파트너 역할 (구매자·송장수신자 등) |
리턴: EXPPURCHASEORDER 에 생성된 PO 번호가 담겨 돌아옵니다.
1단계 — POHEADER 채우기
헤더는 PO 1건당 한 행. 회사 코드·거래처·PO 유형이 결정되면 나머지는 거래처 마스터·정보 레코드에서 자동 결정됩니다.
DATA ls_header TYPE bapimepoheader.
ls_header-doc_type = 'NB'. " PO 유형 (NB=Standard, FO=Framework)
ls_header-vendor = '0000100001'. " 거래처
ls_header-purch_org = '1000'. " 구매 조직
ls_header-pur_group = '001'. " 구매 그룹
ls_header-comp_code = '1000'. " 회사 코드
ls_header-currency = 'KRW'. " 통화
ls_header-doc_date = sy-datum. " 문서일자
ls_header-langu = sy-langu.
ls_header-pmnttrms = '0001'. " 지급 조건
핵심 — DOC_TYPE 이 PO 의 성격을 결정합니다. NB 는 표준 PO, FO 는 프레임워크 PO, UB 는 회사 간 이체 PO. 회사에서 정의된 PO 유형 코드(T161) 를 확인해서 시나리오에 맞게 넘기시기 바랍니다.
생성 시에는 POHEADERX 가 거의 필요 없습니다. 변경 BAPI(BAPI_PO_CHANGE) 에서는 어떤 필드를 변경할지 X-구조로 명시해야 하지만, 생성 BAPI 는 채운 필드를 모두 사용하므로 X-구조를 생략해도 됩니다.
2단계 — POITEM 채우기 (자재 PO 표준)
아이템은 자재 라인별로 한 줄씩 추가. PO 1건에 여러 자재를 묶을 수 있습니다.
DATA: lt_item TYPE TABLE OF bapimepoitem,
ls_item TYPE bapimepoitem.
ls_item-po_item = '00010'. " 아이템 번호 (10 단위)
ls_item-material = 'TEST-MAT-001'. " 자재
ls_item-plant = '1000'. " 플랜트
ls_item-stge_loc = '0001'. " 저장위치
ls_item-quantity = '100'. " 수량
ls_item-po_unit = 'EA'. " 단위
ls_item-net_price = '5000'. " 단가
ls_item-price_unit = '1'. " 가격 단위 (1개당 5,000)
ls_item-tax_code = 'V1'. " 세금 코드
APPEND ls_item TO lt_item.
ls_item-po_item = '00020'.
ls_item-material = 'TEST-MAT-002'.
ls_item-plant = '1000'.
ls_item-stge_loc = '0001'.
ls_item-quantity = '50'.
ls_item-po_unit = 'EA'.
ls_item-net_price = '8000'.
ls_item-price_unit = '1'.
ls_item-tax_code = 'V1'.
APPEND ls_item TO lt_item.
아이템 번호는 10 단위 권장. SAP 표준이 자동 채번할 때도 10 단위로 만들므로(00010 → 00020) 같은 규칙으로 통일하면 일관성이 유지됩니다.
가격을 거래처 정보 레코드(EINA·EINE) 에서 자동 결정시키려면 NET_PRICE 를 비우고 NO_PRICE_FROM_PO 옵션을 빈 값으로 두면 됩니다.
3단계 — POSCHEDULE 채우기 (납기 일정)
아이템 라인별로 납기 일정을 분할할 수 있습니다. 단순 일정(한 아이템 = 한 일정)이면 라인당 1건만 채웁니다.
DATA: lt_sched TYPE TABLE OF bapimeposchedule,
ls_sched TYPE bapimeposchedule.
ls_sched-po_item = '00010'. " 아이템 번호
ls_sched-sched_line = '0001'. " 일정 라인 번호
ls_sched-delivery_date = sy-datum + 14. " 납기일 (2주 후)
ls_sched-quantity = '100'. " 일정 수량
APPEND ls_sched TO lt_sched.
ls_sched-po_item = '00020'.
ls_sched-sched_line = '0001'.
ls_sched-delivery_date = sy-datum + 14.
ls_sched-quantity = '50'.
APPEND ls_sched TO lt_sched.
납기를 여러 번에 나눠 받으려면 같은 PO_ITEM 에 SCHED_LINE 을 1, 2, 3 ... 으로 늘리고 QUANTITY 합계가 POITEM 의 수량과 일치하게 채웁니다. 일치하지 않으면 BAPI 가 거부합니다.
4단계 — 계정 분개 (POACCOUNT)
자재가 아닌 비용 / 자산 / 오더 발주 의 경우 계정 분개가 필요합니다. POITEM 의 ACCTASSCAT 가 K(비용센터) · A(자산) · F(오더) 일 때 호출합니다.
DATA: lt_acct TYPE TABLE OF bapimepoaccount,
ls_acct TYPE bapimepoaccount.
" 비용센터 발주 예시
ls_item-acctasscat = 'K'. " 비용센터
" ... (다른 POITEM 필드 채움)
APPEND ls_item TO lt_item.
ls_acct-po_item = '00010'.
ls_acct-serial_no = '01'.
ls_acct-quantity = '100'.
ls_acct-gl_account = '0000510100'. " G/L 계정
ls_acct-costcenter = '0000100001'. " 비용센터
APPEND ls_acct TO lt_acct.
자재 PO(공백) 와 계정 PO(K·A·F) 를 한 PO 에 섞을 수도 있습니다. POITEM 별로 ACCTASSCAT 가 결정되며 그에 맞는 POACCOUNT 가 따라옵니다.
5단계 — TESTRUN 시뮬레이션
TESTRUN = 'X' 로 호출하면 실제 PO 를 만들지 않고 검증만 수행. 인터페이스 사전 점검에 유용합니다.
CALL FUNCTION 'BAPI_PO_CREATE1'
EXPORTING
poheader = ls_header
testrun = 'X' " 시뮬레이션 모드
IMPORTING
exppurchaseorder = lv_ebeln
TABLES
return = lt_return
poitem = lt_item
poschedule = lt_sched.
검증 에러(가격 없음·납기 미래 불가·거래처 차단 등) 가 RETURN 에 담겨 돌아옵니다. 단, DB 락이나 외부 시스템 호출 같은 일부 검증은 시뮬레이션에서 건너뛰므로 100% 보장은 아닙니다.
6단계 — BAPI 호출 + COMMIT
전체 호출 흐름입니다.
CALL FUNCTION 'BAPI_PO_CREATE1'
EXPORTING
poheader = ls_header
IMPORTING
exppurchaseorder = lv_ebeln
TABLES
return = lt_return
poitem = lt_item
poschedule = lt_sched.
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: / 'PO 번호:', lv_ebeln.
ENDIF.
흔히 빠뜨리는 함정
POITEM 수량 ≠ POSCHEDULE 합계
가장 흔한 에러입니다. POITEM 의 QUANTITY 와 같은 PO_ITEM 의 POSCHEDULE QUANTITY 합계가 일치해야 합니다. 1 EA 차이라도 BAPI 가 거부합니다.
거래처-자재 마스터 / 정보 레코드 미등록
자재가 해당 거래처로 발주된 적 없으면 거래처-자재 정보 레코드(EINA/EINE) 가 비어 있어 단가 자동 결정이 실패합니다. NET_PRICE 를 직접 채우거나 ME11 로 정보 레코드를 먼저 생성하시기 바랍니다.
단위 변환 차이
PO 단위(PO_UNIT) 가 자재 마스터의 기본 단위와 다른 경우, 자재 마스터에 단위 변환 규칙이 등록되어 있어야 합니다. 외부 시스템에서 받은 단위가 SAP 단위와 다르면 호출 측에서 정합화가 필요합니다.
통화 자릿수
JPY(0자리)·KWD(3자리) 같은 통화에서 소수점 자릿수를 잘못 다루면 100배·1000배 어긋난 PO 가 생성됩니다. TCURX 의 통화별 자릿수를 사전 확인.
결재 흐름 (Release Strategy)
PO 가 결재 대상이면 BAPI 호출 시점에는 결재 대기 상태로 등록되고 GR / IR 처리는 결재 완료 후에만 가능합니다. 결재 대상 / 비대상 분기 로직을 인터페이스에 미리 박아두지 않으면 후속 흐름에서 막힙니다.
회사 간 발주 (UB / NB-Crossplant)
자회사·공장 간 발주에서는 DOC_TYPE = 'UB' + SUPPL_PLNT (공급 플랜트) 가 필요합니다. 일반 거래처 발주와 헤더 구조가 다르므로 분기 처리하시기 바랍니다.
COMMIT 누락
BAPI 자체 호출 성공이라도 BAPI_TRANSACTION_COMMIT 누락 시 DB 미반영. 비동기 인터페이스에서 COMMIT 직후 EKKO 를 SELECT 하려면 wait = 'X' 가 필수입니다.
전체 코드 — 복사용 통합본
*&---------------------------------------------------------------------*
*& Report Z_XX_PO_CREATE
*&---------------------------------------------------------------------*
*& 구매오더 생성 BAPI 호출 샘플 (자재 PO 표준)
*&---------------------------------------------------------------------*
REPORT z_xx_po_create.
DATA: ls_header TYPE bapimepoheader,
lt_item TYPE TABLE OF bapimepoitem,
ls_item TYPE bapimepoitem,
lt_sched TYPE TABLE OF bapimeposchedule,
ls_sched TYPE bapimeposchedule,
lt_return TYPE TABLE OF bapiret2,
ls_return TYPE bapiret2,
lv_ebeln TYPE bapimepoheader-po_number.
* ★ 1) 헤더 (PO 1건의 공통 정보)
ls_header-doc_type = 'NB'. " 표준 PO
ls_header-vendor = '0000100001'.
ls_header-purch_org = '1000'.
ls_header-pur_group = '001'.
ls_header-comp_code = '1000'.
ls_header-currency = 'KRW'.
ls_header-doc_date = sy-datum.
ls_header-pmnttrms = '0001'.
* ★ 2) 아이템 (자재 라인별)
ls_item-po_item = '00010'.
ls_item-material = 'TEST-MAT-001'.
ls_item-plant = '1000'.
ls_item-stge_loc = '0001'.
ls_item-quantity = '100'.
ls_item-po_unit = 'EA'.
ls_item-net_price = '5000'.
ls_item-price_unit = '1'.
ls_item-tax_code = 'V1'.
APPEND ls_item TO lt_item.
ls_item-po_item = '00020'.
ls_item-material = 'TEST-MAT-002'.
ls_item-plant = '1000'.
ls_item-stge_loc = '0001'.
ls_item-quantity = '50'.
ls_item-po_unit = 'EA'.
ls_item-net_price = '8000'.
ls_item-price_unit = '1'.
ls_item-tax_code = 'V1'.
APPEND ls_item TO lt_item.
* ★ 3) 일정라인 (아이템별 납기)
ls_sched-po_item = '00010'.
ls_sched-sched_line = '0001'.
ls_sched-delivery_date = sy-datum + 14.
ls_sched-quantity = '100'.
APPEND ls_sched TO lt_sched.
ls_sched-po_item = '00020'.
ls_sched-sched_line = '0001'.
ls_sched-delivery_date = sy-datum + 14.
ls_sched-quantity = '50'.
APPEND ls_sched TO lt_sched.
* ★ 4) BAPI 호출
CALL FUNCTION 'BAPI_PO_CREATE1'
EXPORTING
poheader = ls_header
* testrun = 'X' " 시뮬레이션 모드 시 활성화
IMPORTING
exppurchaseorder = lv_ebeln
TABLES
return = lt_return
poitem = lt_item
poschedule = lt_sched.
* ★ 5) RETURN 검증
READ TABLE lt_return INTO ls_return
WITH KEY type = 'E'.
IF sy-subrc = 0.
CALL FUNCTION 'BAPI_TRANSACTION_ROLLBACK'.
WRITE: / 'PO 생성 실패'.
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: / 'PO 생성 성공',
/ 'PO 번호:', lv_ebeln.
ENDIF.
같이 보면 좋은 글은 "BAPI_PO_CHANGE — 구매오더 변경·삭제 BAPI" 입니다. 생성과 변경을 한 세트로 익혀두면 PO 라이프사이클(생성 → 변경 → 입고 → 송장) 전체를 자동화할 수 있습니다.
요약
| 단계 | 처리 내용 | 핵심 포인트 |
|---|---|---|
| 1 | POHEADER | DOC_TYPE + 거래처 + 회사코드 |
| 2 | POITEM | 자재·플랜트·수량·단가 |
| 3 | POSCHEDULE | 납기 + 일정 수량 합계 = POITEM |
| 4 | POACCOUNT (선택) | 비용/자산/오더 PO 일 때만 |
| 5 | TESTRUN | 대량 배치 사전 검증 권장 |
| 6 | 호출 + COMMIT | RETURN 검증 후 wait='X' |
PO 생성 BAPI 는 시그니처가 크지만 HEADER + ITEM + SCHEDULE 3종 세트 만 정확히 채우면 표준 자재 PO 의 90% 케이스를 처리할 수 있습니다. 금액 / 수량 정합성(POITEM-QUANTITY = ∑POSCHEDULE-QUANTITY) 과 결재 흐름 / 회사 정책만 사전 확인하면 운영 인터페이스에서 안정적으로 동작합니다.
Disclaimer — 이 포스트는 실무 정리 노트를 바탕으로 AI 보조로 정리되었습니다.
BAPI 시그니처·구조체 필드(BAPIMEPOHEADER·BAPIMEPOITEM·BAPIMEPOSCHEDULE) · PO 유형(T161) 코드는 SAP MM 표준(패키지 ME · ECC 6.0 / S/4HANA on-premise) 기준이며, 사내 추가 BAdI · 결재 흐름 · 가격 결정 룰에 따라 일부 동작이 다를 수 있으니 운영 시스템 적용 전 개발·QA 환경에서 검증하시기 바랍니다.